diff options
| author | Bruce Hill <bruce@bruce-hill.com> | 2025-01-02 20:29:55 -0500 |
|---|---|---|
| committer | Bruce Hill <bruce@bruce-hill.com> | 2025-01-02 20:29:55 -0500 |
| commit | b025cf269d2e07e179be4a0e34d936862dc640c2 (patch) | |
| tree | 6bcce81829436a08dbe4f0101044c0cd07811525 | |
| parent | 500e4e1bd74b45476c4df0f4f9da72fbada9bb18 (diff) | |
Use `holding` blocks for mutexed data instead of lambdas
| -rw-r--r-- | Makefile | 2 | ||||
| -rw-r--r-- | ast.c | 2 | ||||
| -rw-r--r-- | ast.h | 8 | ||||
| -rw-r--r-- | compile.c | 76 | ||||
| -rw-r--r-- | docs/mutexed.md | 28 | ||||
| -rw-r--r-- | parse.c | 28 | ||||
| -rw-r--r-- | stdlib/arrays.h | 4 | ||||
| -rw-r--r-- | stdlib/datatypes.h | 5 | ||||
| -rw-r--r-- | stdlib/pointers.c | 6 | ||||
| -rw-r--r-- | stdlib/stdlib.c | 30 | ||||
| -rw-r--r-- | stdlib/stdlib.h | 2 | ||||
| -rw-r--r-- | stdlib/threads.c | 4 | ||||
| -rw-r--r-- | stdlib/tomo.h | 1 | ||||
| -rw-r--r-- | stdlib/types.h | 4 | ||||
| -rw-r--r-- | test/optionals.tm | 20 | ||||
| -rw-r--r-- | test/threads.tm | 56 | ||||
| -rw-r--r-- | typecheck.c | 22 | ||||
| -rw-r--r-- | types.c | 11 | ||||
| -rw-r--r-- | types.h | 3 |
19 files changed, 216 insertions, 96 deletions
@@ -34,7 +34,7 @@ BUILTIN_OBJS=stdlib/siphash.o stdlib/arrays.o stdlib/bools.o stdlib/bytes.o stdl stdlib/pointers.o stdlib/memory.o stdlib/text.o stdlib/threads.o stdlib/c_strings.o stdlib/tables.o \ stdlib/types.o stdlib/util.o stdlib/files.o stdlib/ranges.o stdlib/shell.o stdlib/paths.o stdlib/rng.o \ stdlib/optionals.o stdlib/patterns.o stdlib/metamethods.o stdlib/functiontype.o stdlib/stdlib.o \ - stdlib/structs.o stdlib/enums.o stdlib/moments.o + stdlib/structs.o stdlib/enums.o stdlib/moments.o stdlib/mutexeddata.o TESTS=$(patsubst %.tm,%.tm.testresult,$(wildcard test/*.tm)) all: libtomo.so tomo @@ -117,6 +117,7 @@ CORD ast_to_xml(ast_t *ast) T(HeapAllocate, "<HeapAllocate>%r</HeapAllocate>", ast_to_xml(data.value)) T(StackReference, "<StackReference>%r</StackReference>", ast_to_xml(data.value)) T(Mutexed, "<Mutexed>%r</Mutexed>", ast_to_xml(data.value)) + T(Holding, "<Holding>%r%r</Holding>", ast_to_xml(data.mutexed), ast_to_xml(data.body)) T(Min, "<Min>%r%r%r</Min>", ast_to_xml(data.lhs), ast_to_xml(data.rhs), optional_tagged("key", data.key)) T(Max, "<Max>%r%r%r</Max>", ast_to_xml(data.lhs), ast_to_xml(data.rhs), optional_tagged("key", data.key)) T(Array, "<Array>%r%r</Array>", optional_tagged_type("item-type", data.item_type), ast_list_to_xml(data.items)) @@ -185,6 +186,7 @@ CORD type_ast_to_xml(type_ast_t *t) T(TableTypeAST, "<TableType>%r %r</TableType>", type_ast_to_xml(data.key), type_ast_to_xml(data.value)) T(FunctionTypeAST, "<FunctionType>%r %r</FunctionType>", arg_list_to_xml(data.args), type_ast_to_xml(data.ret)) T(OptionalTypeAST, "<OptionalType>%r</OptionalType>", data.type) + T(MutexedTypeAST, "<MutexedType>%r</MutexedType>", data.type) #undef T default: return CORD_EMPTY; } @@ -78,6 +78,7 @@ typedef enum { TableTypeAST, FunctionTypeAST, OptionalTypeAST, + MutexedTypeAST, } type_ast_e; typedef struct tag_ast_s { @@ -116,7 +117,7 @@ struct type_ast_s { } FunctionTypeAST; struct { type_ast_t *type; - } OptionalTypeAST; + } OptionalTypeAST, MutexedTypeAST; } __data; }; @@ -127,7 +128,7 @@ typedef enum { TextLiteral, TextJoin, PrintStatement, Declare, Assign, BinaryOp, UpdateAssign, - Not, Negative, HeapAllocate, StackReference, Mutexed, + Not, Negative, HeapAllocate, StackReference, Mutexed, Holding, Min, Max, Array, Set, Table, TableEntry, Comprehension, FunctionDef, Lambda, @@ -195,6 +196,9 @@ struct ast_s { ast_t *value; } Not, Negative, HeapAllocate, StackReference, Mutexed; struct { + ast_t *mutexed, *body; + } Holding; + struct { ast_t *lhs, *rhs, *key; } Min, Max; struct { @@ -249,6 +249,7 @@ CORD compile_type(type_t *t) case AbortType: return "void"; case VoidType: return "void"; case MemoryType: return "void"; + case MutexedType: return "MutexedData_t"; case BoolType: return "Bool_t"; case ByteType: return "Byte_t"; case CStringType: return "char*"; @@ -296,7 +297,7 @@ CORD compile_type(type_t *t) case OptionalType: { type_t *nonnull = Match(t, OptionalType)->type; switch (nonnull->tag) { - case CStringType: case FunctionType: case ClosureType: + case CStringType: case FunctionType: case ClosureType: case MutexedType: case PointerType: case EnumType: return compile_type(nonnull); case TextType: @@ -459,6 +460,8 @@ CORD check_none(type_t *t, CORD value) return CORD_all("((", value, ").tag == 0)"); else if (t->tag == MomentType) return CORD_all("((", value, ").tv_usec < 0)"); + else if (t->tag == MutexedType) + return CORD_all("(", value, " == NULL)"); errx(1, "Optional check not implemented for: %T", t); } @@ -1038,7 +1041,10 @@ CORD compile_statement(env_t *env, ast_t *ast) CORD code = CORD_EMPTY; for (deferral_t *deferred = env->deferred; deferred && deferred != ctx->deferred; deferred = deferred->next) code = CORD_all(code, compile_statement(deferred->defer_env, deferred->block)); - return CORD_all(code, "goto ", ctx->skip_label, ";"); + if (code) + return CORD_all("{\n", code, "goto ", ctx->skip_label, ";\n}\n"); + else + return CORD_all("goto ", ctx->skip_label, ";"); } } if (env->loop_ctx) @@ -1064,7 +1070,10 @@ CORD compile_statement(env_t *env, ast_t *ast) CORD code = CORD_EMPTY; for (deferral_t *deferred = env->deferred; deferred && deferred != ctx->deferred; deferred = deferred->next) code = CORD_all(code, compile_statement(deferred->defer_env, deferred->block)); - return CORD_all(code, "goto ", ctx->stop_label, ";"); + if (code) + return CORD_all("{\n", code, "goto ", ctx->stop_label, ";\n}\n"); + else + return CORD_all("goto ", ctx->stop_label, ";"); } } if (env->loop_ctx) @@ -1181,6 +1190,30 @@ CORD compile_statement(env_t *env, ast_t *ast) loop = CORD_all(loop, "\n", loop_ctx.stop_label, ":;"); return loop; } + case Holding: { + ast_t *held = Match(ast, Holding)->mutexed; + type_t *held_type = get_type(env, held); + if (held_type->tag != MutexedType) + code_err(held, "This is a %t, not a mutexed value", held_type); + CORD code = CORD_all( + "{ // Holding\n", + "MutexedData_t mutexed = ", compile(env, held), ";\n", + "pthread_mutex_lock(&mutexed->mutex);\n"); + + env_t *body_scope = fresh_scope(env); + body_scope->deferred = new(deferral_t, .defer_env=env, + .block=FakeAST(InlineCCode, .code="pthread_mutex_unlock(&mutexed->mutex);"), + .next=body_scope->deferred); + if (held->tag == Var) { + CORD held_var = CORD_all(Match(held, Var)->name, "$held"); + set_binding(body_scope, Match(held, Var)->name, new(binding_t, .type=Type(PointerType, .pointed=Match(held_type, MutexedType)->type, .is_stack=true), + .code=held_var)); + code = CORD_all(code, compile_declaration(Type(PointerType, .pointed=Match(held_type, MutexedType)->type), held_var), + " = (", compile_type(Type(PointerType, .pointed=Match(held_type, MutexedType)->type)), ")mutexed->data;\n"); + } + return CORD_all(code, compile_statement(body_scope, Match(ast, Holding)->body), + "pthread_mutex_unlock(&mutexed->mutex);\n}"); + } case For: { auto for_ = Match(ast, For); @@ -1585,7 +1618,8 @@ CORD expr_as_text(env_t *env, CORD expr, type_t *t, CORD color) case FunctionType: case ClosureType: return CORD_asprintf("Func$as_text(stack(%r), %r, %r)", expr, color, compile_type_info(env, t)); case PointerType: return CORD_asprintf("Pointer$as_text(stack(%r), %r, %r)", expr, color, compile_type_info(env, t)); case OptionalType: return CORD_asprintf("Optional$as_text(stack(%r), %r, %r)", expr, color, compile_type_info(env, t)); - case StructType: case EnumType: return CORD_asprintf("generic_as_text(stack(%r), %r, %r)", expr, color, compile_type_info(env, t)); + case StructType: case EnumType: case MutexedType: + return CORD_asprintf("generic_as_text(stack(%r), %r, %r)", expr, color, compile_type_info(env, t)); default: compiler_err(NULL, NULL, NULL, "Stringifying is not supported for %T", t); } } @@ -1997,6 +2031,7 @@ CORD compile_none(type_t *t) env_t *enum_env = Match(t, EnumType)->env; return CORD_all("((", compile_type(t), "){", namespace_prefix(enum_env, enum_env->namespace), "null})"); } + case MutexedType: return "NONE_MUTEXED_DATA"; default: compiler_err(NULL, NULL, NULL, "none isn't implemented for this type: %T", t); } } @@ -2125,7 +2160,34 @@ CORD compile(env_t *env, ast_t *ast) return CORD_all("stack(", compile(env, subject), ")"); } case Mutexed: { - return CORD_all("mutexed(", compile(env, Match(ast, Mutexed)->value), ")"); + ast_t *mutexed = Match(ast, Mutexed)->value; + return CORD_all("new(struct MutexedData_s, .mutex=PTHREAD_MUTEX_INITIALIZER, .data=", compile(env, WrapAST(mutexed, HeapAllocate, mutexed)), ")"); + } + case Holding: { + ast_t *held = Match(ast, Holding)->mutexed; + type_t *held_type = get_type(env, held); + if (held_type->tag != MutexedType) + code_err(held, "This is a %t, not a mutexed value", held_type); + CORD code = CORD_all( + "({ // Holding\n", + "MutexedData_t mutexed = ", compile(env, held), ";\n", + "pthread_mutex_lock(&mutexed->mutex);\n"); + + env_t *body_scope = fresh_scope(env); + body_scope->deferred = new(deferral_t, .defer_env=env, + .block=FakeAST(InlineCCode, .code="pthread_mutex_unlock(&mutexed->mutex);"), + .next=body_scope->deferred); + if (held->tag == Var) { + CORD held_var = CORD_all(Match(held, Var)->name, "$held"); + set_binding(body_scope, Match(held, Var)->name, new(binding_t, .type=Type(PointerType, .pointed=Match(held_type, MutexedType)->type, .is_stack=true), + .code=held_var)); + code = CORD_all(code, compile_declaration(Type(PointerType, .pointed=Match(held_type, MutexedType)->type), held_var), + " = (", compile_type(Type(PointerType, .pointed=Match(held_type, MutexedType)->type)), ")mutexed->data;\n"); + } + type_t *body_type = get_type(env, ast); + return CORD_all(code, compile_declaration(body_type, "result"), " = ", compile(body_scope, Match(ast, Holding)->body), ";\n" + "pthread_mutex_unlock(&mutexed->mutex);\n" + "result;\n})"); } case Optional: { ast_t *value = Match(ast, Optional)->value; @@ -3812,6 +3874,10 @@ CORD compile_type_info(env_t *env, type_t *t) type_t *non_optional = Match(t, OptionalType)->type; return CORD_asprintf("Optional$info(%zu, %zu, %r)", type_size(t), type_align(t), compile_type_info(env, non_optional)); } + case MutexedType: { + type_t *mutexed = Match(t, MutexedType)->type; + return CORD_all("MutexedData$info(", compile_type_info(env, mutexed), ")"); + } case TypeInfoType: return CORD_all("Type$info(", CORD_quoted(type_to_cord(Match(t, TypeInfoType)->type)), ")"); case MemoryType: return "&Memory$info"; case VoidType: return "&Void$info"; diff --git a/docs/mutexed.md b/docs/mutexed.md index eebf9015..97357f39 100644 --- a/docs/mutexed.md +++ b/docs/mutexed.md @@ -1,24 +1,34 @@ # Mutexed Data To serve the general case of synchronizing access to shared datastructures, -Tomo uses the `mutexed` keyword, which returns a function which can be used -to ensure that all access to a datastructure is guarded by a mutex: +Tomo uses the `mutexed` keyword, which allocates a mutex and some heap memory +for a value. Access to the heap-allocated value's memory can only be obtained +by using a `holding` block. `holding` blocks ensure that the underlying mutex +is locked before entering the block and unlocked before leaving it (even if a +short-circuiting control flow statement like `return` or `stop` is used). Here +is a simple example: ```tomo -with_nums := mutexed [10, 20, 30] +nums := mutexed [10, 20, 30] + +>> nums += mutexed [Int]<0x12345678> : mutexed([Int]) + +holding nums: + # Inside this block, the type of `nums` is `&[Int]` + >> nums + = &[10, 20, 30] : &[Int] thread := Thread.new(func(): - with_nums(func(nums:&[Int]): + holding nums: nums:insert(30) - ) ) -with_nums(func(nums:&[Int]): +holding nums: nums:insert(40) -) thread:join() ``` -Without having a mutex guard, the code above could run into concurrency issues -leading to data corruption. +Without using a mutex, the code above could run into concurrency issues leading +to data corruption. @@ -52,7 +52,7 @@ int op_tightness[] = { static const char *keywords[] = { "yes", "xor", "while", "when", "use", "unless", "struct", "stop", "skip", "return", - "or", "not", "no", "mutexed", "mod1", "mod", "pass", "lang", "inline", "in", "if", + "or", "not", "no", "mutexed", "mod1", "mod", "pass", "lang", "inline", "in", "if", "holding", "func", "for", "extern", "enum", "else", "do", "deserialize", "defer", "and", "none", "_min_", "_max_", NULL, }; @@ -85,6 +85,7 @@ static type_ast_t *parse_array_type(parse_ctx_t *ctx, const char *pos); static type_ast_t *parse_func_type(parse_ctx_t *ctx, const char *pos); static type_ast_t *parse_non_optional_type(parse_ctx_t *ctx, const char *pos); static type_ast_t *parse_pointer_type(parse_ctx_t *ctx, const char *pos); +static type_ast_t *parse_mutexed_type(parse_ctx_t *ctx, const char *pos); static type_ast_t *parse_set_type(parse_ctx_t *ctx, const char *pos); static type_ast_t *parse_table_type(parse_ctx_t *ctx, const char *pos); static type_ast_t *parse_type(parse_ctx_t *ctx, const char *pos); @@ -105,6 +106,7 @@ static PARSER(parse_file_body); static PARSER(parse_for); static PARSER(parse_func_def); static PARSER(parse_heap_alloc); +static PARSER(parse_holding); static PARSER(parse_if); static PARSER(parse_inline_c); static PARSER(parse_int); @@ -635,6 +637,19 @@ type_ast_t *parse_pointer_type(parse_ctx_t *ctx, const char *pos) { return ptr_type; } +type_ast_t *parse_mutexed_type(parse_ctx_t *ctx, const char *pos) { + const char *start = pos; + if (!match_word(&pos, "mutexed")) return NULL; + spaces(&pos); + if (!match(&pos, "(")) return NULL; + spaces(&pos); + type_ast_t *mutexed = expect(ctx, start, &pos, parse_type, + "I couldn't parse a mutexed type after this point"); + spaces(&pos); + if (!match(&pos, ")")) return NULL; + return NewTypeAST(ctx->file, start, pos, MutexedTypeAST, .type=mutexed); +} + type_ast_t *parse_type_name(parse_ctx_t *ctx, const char *pos) { const char *start = pos; const char *id = get_id(&pos); @@ -661,6 +676,7 @@ type_ast_t *parse_non_optional_type(parse_ctx_t *ctx, const char *pos) { || (type=parse_set_type(ctx, pos)) || (type=parse_type_name(ctx, pos)) || (type=parse_func_type(ctx, pos)) + || (type=parse_mutexed_type(ctx, pos)) ); if (!success && match(&pos, "(")) { whitespace(&pos); @@ -1271,6 +1287,15 @@ PARSER(parse_mutexed) { return NewAST(ctx->file, start, pos, Mutexed, .value=val); } +PARSER(parse_holding) { + const char *start = pos; + if (!match_word(&pos, "holding")) return NULL; + spaces(&pos); + ast_t *mutexed = expect(ctx, start, &pos, parse_expr, "I expected an expression for this 'holding'"); + ast_t *body = expect(ctx, start, &pos, parse_block, "I expected a block to be here"); + return NewAST(ctx->file, start, pos, Holding, .mutexed=mutexed, .body=body); +} + PARSER(parse_not) { const char *start = pos; if (!match_word(&pos, "not")) return NULL; @@ -1950,6 +1975,7 @@ PARSER(parse_extended_expr) { || (expr=optional(ctx, &pos, parse_when)) || (expr=optional(ctx, &pos, parse_repeat)) || (expr=optional(ctx, &pos, parse_do)) + || (expr=optional(ctx, &pos, parse_holding)) ) return expr; diff --git a/stdlib/arrays.h b/stdlib/arrays.h index cf1f088b..53681dc7 100644 --- a/stdlib/arrays.h +++ b/stdlib/arrays.h @@ -59,12 +59,12 @@ #define ARRAY_DECREF(arr) (arr).data_refcount -= ((arr).data_refcount < ARRAY_MAX_DATA_REFCOUNT) #define ARRAY_COPY(arr) ({ ARRAY_INCREF(arr); arr; }) -#define Array$insert_value(arr, item_expr, index, padded_item_size) ({ __typeof(item_expr) item = item_expr; Array$insert(arr, &item, index, padded_item_size); }) +#define Array$insert_value(arr, item_expr, index, padded_item_size) Array$insert(arr, (__typeof(item_expr)[1]){item_expr}, index, padded_item_size) void Array$insert(Array_t *arr, const void *item, Int_t index, int64_t padded_item_size); void Array$insert_all(Array_t *arr, Array_t to_insert, Int_t index, int64_t padded_item_size); void Array$remove_at(Array_t *arr, Int_t index, Int_t count, int64_t padded_item_size); void Array$remove_item(Array_t *arr, void *item, Int_t max_removals, const TypeInfo_t *type); -#define Array$remove_item_value(arr, item_expr, max, type) ({ __typeof(item_expr) item = item_expr; Array$remove_item(arr, &item, max, type); }) +#define Array$remove_item_value(arr, item_expr, max, type) Array$remove_item(arr, (__typeof(item_expr)[1]){item_expr}, max, type) #define Array$pop(arr_expr, index_expr, item_type, nonnone_var, nonnone_expr, none_expr, padded_item_size) ({ \ Array_t *arr = arr_expr; \ diff --git a/stdlib/datatypes.h b/stdlib/datatypes.h index 9c3cabc2..11ae130e 100644 --- a/stdlib/datatypes.h +++ b/stdlib/datatypes.h @@ -89,4 +89,9 @@ typedef struct timeval Moment_t; typedef struct RNGState_t* RNG_t; +typedef struct MutexedData_s { + pthread_mutex_t mutex; + void *data; +} *MutexedData_t; + // vim: ts=4 sw=0 et cino=L2,l1,(0,W4,m1,\:0 diff --git a/stdlib/pointers.c b/stdlib/pointers.c index 40348965..76e882ec 100644 --- a/stdlib/pointers.c +++ b/stdlib/pointers.c @@ -18,12 +18,10 @@ public Text_t Pointer$as_text(const void *x, bool colorize, const TypeInfo_t *ty auto ptr_info = type->PointerInfo; if (!x) { Text_t typename = generic_as_text(NULL, false, ptr_info.pointed); - Text_t text; if (colorize) - text = Text$concat(Text("\x1b[34;1m"), Text$from_str(ptr_info.sigil), typename, Text("\x1b[m")); + return Text$concat(Text("\x1b[34;1m"), Text$from_str(ptr_info.sigil), typename, Text("\x1b[m")); else - text = Text$concat(Text$from_str(ptr_info.sigil), typename); - return text; + return Text$concat(Text$from_str(ptr_info.sigil), typename); } const void *ptr = *(const void**)x; if (!ptr) { diff --git a/stdlib/stdlib.c b/stdlib/stdlib.c index 0266e788..cb9d2213 100644 --- a/stdlib/stdlib.c +++ b/stdlib/stdlib.c @@ -621,34 +621,4 @@ public void sleep_num(double seconds) nanosleep(&ts, NULL); } -static void use_mutexed(Closure_t fn, void *userdata) -{ - void (*call)(void*, void*) = fn.fn; - struct data_t { - pthread_mutex_t mutex; - char item[0] __attribute__ ((aligned (8))); - }; - pthread_mutex_t *mutex = &((struct data_t*)userdata)->mutex; - pthread_mutex_lock(mutex); - void *mutexed_item = (void*)((struct data_t*)userdata)->item; - call(mutexed_item, fn.userdata); - pthread_mutex_unlock(mutex); -} - -public Closure_t _mutexed(const void *item, size_t size) -{ - struct data_t { - pthread_mutex_t mutex; - char item[size] __attribute__ ((aligned (8))); - }; - struct data_t *userdata = GC_MALLOC(sizeof(struct data_t)); - - pthread_mutex_init(&userdata->mutex, NULL); - memcpy(userdata->item, item, size); - return (Closure_t){ - .fn=(void*)use_mutexed, - .userdata=(void*)userdata, - }; -} - // vim: ts=4 sw=0 et cino=L2,l1,(0,W4,m1,\:0 diff --git a/stdlib/stdlib.h b/stdlib/stdlib.h index c68442a6..7c9bd7e1 100644 --- a/stdlib/stdlib.h +++ b/stdlib/stdlib.h @@ -42,7 +42,5 @@ Closure_t spawn(Closure_t fn); bool pop_flag(char **argv, int *i, const char *flag, Text_t *result); void print_stack_trace(FILE *out, int start, int stop); void sleep_num(double seconds); -public Closure_t _mutexed(const void *item, size_t size); -#define mutexed(item) _mutexed((__typeof(item)[1]){item}, sizeof(item)) // vim: ts=4 sw=0 et cino=L2,l1,(0,W4,m1,\:0 diff --git a/stdlib/threads.c b/stdlib/threads.c index c5690111..efd23546 100644 --- a/stdlib/threads.c +++ b/stdlib/threads.c @@ -34,8 +34,8 @@ static void *run_thread(Closure_t *closure) public Thread_t Thread$new(Closure_t fn) { Thread_t thread = new(pthread_t); - Closure_t *doop = new(Closure_t, .fn=fn.fn, .userdata=fn.userdata); - pthread_create(thread, NULL, (void*)run_thread, doop); + Closure_t *closure = new(Closure_t, .fn=fn.fn, .userdata=fn.userdata); + pthread_create(thread, NULL, (void*)run_thread, closure); return thread; } diff --git a/stdlib/tomo.h b/stdlib/tomo.h index 10ac3668..7ed25412 100644 --- a/stdlib/tomo.h +++ b/stdlib/tomo.h @@ -18,6 +18,7 @@ #include "memory.h" #include "metamethods.h" #include "moments.h" +#include "mutexeddata.h" #include "nums.h" #include "optionals.h" #include "paths.h" diff --git a/stdlib/types.h b/stdlib/types.h index 36508393..2c9abcfc 100644 --- a/stdlib/types.h +++ b/stdlib/types.h @@ -30,7 +30,7 @@ struct TypeInfo_s { metamethods_t metamethods; struct { // Anonymous tagged union for convenience enum { OpaqueInfo, StructInfo, EnumInfo, PointerInfo, TextInfo, ArrayInfo, TableInfo, FunctionInfo, - OptionalInfo, TypeInfoInfo } tag; + OptionalInfo, MutexedDataInfo, TypeInfoInfo } tag; union { struct {} OpaqueInfo; struct { @@ -54,7 +54,7 @@ struct TypeInfo_s { } TypeInfoInfo; struct { const TypeInfo_t *type; - } OptionalInfo; + } OptionalInfo, MutexedDataInfo; struct { const char *name; int num_tags; diff --git a/test/optionals.tm b/test/optionals.tm index 13a8dcae..3ef7f28e 100644 --- a/test/optionals.tm +++ b/test/optionals.tm @@ -67,6 +67,12 @@ func maybe_thread(should_i:Bool->Thread?): else: return none +func maybe_mutexed(should_i:Bool->mutexed(Bool)?): + if should_i: + return mutexed no + else: + return none + func main(): >> 5? = 5 : Int? @@ -251,6 +257,20 @@ func main(): fail("Truthy: $nope") else: !! Falsey: $nope + do: + !! ... + !! Mutexed: + >> yep := maybe_mutexed(yes) + # No "=" test here because threads use addresses in the text version + >> nope := maybe_mutexed(no) + = none : mutexed(Bool)? + >> if yep: >> yep + else: fail("Falsey: $yep") + >> if nope: + fail("Truthy: $nope") + else: !! Falsey: $nope + + if yep := maybe_int(yes): >> yep = 123 : Int diff --git a/test/threads.tm b/test/threads.tm index 772c866f..adf7784a 100644 --- a/test/threads.tm +++ b/test/threads.tm @@ -2,55 +2,47 @@ enum Job(Increment(x:Int), Decrement(x:Int)) func main(): do: - >> with_nums := mutexed [10, 20, 30] - with_nums(func(nums:&[Int]): + >> nums := mutexed [10, 20, 30] + holding nums: >> nums[] = [10, 20, 30] nums:insert(40) - ) - with_nums(func(nums:&[Int]): + + holding nums: >> nums[] = [10, 20, 30, 40] - ) - with_jobs := mutexed [Job.Increment(5)] - enqueue_job := func(job:Job): - with_jobs(func(jobs:&[Job]): jobs:insert(job)) - dequeue_job := func(): - job := @none:Job - with_jobs(func(jobs:&[Job]): job[] = jobs:pop(1)) - job[] + jobs := mutexed [Job.Increment(5)] - with_results := mutexed [:Int] - enqueue_result := func(result:Int): - with_results(func(results:&[Int]): results:insert(result)) - dequeue_result := func(): - result := @none:Int - repeat: - with_results(func(results:&[Int]): result[] = results:pop(1)) - stop if result[] - sleep(0.00001) - result[]! + results := mutexed [:Int] >> thread := Thread.new(func(): !! In another thread! repeat: - job := dequeue_job() or stop + job := holding jobs: (jobs:pop(1) or stop) when job is Increment(x): - enqueue_result(x + 1) + holding results: results:insert(x + 1) is Decrement(x): - enqueue_result(x - 1) + holding results: results:insert(x - 1) ) - enqueue_job(Decrement(100)) - enqueue_job(Decrement(200)) - enqueue_job(Decrement(300)) - enqueue_job(Decrement(400)) - enqueue_job(Decrement(500)) - enqueue_job(Decrement(600)) + holding jobs: + jobs:insert(Decrement(100)) + jobs:insert(Decrement(200)) + jobs:insert(Decrement(300)) + jobs:insert(Decrement(400)) + jobs:insert(Decrement(500)) + jobs:insert(Decrement(600)) + jobs:insert(Increment(1000)) - >> enqueue_job(Increment(1000)) + dequeue_result := func(): + result := none:Int + repeat: + result = (holding results: results:pop(1)) + stop if result + sleep(0.00001) + return result! >> dequeue_result() = 6 diff --git a/typecheck.c b/typecheck.c index 5bc33181..fbee0d07 100644 --- a/typecheck.c +++ b/typecheck.c @@ -123,6 +123,13 @@ type_t *parse_type_ast(env_t *env, type_ast_t *ast) code_err(ast, "Nested optional types are not currently supported"); return Type(OptionalType, .type=t); } + case MutexedTypeAST: { + type_ast_t *mutexed = Match(ast, MutexedTypeAST)->type; + type_t *t = parse_type_ast(env, mutexed); + if (t->tag == VoidType || t->tag == AbortType || t->tag == ReturnType) + code_err(ast, "Mutexed %T types are not supported.", t); + return Type(MutexedType, .type=t); + } case UnknownTypeAST: code_err(ast, "I don't know how to get this type"); } #pragma GCC diagnostic pop @@ -952,9 +959,18 @@ type_t *get_type(env_t *env, ast_t *ast) } case Mutexed: { type_t *item_type = get_type(env, Match(ast, Mutexed)->value); - type_t *user_thunk = Type(ClosureType, Type(FunctionType, .args=new(arg_t, .name="object", .type=Type(PointerType, .pointed=item_type, .is_stack=true)), - .ret=Type(VoidType))); - return Type(ClosureType, Type(FunctionType, .args=new(arg_t, .name="fn", .type=user_thunk), .ret=Type(VoidType))); + return Type(MutexedType, .type=item_type); + } + case Holding: { + ast_t *held = Match(ast, Holding)->mutexed; + type_t *held_type = get_type(env, held); + if (held_type->tag != MutexedType) + code_err(held, "This is a %t, not a mutexed value", held_type); + if (held->tag == Var) { + env = fresh_scope(env); + set_binding(env, Match(held, Var)->name, new(binding_t, .type=Type(PointerType, .pointed=Match(held_type, MutexedType)->type, .is_stack=true))); + } + return get_type(env, Match(ast, Holding)->body); } case BinaryOp: { auto binop = Match(ast, BinaryOp); @@ -85,6 +85,13 @@ CORD type_to_cord(type_t *t) { else return "(Unknown optional type)"; } + case MutexedType: { + type_t *opt = Match(t, MutexedType)->type; + if (opt) + return CORD_all("mutexed ", type_to_cord(opt)); + else + return "(Unknown optional type)"; + } case TypeInfoType: { return CORD_all("Type$info(", Match(t, TypeInfoType)->name, ")"); } @@ -246,6 +253,7 @@ PUREFUNC bool has_heap_memory(type_t *t) case SetType: return true; case PointerType: return true; case OptionalType: return has_heap_memory(Match(t, OptionalType)->type); + case MutexedType: return true; case BigIntType: return true; case StructType: { for (arg_t *field = Match(t, StructType)->fields; field; field = field->next) { @@ -270,6 +278,7 @@ PUREFUNC bool has_stack_memory(type_t *t) switch (t->tag) { case PointerType: return Match(t, PointerType)->is_stack; case OptionalType: return has_stack_memory(Match(t, OptionalType)->type); + case MutexedType: return has_stack_memory(Match(t, MutexedType)->type); default: return false; } } @@ -498,6 +507,7 @@ PUREFUNC size_t type_size(type_t *t) case FunctionType: return sizeof(void*); case ClosureType: return sizeof(struct {void *fn, *userdata;}); case PointerType: return sizeof(void*); + case MutexedType: return sizeof(MutexedData_t); case OptionalType: { type_t *nonnull = Match(t, OptionalType)->type; switch (nonnull->tag) { @@ -582,6 +592,7 @@ PUREFUNC size_t type_align(type_t *t) case FunctionType: return __alignof__(void*); case ClosureType: return __alignof__(struct {void *fn, *userdata;}); case PointerType: return __alignof__(void*); + case MutexedType: return __alignof__(MutexedData_t); case OptionalType: { type_t *nonnull = Match(t, OptionalType)->type; switch (nonnull->tag) { @@ -60,6 +60,7 @@ struct type_s { EnumType, OptionalType, TypeInfoType, + MutexedType, ModuleType, } tag; @@ -117,7 +118,7 @@ struct type_s { } EnumType; struct { type_t *type; - } OptionalType; + } OptionalType, MutexedType; struct { const char *name; type_t *type; |
