successful multi-threading on NodeJS!
authorBrendan Hansen <brendan.f.hansen@gmail.com>
Sun, 17 Oct 2021 20:10:36 +0000 (15:10 -0500)
committerBrendan Hansen <brendan.f.hansen@gmail.com>
Sun, 17 Oct 2021 20:10:36 +0000 (15:10 -0500)
12 files changed:
bin/onyx
bin/onyx-js
core/intrinsics/atomics.onyx
core/runtime/js.onyx
docs/todo
include/astnodes.h
include/wasm.h
src/builtins.c
src/checker.c
src/onyx.c
src/wasm.c
src/wasm_output.c

index 2a32dd070fee55cee4cde8737fd6021207e3baaf..f843ade9b013b3269d87e4fd65c623223f3d38a1 100755 (executable)
Binary files a/bin/onyx and b/bin/onyx differ
index 3889ed9a91daf55e78ea44ace808ee63ed0eb580..0aa2a126a7e4bbba94806e3e753a9164f58b295f 100755 (executable)
@@ -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);
+        });
+}
 
index cc3ea2e47aabf32844ab2c8733cef37928f6b6e4..ea449c6b17c9830a232bf3b6c4cc752a1c9ffed0 100644 (file)
@@ -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."
     }
 }
 
index 17cc99bf79ad0652bbf2fd02d26e1700463a6a5b..8155181cad086f1a41d6d2f8114a62d3fcefab0a 100644 (file)
@@ -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
index ef21b9824493a9171a5d6d402b2978f1452a6151..11e007d11caaabc924ac63a52434ee1bef3f24dd 100644 (file)
--- 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
+
index 78b4b570445e08516e492f1f35d15069aa5676c1..4f6b1ab50da4249d9f57f5895567b1808f6aa2e4 100644 (file)
@@ -1270,6 +1270,7 @@ struct CompileOptions {
     b32 no_file_contents        : 1;
     
     b32 use_post_mvp_features : 1;
+    b32 use_multi_threading   : 1;
 
     Runtime runtime;
 
index e651924c2a4cda742516ec101b15597ac3753952..22863784801c31d02af5810679a26b3397ab552b 100644 (file)
@@ -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 {
index f70ea09b1e729ae5da757cf5ff146524f6cb7a07..14c257ee0583808db537d044e4190c0692f2ffe4 100644 (file)
@@ -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);
 }
index a2f6b10ec68ae0bbb379bdd5479efceb1c5721f9..3f7542855486af5820ff45f31a864be157da4bbd 100644 (file)
@@ -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) {
index 6f1557d2c906eabddeb55cdc86ebe7f4a8447a9e..48b3f4e40bc8a5de39844ec83fab9d8e2865d673 100644 (file)
@@ -37,20 +37,21 @@ static const char* docstring = "Onyx compiler version " VERSION "\n"
     "\n"
     "Flags:\n"
     "\t<input files>           List of initial files\n"
-    "\t-o <target_file>        Specify the target file (default: out.wasm)\n"
+    "\t-o <target_file>        Specify the target file (default: out.wasm).\n"
     "\t--runtime, -r <runtime> 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 <doc_file>\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]);
             }
index 52938dd4d086acbdda58ea2cca36c89f5cbc77a1..51498d5e63681545a5df7b58f94e1cc0769a8dd2 100644 (file)
@@ -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;
 }
index 8a00b5da3cae26301a01da25877045a7892c3491..92223cd42fc58ee9235a3c4b1a2a352a922bea74 100644 (file)
@@ -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);
         }
     }