#!/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);
+ });
+}
#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."
}
}
__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
- 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
+
b32 no_file_contents : 1;
b32 use_post_mvp_features : 1;
+ b32 use_multi_threading : 1;
Runtime runtime;
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 {
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;
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);
}
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) {
"\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";
.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,
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]);
}
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);
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);
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;
}
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);
// 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);
}
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);
// 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);
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);
}
}