From: Brendan Hansen Date: Sun, 17 Oct 2021 20:10:36 +0000 (-0500) Subject: successful multi-threading on NodeJS! X-Git-Url: https://git.brendanfh.com/?a=commitdiff_plain;h=928d819a1f0160a2eff397b80f3f5c39ea4d5987;p=onyx.git successful multi-threading on NodeJS! --- diff --git a/bin/onyx b/bin/onyx index 2a32dd07..f843ade9 100755 Binary files a/bin/onyx and b/bin/onyx differ diff --git a/bin/onyx-js b/bin/onyx-js index 3889ed9a..0aa2a126 100755 --- a/bin/onyx-js +++ b/bin/onyx-js @@ -1,29 +1,80 @@ #!/usr/bin/env node +const { Worker, isMainThread, parentPort, workerData } = require("worker_threads"); const fs = require('fs'); -const buf = fs.readFileSync(process.argv[2]); +let wasm_bytes; let wasm_instance; +let wasm_memory; const ENV = { + onyx: { memory: null }, + host: { print_str(ptr, len) { - const data = new Uint8Array(wasm_instance.exports.memory.buffer, ptr, len); + const data = new Uint8Array(wasm_memory.buffer, ptr, len); const str = new TextDecoder().decode(data); process.stdout.write(str); }, exit(status) { process.exit(status); - } - } + }, + + spawn_thread(funcidx, dataptr) { + try { + const worker = new Worker(__filename, { + workerData: { + memory: wasm_memory, + wasm_bytes: wasm_bytes, + funcidx: funcidx, + dataptr: dataptr, + }, + }); + + /*worker.on("exit", (code) => { + console.log("THREAD STOPPED"); + }); + */ + + return 1; + + } catch (e) { + console.error(e); + return 0; + } + }, + } } -WebAssembly.instantiate(new Uint8Array(buf), ENV) - .then(res => { - wasm_instance = res.instance; +if (isMainThread) { + wasm_bytes = fs.readFileSync(process.argv[2]); - const lib = res.instance.exports; - lib._start(); - }); + // main thread + wasm_memory = new WebAssembly.Memory({ initial: 1024, maximum: 65536, shared: true }); + ENV.onyx.memory = wasm_memory; + + WebAssembly.instantiate(new Uint8Array(wasm_bytes), ENV) + .then(res => { + wasm_instance = res.instance; + + const lib = res.instance.exports; + lib._start(); + }); + +} else { + let { memory, wasm_bytes, funcidx, dataptr } = workerData; + + ENV.onyx.memory = memory; + wasm_memory = memory; + + // worker thread + WebAssembly.instantiate(new Uint8Array(wasm_bytes), ENV) + .then(res => { + wasm_instance = res.instance; + + const lib = res.instance.exports; + lib._thread_start(funcidx, dataptr); + }); +} diff --git a/core/intrinsics/atomics.onyx b/core/intrinsics/atomics.onyx index cc3ea2e4..ea449c6b 100644 --- a/core/intrinsics/atomics.onyx +++ b/core/intrinsics/atomics.onyx @@ -3,8 +3,8 @@ package core.intrinsics.atomics #private_file { runtime :: package runtime - #if !#defined(runtime.Allow_Multi_Threading) { - // #error "Multi-threading is not enabled so you cannot include the 'core.intrinsics.atomics' package." + #if !runtime.Multi_Threading_Enabled { + #error "Multi-threading is not enabled so you cannot include the 'core.intrinsics.atomics' package." } } diff --git a/core/runtime/js.onyx b/core/runtime/js.onyx index 17cc99bf..8155181c 100644 --- a/core/runtime/js.onyx +++ b/core/runtime/js.onyx @@ -17,3 +17,15 @@ __exit :: (status: i32) -> void #foreign "host" "exit" --- __flush_stdio(); } + +#if Multi_Threading_Enabled { + __spawn_thread :: (func: (data: rawptr) -> void, data: rawptr) -> bool #foreign "host" "spawn_thread" --- + + #export "_thread_start" (func: (data: rawptr) -> void, data: rawptr) { + // Do thread initialization stuff... + // Just has to setup a stack frame for itself + __stack_top = raw_alloc(context.allocator, 1 << 20); + + func(data); + } +} \ No newline at end of file diff --git a/docs/todo b/docs/todo index ef21b982..11e007d1 100644 --- a/docs/todo +++ b/docs/todo @@ -176,3 +176,22 @@ Usability: - WASM directives (#export, #foreign) - #solidify directive - SIMD + + +To-Do list for threading capability in the browser: + [X] Emit a "shared" memory type + [X] Add compiler flags to control when the program should be built for multi threading + [X] WASM memories should be able to be imported, not just exported. + - But exported and not shared by default + [X] Change initialization process for main thread / worker threads + - _thread_start + [X] Write the JS glue code that will ship with the multi-threaded binary + [ ] Write basic threading primatives like mutex and semaphore + [ ] Make heap and printf take a mutex + + [ ] LATER: add thread-local variables + - Have a new global that is the TLS base pointer + - Any global variable (memres) tagged with #threadlocal will be placed in a block with the + base pointer being the new global + - The global is initialized to a heap allocated block at the start of each thread + diff --git a/include/astnodes.h b/include/astnodes.h index 78b4b570..4f6b1ab5 100644 --- a/include/astnodes.h +++ b/include/astnodes.h @@ -1270,6 +1270,7 @@ struct CompileOptions { b32 no_file_contents : 1; b32 use_post_mvp_features : 1; + b32 use_multi_threading : 1; Runtime runtime; diff --git a/include/wasm.h b/include/wasm.h index e651924c..22863784 100644 --- a/include/wasm.h +++ b/include/wasm.h @@ -564,8 +564,14 @@ typedef struct WasmExport { typedef struct WasmImport { WasmForeignKind kind; - i32 idx; - OnyxToken *mod, *name; + union { + i32 idx; + struct { + i32 min, max; + b32 shared; + }; + }; + char *mod, *name; } WasmImport; typedef struct WasmDatum { diff --git a/src/builtins.c b/src/builtins.c index f70ea09b..14c257ee 100644 --- a/src/builtins.c +++ b/src/builtins.c @@ -41,7 +41,7 @@ OnyxToken builtin_package_token = { Token_Type_Symbol, 7, "builtin ", { 0 } }; static OnyxToken builtin_heap_start_token = { Token_Type_Symbol, 12, "__heap_start ", { 0 } }; static OnyxToken builtin_stack_top_token = { Token_Type_Symbol, 11, "__stack_top ", { 0 } }; AstNumLit builtin_heap_start = { Ast_Kind_NumLit, Ast_Flag_Const, &builtin_heap_start_token, NULL, NULL, (AstType *) &basic_type_rawptr, NULL, 0 }; -AstGlobal builtin_stack_top = { Ast_Kind_Global, Ast_Flag_Const | Ast_Flag_Global_Stack_Top, &builtin_stack_top_token, NULL, NULL, (AstType *) &basic_type_rawptr, NULL }; +AstGlobal builtin_stack_top = { Ast_Kind_Global, Ast_Flag_Global_Stack_Top, &builtin_stack_top_token, NULL, NULL, (AstType *) &basic_type_rawptr, NULL }; AstType *builtin_string_type; AstType *builtin_range_type; @@ -447,4 +447,8 @@ void introduce_build_options(bh_allocator a) { AstNumLit* runtime_type = make_int_literal(a, context.options->runtime); symbol_builtin_introduce(p->scope, "Runtime", (AstNode *) runtime_type); + + AstNumLit* multi_threaded = make_int_literal(a, context.options->use_multi_threading); + multi_threaded->type_node = (AstType *) &basic_type_bool; + symbol_builtin_introduce(p->scope, "Multi_Threading_Enabled", (AstNode *) multi_threaded); } diff --git a/src/checker.c b/src/checker.c index a2f6b10e..3f754285 100644 --- a/src/checker.c +++ b/src/checker.c @@ -648,7 +648,7 @@ CheckStatus check_binaryop_assignment(AstBinaryOp** pbinop) { if ((binop->left->flags & Ast_Flag_Const) != 0 && binop->left->type != NULL) ERROR_(binop->token->pos, - "Cannot assign to constant '%b.'.", + "Cannot assign to constant '%b'.", binop->left->token->text, binop->left->token->length); if (binop->operation == Binary_Op_Assign) { diff --git a/src/onyx.c b/src/onyx.c index 6f1557d2..48b3f4e4 100644 --- a/src/onyx.c +++ b/src/onyx.c @@ -37,20 +37,21 @@ static const char* docstring = "Onyx compiler version " VERSION "\n" "\n" "Flags:\n" "\t List of initial files\n" - "\t-o Specify the target file (default: out.wasm)\n" + "\t-o Specify the target file (default: out.wasm).\n" "\t--runtime, -r Specifies a runtime. Can be: wasi, js, custom.\n" - "\t--verbose, -V Verbose output\n" - "\t -VV Very verbose output\n" - "\t -VVV Very very verbose output (to be used by compiler developers)\n" - "\t--use-post-mvp-features Enables post MVP WASM features such as memory.copy and memory.fill\n" + "\t--verbose, -V Verbose output.\n" + "\t -VV Very verbose output.\n" + "\t -VVV Very very verbose output (to be used by compiler developers).\n" + "\t--use-post-mvp-features Enables post MVP WASM features.\n" "\t--doc \n" + "\t--use-multi-threading Enables multi-threading for this compilation.\n" "\n" "Developer flags:\n" "\t--print-function-mappings Prints a mapping from WASM function index to source location.\n" "\t--print-static-if-results Prints the conditional result of each #if statement. Useful for debugging.\n" "\t--print-notes Prints the location of notes throughout the loaded code.\n" - "\t--no-colors Disables colors in the error message\n" - "\t--no-file-contents Disables '#file_contents' for security\n" + "\t--no-colors Disables colors in the error message.\n" + "\t--no-file-contents Disables '#file_contents' for security.\n" "\n"; @@ -62,9 +63,11 @@ static CompileOptions compile_opts_parse(bh_allocator alloc, int argc, char *arg .verbose_output = 0, .fun_output = 0, .print_function_mappings = 0, - .use_post_mvp_features = 0, .no_file_contents = 0, + .use_post_mvp_features = 0, + .use_multi_threading = 0, + .runtime = Runtime_Wasi, .files = NULL, @@ -117,6 +120,9 @@ static CompileOptions compile_opts_parse(bh_allocator alloc, int argc, char *arg else if (!strcmp(argv[i], "--use-post-mvp-features")) { options.use_post_mvp_features = 1; } + else if (!strcmp(argv[i], "--use-multi-threading")) { + options.use_multi_threading = 1; + } else if (!strcmp(argv[i], "-I")) { bh_arr_push(options.included_folders, argv[++i]); } diff --git a/src/wasm.c b/src/wasm.c index 52938dd4..51498d5e 100644 --- a/src/wasm.c +++ b/src/wasm.c @@ -3263,8 +3263,8 @@ static void emit_foreign_function(OnyxWasmModule* mod, AstFunction* fd) { WasmImport import = { .kind = WASM_FOREIGN_FUNCTION, .idx = type_idx, - .mod = fd->foreign_module, - .name = fd->foreign_name, + .mod = bh_aprintf(global_heap_allocator, "%b", fd->foreign_module->text, fd->foreign_module->length), + .name = bh_aprintf(global_heap_allocator, "%b", fd->foreign_name->text, fd->foreign_name->length), }; bh_arr_push(mod->imports, import); @@ -3332,8 +3332,8 @@ static void emit_foreign_global(OnyxWasmModule* module, AstGlobal* global) { WasmImport import = { .kind = WASM_FOREIGN_GLOBAL, .idx = global_type, - .mod = global->foreign_module, - .name = global->foreign_name, + .mod = bh_aprintf(global_heap_allocator, "%b", global->foreign_module->text, global->foreign_module->length), + .name = bh_aprintf(global_heap_allocator, "%b", global->foreign_name->text, global->foreign_name->length), }; bh_arr_push(module->imports, import); @@ -3696,14 +3696,28 @@ OnyxWasmModule onyx_wasm_module_create(bh_allocator alloc) { bh_arr_new(global_heap_allocator, module.local_allocations, 4); bh_arr_new(global_heap_allocator, module.stack_leave_patches, 4); - WasmExport mem_export = { - .kind = WASM_FOREIGN_MEMORY, - .idx = 0, - }; - // :ArbitraryConstant - // :WasmMemory - bh_table_put(WasmExport, module.exports, "memory", mem_export); - module.export_count++; + if (context.options->use_multi_threading) { + WasmImport mem_import = { + .kind = WASM_FOREIGN_MEMORY, + .min = 1024, + .max = 65536, // NOTE: Why not use all 4 Gigs of memory? + .shared = 1, + + .mod = "onyx", + .name = "memory", + }; + + bh_arr_push(module.imports, mem_import); + + } else { + WasmExport mem_export = { + .kind = WASM_FOREIGN_MEMORY, + .idx = 0, + }; + + bh_table_put(WasmExport, module.exports, "memory", mem_export); + module.export_count++; + } return module; } diff --git a/src/wasm_output.c b/src/wasm_output.c index 8a00b5da..92223cd4 100644 --- a/src/wasm_output.c +++ b/src/wasm_output.c @@ -47,11 +47,15 @@ static i32 output_name(const char* start, i32 length, bh_buffer* buff) { return buff->length - prev_len; } -static i32 output_limits(i32 min, i32 max, bh_buffer* buff) { +static i32 output_limits(i32 min, i32 max, b32 shared, bh_buffer* buff) { i32 leb_len, prev_len = buff->length; u8* leb; + + u8 mem_type = 0x00; + if (max >= 0) mem_type |= 0x01; + if (shared) mem_type |= 0x02; - bh_buffer_write_byte(buff, (max >= 0) ? 0x01 : 0x00); + bh_buffer_write_byte(buff, mem_type); leb = uint_to_uleb128((u64) min, &leb_len); bh_buffer_append(buff, leb, leb_len); @@ -148,7 +152,7 @@ static i32 output_tablesection(OnyxWasmModule* module, bh_buffer* buff) { // NOTE: funcrefs are the only valid table element type bh_buffer_write_byte(&vec_buff, 0x70); - output_limits(bh_arr_length(module->elems), -1, &vec_buff); + output_limits(bh_arr_length(module->elems), -1, 0, &vec_buff); leb = uint_to_uleb128((u64) (vec_buff.length), &leb_len); bh_buffer_append(buff, leb, leb_len); @@ -160,6 +164,8 @@ static i32 output_tablesection(OnyxWasmModule* module, bh_buffer* buff) { } static i32 output_memorysection(OnyxWasmModule* module, bh_buffer* buff) { + if (context.options->use_multi_threading) return 0; + i32 prev_len = buff->length; bh_buffer_write_byte(buff, WASM_SECTION_ID_MEMORY); @@ -173,7 +179,7 @@ static i32 output_memorysection(OnyxWasmModule* module, bh_buffer* buff) { // FIXME: This needs to be dynamically chosen depending on the size of // the data section and stack size pre-requeseted. // :WasmMemory - output_limits(1024, -1, &vec_buff); + output_limits(1024, -1, 0, &vec_buff); leb = uint_to_uleb128((u64) (vec_buff.length), &leb_len); bh_buffer_append(buff, leb, leb_len); @@ -227,16 +233,29 @@ static i32 output_importsection(OnyxWasmModule* module, bh_buffer* buff) { bh_buffer_append(&vec_buff, leb, leb_len); bh_arr_each(WasmImport, import, module->imports) { - output_name(import->mod->text, import->mod->length, &vec_buff); - output_name(import->name->text, import->name->length, &vec_buff); + output_name(import->mod, strlen(import->mod), &vec_buff); + output_name(import->name, strlen(import->name), &vec_buff); bh_buffer_write_byte(&vec_buff, (u8) import->kind); - - leb = uint_to_uleb128((u64) import->idx, &leb_len); - bh_buffer_append(&vec_buff, leb, leb_len); - - if (import->kind == WASM_FOREIGN_GLOBAL) { - // NOTE: All foreign globals are mutable - bh_buffer_write_byte(&vec_buff, 0x01); + + switch (import->kind) { + case WASM_FOREIGN_FUNCTION: + leb = uint_to_uleb128((u64) import->idx, &leb_len); + bh_buffer_append(&vec_buff, leb, leb_len); + break; + + case WASM_FOREIGN_GLOBAL: + leb = uint_to_uleb128((u64) import->idx, &leb_len); + bh_buffer_append(&vec_buff, leb, leb_len); + + // NOTE: All foreign globals are mutable + bh_buffer_write_byte(&vec_buff, 0x01); + break; + + case WASM_FOREIGN_MEMORY: + output_limits(import->min, import->max, import->shared, &vec_buff); + break; + + case WASM_FOREIGN_TABLE: assert(0); } }