From f328989c001dd3916e51f9319e91e3df2cbb4daf Mon Sep 17 00:00:00 2001 From: Brendan Hansen Date: Mon, 22 Nov 2021 09:51:12 -0600 Subject: [PATCH] `onyx run` supports multi threads --- build.sh | 10 +- core/io/file.onyx | 3 +- core/runtime/build_opts.onyx | 7 +- core/runtime/onyx_run.onyx | 32 ++++ core/runtime/wasi.onyx | 3 - core/std.onyx | 14 +- core/wasi/clock.onyx | 6 +- core/wasi/env.onyx | 6 +- include/astnodes.h | 7 +- include/small_windows.h | 1 + lib/linux_x86_64/include/wasm.h | 12 +- src/onyx.c | 7 +- src/wasm_emit.c | 17 +- src/wasm_runtime.c | 264 +++++++++++++++++++++++++++----- 14 files changed, 304 insertions(+), 85 deletions(-) create mode 100644 core/runtime/onyx_run.onyx diff --git a/build.sh b/build.sh index 447e1291..90e7e8a0 100755 --- a/build.sh +++ b/build.sh @@ -43,12 +43,10 @@ else TARGET='./bin/onyx' fi -if true; then - C_FILES="$C_FILES wasm_runtime" - FLAGS="$FLAGS -DENABLE_RUN_WITH_WASMER" - LIBS="-L$CORE_DIR/lib -lwasmer -Wl,-rpath=$CORE_DIR/lib" - INCLUDES="-I$WASMER_INCLUDE_DIR" -fi +C_FILES="$C_FILES wasm_runtime" +FLAGS="$FLAGS -DENABLE_RUN_WITH_WASMER" +LIBS="-L$CORE_DIR/lib -lwasmer -Wl,-rpath=$CORE_DIR/lib -lpthread" +INCLUDES="-I$WASMER_INCLUDE_DIR" mkdir -p "$BUILD_DIR" diff --git a/core/io/file.onyx b/core/io/file.onyx index 2606355a..08b7f052 100644 --- a/core/io/file.onyx +++ b/core/io/file.onyx @@ -1,7 +1,8 @@ package core.io #local runtime :: package runtime -#if runtime.Runtime != runtime.Runtime_Wasi { +#if runtime.Runtime != runtime.Runtime_Wasi + && runtime.Runtime != runtime.Runtime_Onyx { #error "The file system library is currently only available on the WASI runtime, and should only be included if that is the chosen runtime." } diff --git a/core/runtime/build_opts.onyx b/core/runtime/build_opts.onyx index 5b60b137..483257a5 100644 --- a/core/runtime/build_opts.onyx +++ b/core/runtime/build_opts.onyx @@ -16,6 +16,7 @@ package runtime // such as enums. They can only really handle compile time number math and boolean // logic. - brendanfh 2020/02/08 -Runtime_Wasi :: 1 -Runtime_Js :: 2 -Runtime_Custom :: 3 +Runtime_Onyx :: 1 +Runtime_Wasi :: 2 +Runtime_Js :: 3 +Runtime_Custom :: 4 diff --git a/core/runtime/onyx_run.onyx b/core/runtime/onyx_run.onyx new file mode 100644 index 00000000..723de5b7 --- /dev/null +++ b/core/runtime/onyx_run.onyx @@ -0,0 +1,32 @@ +package runtime + +#load "core/wasi/wasi" +#load "core/runtime/common" + +use package core +use package wasi + +// The "onyx_run" runtime extends the WASI runtime. +#load "core/runtime/wasi" + +#if Multi_Threading_Enabled { + __spawn_thread :: (id: i32, func: (data: rawptr) -> void, data: rawptr) -> bool #foreign "env" "spawn_thread" --- + __kill_thread :: (id: i32) -> i32 #foreign "env" "kill_thread" --- + + #export "_thread_start" (id: i32, func: (data: rawptr) -> void, data: rawptr) { + __stack_top = raw_alloc(alloc.heap_allocator, 1 << 20); + __thread_initialize(); + + context.thread_id = id; + + func(data); + + __flush_stdio(); + } + + #export "_thread_exit" (id: i32) { + raw_free(alloc.heap_allocator, __tls_base); + + thread.__exited(id); + } +} diff --git a/core/runtime/wasi.onyx b/core/runtime/wasi.onyx index 9ad50535..16deca74 100644 --- a/core/runtime/wasi.onyx +++ b/core/runtime/wasi.onyx @@ -51,6 +51,3 @@ __exit :: (status: i32) do proc_exit(status); __flush_stdio(); } - -__spawn_thread :: (t: i32, func: (data: rawptr) -> void, data: rawptr) -> bool --- -__kill_thread :: (t: i32) -> i32 --- diff --git a/core/std.onyx b/core/std.onyx index d1daefdf..936bfca2 100644 --- a/core/std.onyx +++ b/core/std.onyx @@ -37,21 +37,17 @@ package core #load "./type_info/helper" #local runtime :: package runtime -#if runtime.Runtime == runtime.Runtime_Wasi { - #load "./runtime/wasi" +#if runtime.Runtime == runtime.Runtime_Wasi || runtime.Runtime == runtime.Runtime_Onyx { #load "./wasi/wasi" #load "./wasi/env" #load "./wasi/clock" #load "./io/file" } -#if runtime.Runtime == runtime.Runtime_Js { - #load "./runtime/js" -} - -#if runtime.Runtime != runtime.Runtime_Custom { - #load "./stdio" -} +#if runtime.Runtime == runtime.Runtime_Onyx { #load "./runtime/onyx_run" } +#if runtime.Runtime == runtime.Runtime_Wasi { #load "./runtime/wasi" } +#if runtime.Runtime == runtime.Runtime_Js { #load "./runtime/js" } +#if runtime.Runtime != runtime.Runtime_Custom { #load "./stdio" } #if runtime.Multi_Threading_Enabled { #load "./intrinsics/atomics" diff --git a/core/wasi/clock.onyx b/core/wasi/clock.onyx index 7f1e2b7b..e3dd7080 100644 --- a/core/wasi/clock.onyx +++ b/core/wasi/clock.onyx @@ -1,7 +1,9 @@ package core.clock -#if (package runtime).Runtime != (package runtime).Runtime_Wasi { - #error "'core.clock' is only available with the 'wasi' runtime."; +#local runtime :: package runtime +#if runtime.Runtime != runtime.Runtime_Wasi + && runtime.Runtime != runtime.Runtime_Onyx { + #error "'core.clock' is only available with the 'wasi' or 'onyx' runtimes."; } use package wasi diff --git a/core/wasi/env.onyx b/core/wasi/env.onyx index dfb408d4..09dfa057 100644 --- a/core/wasi/env.onyx +++ b/core/wasi/env.onyx @@ -1,7 +1,9 @@ package core.env -#if (package runtime).Runtime != (package runtime).Runtime_Wasi { - #error "'core.env' is only available with the 'wasi' runtime."; +#local runtime :: package runtime +#if runtime.Runtime != runtime.Runtime_Wasi + && runtime.Runtime != runtime.Runtime_Onyx { + #error "'core.env' is only available with the 'wasi' and 'onyx' runtimes."; } diff --git a/include/astnodes.h b/include/astnodes.h index d3bded15..8cf6d822 100644 --- a/include/astnodes.h +++ b/include/astnodes.h @@ -1381,9 +1381,10 @@ enum CompileAction { typedef enum Runtime Runtime; enum Runtime { Runtime_Unknown = 0, - Runtime_Wasi = 1, - Runtime_Js = 2, - Runtime_Custom = 3, + Runtime_Onyx = 1, + Runtime_Wasi = 2, + Runtime_Js = 3, + Runtime_Custom = 4, }; diff --git a/include/small_windows.h b/include/small_windows.h index 22d8f7a4..2836ad7a 100644 --- a/include/small_windows.h +++ b/include/small_windows.h @@ -274,6 +274,7 @@ GB_DLL_IMPORT HANDLE WINAPI CreateThread (SECURITY_ATTRIBUTES *semaphore_ DWORD creation_flags, DWORD *thread_id); GB_DLL_IMPORT DWORD WINAPI GetThreadId (HANDLE handle); GB_DLL_IMPORT void WINAPI RaiseException (DWORD, DWORD, DWORD, ULONG_PTR const *); +GB_DLL_IMPORT BOOL WINAPI TerminateThread (HANDLE hThread, DWORD dwExitCode); GB_DLL_IMPORT BOOL WINAPI GetLogicalProcessorInformation(SYSTEM_LOGICAL_PROCESSOR_INFORMATION *buffer, DWORD *return_length); diff --git a/lib/linux_x86_64/include/wasm.h b/lib/linux_x86_64/include/wasm.h index 25e0453a..4a7e2c00 100644 --- a/lib/linux_x86_64/include/wasm.h +++ b/lib/linux_x86_64/include/wasm.h @@ -704,12 +704,12 @@ static inline void* wasm_val_ptr(const wasm_val_t* val) { #endif } -#define WASM_I32_VAL(i) {.kind = WASM_I32, .of = {.i32 = i}} -#define WASM_I64_VAL(i) {.kind = WASM_I64, .of = {.i64 = i}} -#define WASM_F32_VAL(z) {.kind = WASM_F32, .of = {.f32 = z}} -#define WASM_F64_VAL(z) {.kind = WASM_F64, .of = {.f64 = z}} -#define WASM_REF_VAL(r) {.kind = WASM_ANYREF, .of = {.ref = r}} -#define WASM_INIT_VAL {.kind = WASM_ANYREF, .of = {.ref = NULL}} +#define WASM_I32_VAL(i) (wasm_val_t) {.kind = WASM_I32, .of = {.i32 = i}} +#define WASM_I64_VAL(i) (wasm_val_t) {.kind = WASM_I64, .of = {.i64 = i}} +#define WASM_F32_VAL(z) (wasm_val_t) {.kind = WASM_F32, .of = {.f32 = z}} +#define WASM_F64_VAL(z) (wasm_val_t) {.kind = WASM_F64, .of = {.f64 = z}} +#define WASM_REF_VAL(r) (wasm_val_t) {.kind = WASM_ANYREF, .of = {.ref = r}} +#define WASM_INIT_VAL (wasm_val_t) {.kind = WASM_ANYREF, .of = {.ref = NULL}} /////////////////////////////////////////////////////////////////////////////// diff --git a/src/onyx.c b/src/onyx.c index 5af0542b..2ded60d7 100644 --- a/src/onyx.c +++ b/src/onyx.c @@ -71,7 +71,7 @@ static CompileOptions compile_opts_parse(bh_allocator alloc, int argc, char *arg .use_post_mvp_features = 1, .use_multi_threading = 0, - .runtime = Runtime_Wasi, + .runtime = Runtime_Onyx, .files = NULL, .target_file = "out.wasm", @@ -145,11 +145,12 @@ static CompileOptions compile_opts_parse(bh_allocator alloc, int argc, char *arg } else if (!strcmp(argv[i], "-r") || !strcmp(argv[i], "--runtime")) { i += 1; - if (!strcmp(argv[i], "wasi")) options.runtime = Runtime_Wasi; + if (!strcmp(argv[i], "onyx")) options.runtime = Runtime_Onyx; + else if (!strcmp(argv[i], "wasi")) options.runtime = Runtime_Wasi; else if (!strcmp(argv[i], "js")) options.runtime = Runtime_Js; else if (!strcmp(argv[i], "custom")) options.runtime = Runtime_Custom; else { - bh_printf("WARNING: '%s' is not a valid runtime. Defaulting to 'wasi'.\n", argv[i]); + bh_printf("WARNING: '%s' is not a valid runtime. Defaulting to 'onyx'.\n", argv[i]); options.runtime = Runtime_Wasi; } } diff --git a/src/wasm_emit.c b/src/wasm_emit.c index eec3e854..6a133ba2 100644 --- a/src/wasm_emit.c +++ b/src/wasm_emit.c @@ -3712,23 +3712,22 @@ OnyxWasmModule onyx_wasm_module_create(bh_allocator alloc) { .kind = WASM_FOREIGN_MEMORY, .min = 1024, .max = 65536, // NOTE: Why not use all 4 Gigs of memory? - .shared = 1, + .shared = context.options->runtime != Runtime_Onyx, .mod = "onyx", .name = "memory", }; bh_arr_push(module.imports, mem_import); + } - } else { - WasmExport mem_export = { - .kind = WASM_FOREIGN_MEMORY, - .idx = 0, - }; + WasmExport mem_export = { + .kind = WASM_FOREIGN_MEMORY, + .idx = 0, + }; - bh_table_put(WasmExport, module.exports, "memory", mem_export); - module.export_count++; - } + bh_table_put(WasmExport, module.exports, "memory", mem_export); + module.export_count++; WasmExport func_table_export = { .kind = WASM_FOREIGN_TABLE, diff --git a/src/wasm_runtime.c b/src/wasm_runtime.c index 03bbca28..35b9683d 100644 --- a/src/wasm_runtime.c +++ b/src/wasm_runtime.c @@ -3,27 +3,161 @@ #include "wasm.h" #include "wasmer.h" +#ifdef _BH_LINUX + #include + #include +#endif + #ifndef WASMER_VERSION #error "Currently, building the Onyx compiler with built-in execution support requires the Wasmer library to be compiled and linked." #endif +static wasm_config_t* wasm_config; +static wasi_config_t* wasi_config; +static wasi_env_t* wasi_env; +static wasm_engine_t* wasm_engine; +static wasm_store_t* wasm_store; +static wasm_extern_vec_t wasm_imports; +static wasm_module_t* wasm_module; +static wasm_memory_t* wasm_memory; + +b32 wasm_name_equals(const wasm_name_t* name1, const wasm_name_t* name2) { + if (name1->size != name2->size) return 0; + return !strncmp(name1->data, name2->data, name1->size); +} + +b32 wasm_name_equals_string(const wasm_name_t* name1, const char* name2) { + u32 name2_size = strlen(name2); + if (name1->size != name2_size) return 0; + return !strncmp(name1->data, name2, name1->size); +} + +wasm_extern_t* wasm_extern_lookup_by_name(wasm_module_t* module, wasm_instance_t* instance, const char* name) { + i32 name_len = strlen(name); + + i32 idx = -1; + wasm_exporttype_vec_t export_types; + wasm_module_exports(module, &export_types); + fori (i, 0, (i64) export_types.size) { + wasm_exporttype_t* export_type = export_types.data[i]; + const wasm_name_t* export_name = wasm_exporttype_name(export_type); + + if (!strncmp(export_name->data, name, name_len)) { + idx = i; + break; + } + } + + if (idx == -1) return NULL; + + wasm_extern_vec_t exports; + wasm_instance_exports(instance, &exports); + + return exports.data[idx]; +} + + +typedef struct OnyxThread { + i32 id; + i32 funcidx; + i32 dataptr; + wasm_instance_t* instance; + + #ifdef _BH_LINUX + pthread_t thread; + #endif +} OnyxThread; + +static bh_arr(OnyxThread) threads = NULL; + +static void *onyx_run_thread(void *data) { + OnyxThread *thread = (OnyxThread *) data; + + wasm_trap_t* traps = NULL; + thread->instance = wasm_instance_new(wasm_store, wasm_module, &wasm_imports, &traps); + + wasm_extern_t* start_extern = wasm_extern_lookup_by_name(wasm_module, thread->instance, "_thread_start"); + wasm_func_t* start_func = wasm_extern_as_func(start_extern); + + wasm_extern_t* exit_extern = wasm_extern_lookup_by_name(wasm_module, thread->instance, "_thread_exit"); + wasm_func_t* exit_func = wasm_extern_as_func(exit_extern); + + { // Call the _thread_start procedure + wasm_val_t args[] = { WASM_I32_VAL(thread->id), WASM_I32_VAL(thread->funcidx), WASM_I32_VAL(thread->dataptr) }; + wasm_val_vec_t results; + wasm_val_vec_t args_array = WASM_ARRAY_VEC(args); + + wasm_func_call(start_func, &args_array, &results); + } + + { // Cal lthe _thread_exit procedure + wasm_val_t args[] = { WASM_I32_VAL(thread->id) }; + wasm_val_vec_t results; + wasm_val_vec_t args_array = WASM_ARRAY_VEC(args); + + wasm_func_call(exit_func, &args_array, &results); + } + + return NULL; +} + +static wasm_trap_t* onyx_spawn_thread_impl(const wasm_val_vec_t* params, wasm_val_vec_t* results) { + bh_arr_insert_end(threads, 1); + OnyxThread *thread = &bh_arr_last(threads); + + thread->id = params->data[0].of.i32; + thread->funcidx = params->data[1].of.i32; + thread->dataptr = params->data[2].of.i32; + + #ifdef _BH_LINUX + pthread_create(&thread->thread, NULL, onyx_run_thread, thread); + #endif + + #ifdef _BH_WINDOWS + #error "unimplemented" + #endif + + results->data[0] = WASM_I32_VAL(1); + return NULL; +} + +static wasm_trap_t* onyx_kill_thread_impl(const wasm_val_vec_t* params, wasm_val_vec_t* results) { + i32 thread_id = params->data[0].of.i32; + + bh_arr_each(OnyxThread, thread, threads) { + if (thread->id == thread_id) { + #ifdef _BH_LINUX + pthread_kill(thread->thread, SIGKILL); + #endif + + results->data[0] = WASM_I32_VAL(1); + return NULL; + } + } + + results->data[0] = WASM_I32_VAL(0); + return NULL; +} + + void onyx_run_wasm(bh_buffer wasm_bytes) { - wasm_config_t* config = NULL; - wasi_config_t* wasi_config = NULL; - wasi_env_t* wasi_env = NULL; - wasm_engine_t* engine = NULL; - wasm_store_t* store = NULL; - wasm_module_t* module = NULL; wasm_instance_t* instance = NULL; + wasmer_features_t* features = NULL; - config = wasm_config_new(); - if (!config) goto error_handling; + wasm_config = wasm_config_new(); + if (!wasm_config) goto error_handling; // Prefer the LLVM compile because it is faster. This should be configurable from the command line and/or a top-level directive. if (wasmer_is_compiler_available(LLVM)) { - wasm_config_set_compiler(config, LLVM); + wasm_config_set_compiler(wasm_config, LLVM); } + features = wasmer_features_new(); + wasmer_features_simd(features, 1); + wasmer_features_threads(features, 1); + wasmer_features_bulk_memory(features, 1); + wasm_config_set_features(wasm_config, features); + wasi_config = wasi_config_new("onyx"); if (context.options->passthrough_argument_count > 0) { fori (i, 0, context.options->passthrough_argument_count) { @@ -36,45 +170,95 @@ void onyx_run_wasm(bh_buffer wasm_bytes) { wasi_env = wasi_env_new(wasi_config); if (!wasi_env) goto error_handling; - engine = wasm_engine_new_with_config(config); - if (!engine) goto error_handling; + wasm_engine = wasm_engine_new_with_config(wasm_config); + if (!wasm_engine) goto error_handling; - store = wasm_store_new(engine); - if (!store) goto error_handling; + wasm_store = wasm_store_new(wasm_engine); + if (!wasm_store) goto error_handling; wasm_byte_vec_t wasm_data; wasm_data.size = wasm_bytes.length; wasm_data.data = wasm_bytes.data; - module = wasm_module_new(store, &wasm_data); - if (!module) goto error_handling; + wasm_module = wasm_module_new(wasm_store, &wasm_data); + if (!wasm_module) goto error_handling; - wasm_extern_vec_t imports = WASM_EMPTY_VEC; - wasi_get_imports(store, module, wasi_env, &imports); + wasmer_named_extern_vec_t wasi_imports; + wasi_get_unordered_imports(wasm_store, wasm_module, wasi_env, &wasi_imports); - wasm_trap_t* traps = NULL; + wasm_importtype_vec_t module_imports; // @Free + wasm_module_imports(wasm_module, &module_imports); - instance = wasm_instance_new(store, module, &imports, &traps); - if (!instance) goto error_handling; + wasm_imports = (wasm_extern_vec_t) WASM_EMPTY_VEC; + wasm_extern_vec_new_uninitialized(&wasm_imports, module_imports.size); // @Free - // Find the start function - i32 start_function_idx = -1; - wasm_exporttype_vec_t export_types; - wasm_module_exports(module, &export_types); - fori (i, 0, (i64) export_types.size) { - wasm_exporttype_t* export_type = export_types.data[i]; - const wasm_name_t* export_name = wasm_exporttype_name(export_type); - - if (!strncmp(export_name->data, "_start", 6)) { - start_function_idx = i; - break; + fori (i, 0, (i32) module_imports.size) { + const wasm_name_t* module_name = wasm_importtype_module(module_imports.data[i]); + const wasm_name_t* import_name = wasm_importtype_name(module_imports.data[i]); + + wasm_extern_t* import = NULL; + + // First try WASI + fori (j, 0, (i32) wasi_imports.size) { + const wasm_name_t* wasi_module_name = wasmer_named_extern_module(wasi_imports.data[j]); + const wasm_name_t* wasi_import_name = wasmer_named_extern_name(wasi_imports.data[j]); + if (wasm_name_equals(module_name, wasi_module_name) && wasm_name_equals(import_name, wasi_import_name)) { + import = (wasm_extern_t *) wasmer_named_extern_unwrap(wasi_imports.data[j]); + goto import_found; + } + } + + if (wasm_name_equals_string(module_name, "onyx")) { + if (wasm_name_equals_string(import_name, "memory")) { + if (wasm_memory == NULL) { + wasm_limits_t limits = { 1024, 65536 }; + wasm_memorytype_t* memory_type = wasm_memorytype_new(&limits); + wasm_memory = wasm_memory_new(wasm_store, memory_type); + } + + import = wasm_memory_as_extern(wasm_memory); + goto import_found; + } + } + + if (wasm_name_equals_string(module_name, "env")) { + if (wasm_name_equals_string(import_name, "spawn_thread")) { + wasm_functype_t* func_type = wasm_functype_new_3_1( + wasm_valtype_new_i32(), wasm_valtype_new_i32(), wasm_valtype_new_i32(), + wasm_valtype_new_i32()); + + wasm_func_t* wasm_func = wasm_func_new(wasm_store, func_type, onyx_spawn_thread_impl); + import = wasm_func_as_extern(wasm_func); + goto import_found; + } + + if (wasm_name_equals_string(import_name, "kill_thread")) { + wasm_functype_t* func_type = wasm_functype_new_1_1(wasm_valtype_new_i32(), wasm_valtype_new_i32()); + + wasm_func_t* wasm_func = wasm_func_new(wasm_store, func_type, onyx_kill_thread_impl); + import = wasm_func_as_extern(wasm_func); + goto import_found; + } } + + goto bad_import; + + import_found: + wasm_imports.data[i] = import; + continue; + + + bad_import: + bh_printf("Couldn't find import %b.%b.\n", module_name->data, module_name->size, import_name->data, import_name->size); + return; } - wasm_extern_vec_t exports; - wasm_instance_exports(instance, &exports); + wasm_trap_t* traps = NULL; + + instance = wasm_instance_new(wasm_store, wasm_module, &wasm_imports, &traps); + if (!instance) goto error_handling; - wasm_extern_t* start_extern = exports.data[start_function_idx]; + wasm_extern_t* start_extern = wasm_extern_lookup_by_name(wasm_module, instance, "_start"); wasm_func_t* start_func = wasm_extern_as_func(start_extern); wasm_val_vec_t args; @@ -87,11 +271,15 @@ void onyx_run_wasm(bh_buffer wasm_bytes) { error_handling: bh_printf("An error occured trying to run the WASM module...\n"); + i32 len = wasmer_last_error_length(); + char *buf = alloca(len + 1); + wasmer_last_error_message(buf, len); + bh_printf("%b\n", buf, len); cleanup: if (instance) wasm_instance_delete(instance); - if (module) wasm_module_delete(module); - if (store) wasm_store_delete(store); - if (engine) wasm_engine_delete(engine); + if (wasm_module) wasm_module_delete(wasm_module); + if (wasm_store) wasm_store_delete(wasm_store); + if (wasm_engine) wasm_engine_delete(wasm_engine); return; -} \ No newline at end of file +} -- 2.25.1