From: Brendan Hansen Date: Fri, 10 Dec 2021 19:09:24 +0000 (-0600) Subject: moved onyx_runtime out of the compiler and into a module X-Git-Url: https://git.brendanfh.com/?a=commitdiff_plain;h=a4e1c57b93a8d51ecdf7ac066ea0057a4d4fd2e9;p=onyx.git moved onyx_runtime out of the compiler and into a module --- diff --git a/build.bat b/build.bat index cf2dbe6e..143fa8ec 100644 --- a/build.bat +++ b/build.bat @@ -32,3 +32,5 @@ del *.pdb > NUL 2> NUL del *.ilk > NUL 2> NUL del *.obj > NUL 2> NUL del misc\icon_resource.res + +call modules\onyx_runtime\build diff --git a/build.sh b/build.sh index 022579c2..73da61e1 100755 --- a/build.sh +++ b/build.sh @@ -78,5 +78,8 @@ compile echo "Installing onyxrun executable" sudo cp ./bin/onyxrun "$BIN_DIR/onyxrun" +./modules/onyx_runtime/build.sh +sudo mv "./onyx_runtime.so" "$CORE_DIR/lib/onyx_runtime.so" + # Otherwise the prompt ends on the same line printf "\n" diff --git a/core/runtime/onyx_run.onyx b/core/runtime/onyx_run.onyx index 166f2ffd..d034f230 100644 --- a/core/runtime/onyx_run.onyx +++ b/core/runtime/onyx_run.onyx @@ -10,8 +10,8 @@ use package wasi #load "core/runtime/wasi" #if Multi_Threading_Enabled { - __spawn_thread :: (id: i32, tls_base: rawptr, func: (data: rawptr) -> void, data: rawptr) -> bool #foreign "env" "spawn_thread" --- - __kill_thread :: (id: i32) -> i32 #foreign "env" "kill_thread" --- + __spawn_thread :: (id: i32, tls_base: rawptr, func: (data: rawptr) -> void, data: rawptr) -> bool #foreign "onyx_runtime" "__spawn_thread" --- + __kill_thread :: (id: i32) -> i32 #foreign "onyx_runtime" "__kill_thread" --- #export "_thread_start" _thread_start #export "_thread_exit" _thread_exit @@ -26,7 +26,9 @@ use package wasi InternalErr :: 0x03; } -#foreign "env" { +#library "onyx_runtime" + +#foreign "onyx_runtime" { __process_spawn :: (path: str, args: [] str, non_blocking_io: bool) -> os.Process.Handle --- __process_read :: (handle: os.Process.Handle, buffer: [] u8) -> i32 --- __process_write :: (handle: os.Process.Handle, buffer: [] u8) -> i32 --- diff --git a/include/onyx_library.h b/include/onyx_library.h index c5bc9c22..8c68ed76 100644 --- a/include/onyx_library.h +++ b/include/onyx_library.h @@ -15,6 +15,8 @@ typedef struct OnyxRuntime { wasm_instance_t* wasm_instance; wasm_module_t* wasm_module; wasm_memory_t* wasm_memory; + wasm_store_t* wasm_store; + wasm_extern_vec_t wasm_imports; // HACK HACK HACK // There should need to be this much stuff in here, but because Wasmer doesn't ship a "wasmerdll.lib" @@ -25,6 +27,7 @@ typedef struct OnyxRuntime { wasm_extern_t* (*wasm_extern_lookup_by_name)(wasm_module_t* module, wasm_instance_t* instance, const char* name); wasm_func_t* (*wasm_extern_as_func)(wasm_extern_t* ext); wasm_trap_t* (*wasm_func_call)(const wasm_func_t* wasm_func, const wasm_val_vec_t* args, wasm_val_vec_t* results); + wasm_instance_t* (*wasm_instance_new)(wasm_store_t* store, const wasm_module_t* module, const wasm_extern_vec_t* imports, wasm_trap_t** traps); } OnyxRuntime; OnyxRuntime* runtime; @@ -76,6 +79,7 @@ typedef struct WasmFuncDefinition { struct WasmFuncDefinition *ONYX_MODULE_NAME_GEN(ONYX_LIBRARY_NAME)[] = // Shorter names +#ifndef ONYX_NO_SHORT_NAMES #undef BOOL #undef INT #undef LONG @@ -87,5 +91,6 @@ typedef struct WasmFuncDefinition { #define FLOAT WASM_F32 #define DOUBLE WASM_F64 #define PTR WASM_I32 +#endif #define ONYX_PTR(p) (p != 0 ? (runtime->wasm_memory_data(runtime->wasm_memory) + p) : NULL) \ No newline at end of file diff --git a/modules/onyx_runtime/build.bat b/modules/onyx_runtime/build.bat new file mode 100644 index 00000000..e9e919d9 --- /dev/null +++ b/modules/onyx_runtime/build.bat @@ -0,0 +1,9 @@ +@echo off + +call "tools\dev.bat" + +cl /MT /std:c17 /TC /I include /I lib/common/include /D_USRDLL /D_WINDLL modules\onyx_runtime\onyx_runtime.c /link /DLL /OUT:onyx_runtime.dll + +del onyx_runtime.obj +del onyx_runtime.lib +del onyx_runtime.exp \ No newline at end of file diff --git a/modules/onyx_runtime/build.sh b/modules/onyx_runtime/build.sh new file mode 100644 index 00000000..f800c6f5 --- /dev/null +++ b/modules/onyx_runtime/build.sh @@ -0,0 +1,3 @@ +#!/bin/sh + +gcc -shared -fpic -I include -I lib/common/include modules/onyx_runtime/onyx_runtime.c -o onyx_runtime.so -lpthread \ No newline at end of file diff --git a/modules/onyx_runtime/onyx_runtime.c b/modules/onyx_runtime/onyx_runtime.c new file mode 100644 index 00000000..7128cef4 --- /dev/null +++ b/modules/onyx_runtime/onyx_runtime.c @@ -0,0 +1,472 @@ + +#define BH_DEFINE +#include "bh.h" + +#define ONYX_LIBRARY_NAME onyx_runtime +#define ONYX_NO_SHORT_NAMES +#include "onyx_library.h" + +#ifdef _BH_LINUX + #include + #include + #include + #include +#endif + +#include "types.h" // For POINTER_SIZE + +typedef struct OnyxThread { + i32 id; + i32 tls_base; + i32 funcidx; + i32 dataptr; + wasm_instance_t* instance; + + #ifdef _BH_LINUX + pthread_t thread; + #endif + + #ifdef _BH_WINDOWS + HANDLE thread_handle; + i32 thread_id; + #endif +} OnyxThread; + +static bh_arr(OnyxThread) threads = NULL; + +#ifdef _BH_LINUX +static void *onyx_run_thread(void *data) { +#endif +#ifdef _BH_WINDOWS +static i32 onyx_run_thread(void *data) { +#endif + OnyxThread *thread = (OnyxThread *) data; + + wasm_trap_t* traps = NULL; + thread->instance = runtime->wasm_instance_new(runtime->wasm_store, runtime->wasm_module, &runtime->wasm_imports, &traps); + + wasm_extern_t* start_extern = runtime->wasm_extern_lookup_by_name(runtime->wasm_module, thread->instance, "_thread_start"); + wasm_func_t* start_func = runtime->wasm_extern_as_func(start_extern); + + wasm_extern_t* exit_extern = runtime->wasm_extern_lookup_by_name(runtime->wasm_module, thread->instance, "_thread_exit"); + wasm_func_t* exit_func = runtime->wasm_extern_as_func(exit_extern); + + wasm_trap_t* trap=NULL; + + // NOTE: This is cached in a local variable because there is a tiny chance that if a lot of threads are created + // then the backing array for the thread handles will move and thread* we have well not be valid. I'm betting on this + // not happening before now in the function; however, it *could* happen before we call thread_exit. This would be bad + // because of the normal reasons why accessing memory that you don't own any more is bad. + i32 thread_id = thread->id; + + { // Call the _thread_start procedure + wasm_val_t args[] = { WASM_I32_VAL(thread_id), WASM_I32_VAL(thread->tls_base), 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); + + trap = runtime->wasm_func_call(start_func, &args_array, &results); + if (trap != NULL) { + /* + When proper linking is available for Wasmer, re-enable this code. At the moment + I don't see too much value in passing all of these functions on the Runtime object + especially since it is a hack to pass functions there anyway. + + wasm_message_t msg; + wasm_trap_message(trap, &msg); + bh_printf("TRAP: %b\n", msg.data, msg.size); + + wasm_frame_t *origin = wasm_trap_origin(trap); + bh_printf("HERE: func[%d] at %p.\n", wasm_frame_func_index(origin), wasm_frame_module_offset(origin)); + */ + bh_printf("ERROR: WebAssembly trap in thread: %d\n", thread_id); + } + } + + { // Call the _thread_exit procedure + wasm_val_t args[] = { WASM_I32_VAL(thread_id) }; + wasm_val_vec_t results = { 0, 0 }; + wasm_val_vec_t args_array = WASM_ARRAY_VEC(args); + + trap = runtime->wasm_func_call(exit_func, &args_array, &results); + } + + return 0; +} + +ONYX_DEF(__spawn_thread, (WASM_I32, WASM_I32, WASM_I32, WASM_I32), (WASM_I32)) { + if (threads == NULL) bh_arr_new(bh_heap_allocator(), threads, 128); + bh_arr_insert_end(threads, 1); + OnyxThread *thread = &bh_arr_last(threads); + + thread->id = params->data[0].of.i32; + thread->tls_base = params->data[1].of.i32; + thread->funcidx = params->data[2].of.i32; + thread->dataptr = params->data[3].of.i32; + + #ifdef _BH_LINUX + pthread_create(&thread->thread, NULL, onyx_run_thread, thread); + #endif + + #ifdef _BH_WINDOWS + // thread->thread_handle = CreateThread(NULL, 0, onyx_run_thread, thread, 0, &thread->thread_id); + thread->thread_handle = (HANDLE) _beginthreadex(NULL, 0, onyx_run_thread, thread, 0, &thread->thread_id); + #endif + + results->data[0] = WASM_I32_VAL(1); + return NULL; +} + +ONYX_DEF(__kill_thread, (WASM_I32), (WASM_I32)) { + i32 thread_id = params->data[0].of.i32; + + i32 i = 0; + bh_arr_each(OnyxThread, thread, threads) { + if (thread->id == thread_id) { + #ifdef _BH_LINUX + pthread_kill(thread->thread, SIGKILL); + #endif + + #ifdef _BH_WINDOWS + TerminateThread(thread->thread_handle, 0); + CloseHandle(thread->thread_handle); + #endif + + bh_arr_deleten(threads, i, 1); + results->data[0] = WASM_I32_VAL(1); + return NULL; + } + + i++; + } + + results->data[0] = WASM_I32_VAL(0); + return NULL; +} + +#define ONYX_PROCESS_MAGIC_NUMBER 0xdeadfadebabecafe +typedef struct OnyxProcess { + u64 magic_number; + +#ifdef _BH_LINUX + // Pipes + i32 proc_to_host[2]; + i32 host_to_proc[2]; + + pid_t pid; +#endif + +#ifdef _BH_WINDOWS + HANDLE proc_to_host_read; + HANDLE proc_to_host_write; + HANDLE host_to_proc_read; + HANDLE host_to_proc_write; + + PROCESS_INFORMATION proc_info; +#endif +} OnyxProcess; + +ONYX_DEF(__process_spawn, (WASM_I32, WASM_I32, WASM_I32, WASM_I32, WASM_I32), (WASM_I64)) { + char* process_str = ONYX_PTR(params->data[0].of.i32); + i32 process_len = params->data[1].of.i32; + i32 args_ptr = params->data[2].of.i32; + i32 args_len = params->data[3].of.i32; + b32 blocking_io = !params->data[4].of.i32; + + char process_path[1024]; + process_len = bh_min(1023, process_len); + memcpy(process_path, process_str, process_len); + process_path[process_len] = '\0'; + + OnyxProcess *process = malloc(sizeof(OnyxProcess)); + memset(process, 0, sizeof(*process)); + process->magic_number = ONYX_PROCESS_MAGIC_NUMBER; + + #ifdef _BH_LINUX + char **process_args = alloca(sizeof(char *) * (args_len + 2)); + byte_t* array_loc = ONYX_PTR(args_ptr); + fori (i, 0, args_len) { + char *arg_str = ONYX_PTR(*(i32 *) (array_loc + i * 2 * POINTER_SIZE)); + i32 arg_len = *(i32 *) (array_loc + i * 2 * POINTER_SIZE + POINTER_SIZE); + + char *arg = alloca(sizeof(char) * (arg_len + 1)); + memcpy(arg, arg_str, arg_len); + arg[arg_len] = '\0'; + process_args[i + 1] = arg; + } + process_args[0] = process_path; + process_args[args_len + 1] = NULL; + + if (pipe(process->proc_to_host) || pipe(process->host_to_proc)) { + wasm_val_init_ptr(&results->data[0], NULL); // Failed to run + return NULL; + } + + pid_t pid; + switch (pid = fork()) { + case -1: // Bad fork + wasm_val_init_ptr(&results->data[0], NULL); // Failed to run + + close(process->proc_to_host[0]); + close(process->proc_to_host[1]); + close(process->host_to_proc[0]); + close(process->host_to_proc[1]); + break; + + case 0: // Child process + close(process->proc_to_host[0]); + close(process->host_to_proc[1]); + dup2(process->host_to_proc[0], 0); // Map the output to the pipe + dup2(process->proc_to_host[1], 1); // Map the output to the pipe + dup2(process->proc_to_host[1], 2); // Stderr to stdout + + if (!blocking_io) { + fcntl(0, F_SETFL, O_NONBLOCK); + fcntl(1, F_SETFL, O_NONBLOCK); + } + + execv(process_path, process_args); + exit(-1); + break; + + default: { + process->pid = pid; + close(process->host_to_proc[0]); + close(process->proc_to_host[1]); + + wasm_val_init_ptr(&results->data[0], process); + break; + } + } + + #endif + + #ifdef _BH_WINDOWS + // CLEANUP CLEANUP CLEANUP: This is so freaking bad... + char cmdLine[2048]; + memset(cmdLine, 0, 2048); + strncat(cmdLine, process_path, 2047); + + byte_t* array_loc = ONYX_PTR(args_ptr); + fori (i, 0, args_len) { + char *arg_str = ONYX_PTR(*(i32 *) (array_loc + i * 2 * POINTER_SIZE)); + i32 arg_len = *(i32 *) (array_loc + i * 2 * POINTER_SIZE + 4); + + strncat(cmdLine, " ", 2047); + strncat(cmdLine, arg_str, arg_len); + } + + STARTUPINFOA startup; + memset(&startup, 0, sizeof startup); + startup.cb = sizeof(startup); + + SECURITY_ATTRIBUTES saAttr; + saAttr.nLength = sizeof(SECURITY_ATTRIBUTES); + saAttr.lpSecurityDescriptor = NULL; + saAttr.bInheritHandle = 1; + + i32 success = 1; + success = success && CreatePipe(&process->host_to_proc_read, &process->host_to_proc_write, &saAttr, 4096); + success = success && CreatePipe(&process->proc_to_host_read, &process->proc_to_host_write, &saAttr, 4096); + if (!success) { + // printf("FAILED TO CREATE PIPES: %d\n", GetLastError()); + wasm_val_init_ptr(&results->data[0], NULL); // Failed to run @LEAK + return NULL; + } + + success = SetHandleInformation(process->proc_to_host_read, 1 /* HANDLE_FLAG_INHERIT */, 0); + success = success && SetHandleInformation(process->host_to_proc_write, 1 /* HANDLE_FLAG_INHERIT */, 0); + if (!success) { + // printf("FAILED TO CONFIGURE PIPES: %d\n", GetLastError()); + wasm_val_init_ptr(&results->data[0], NULL); // Failed to run @LEAK + return NULL; + } + + startup.hStdInput = process->host_to_proc_read; + startup.hStdOutput = process->proc_to_host_write; + startup.hStdError = process->proc_to_host_write; + + startup.dwFlags |= STARTF_USESTDHANDLES; + + memset(&process->proc_info, 0, sizeof process->proc_info); + + success = CreateProcessA(process_path, cmdLine, &saAttr, &saAttr, 1, 0, NULL, NULL, &startup, &process->proc_info); + if (!success) { + printf("FAILED TO CREATE PROCESS: %d\n", GetLastError()); + wasm_val_init_ptr(&results->data[0], NULL); // Failed to run @LEAK + return NULL; + } + + CloseHandle(process->proc_to_host_write); + CloseHandle(process->host_to_proc_read); + wasm_val_init_ptr(&results->data[0], process); + #endif + + return NULL; +} + +ONYX_DEF(__process_read, (WASM_I64, WASM_I32, WASM_I32), (WASM_I32)) { + OnyxProcess *process = (OnyxProcess *) params->data[0].of.i64; + if (process == NULL || process->magic_number != ONYX_PROCESS_MAGIC_NUMBER) { + results->data[0] = WASM_I32_VAL(0); + return NULL; + } + + i32 output_ptr = params->data[1].of.i32; + i32 output_len = params->data[2].of.i32; + u8 *buffer = ONYX_PTR(output_ptr); + + i32 bytes_read; + #ifdef _BH_LINUX + bytes_read = read(process->proc_to_host[0], buffer, output_len); + bytes_read = bh_max(bytes_read, 0); // Silently consume errors + #endif + + #ifdef _BH_WINDOWS + i32 success = ReadFile(process->proc_to_host_read, buffer, output_len, &bytes_read, NULL); + if (!success) bytes_read = 0; + #endif + + results->data[0] = WASM_I32_VAL(bytes_read); + return NULL; +} + +ONYX_DEF(__process_write, (WASM_I64, WASM_I32, WASM_I32), (WASM_I32)) { + OnyxProcess *process = (OnyxProcess *) params->data[0].of.i64; + if (process == NULL || process->magic_number != ONYX_PROCESS_MAGIC_NUMBER) { + results->data[0] = WASM_I32_VAL(0); + return NULL; + } + + i32 input_ptr = params->data[1].of.i32; + i32 input_len = params->data[2].of.i32; + u8 *buffer = ONYX_PTR(input_ptr); + + i32 bytes_written; + #ifdef _BH_LINUX + bytes_written = write(process->host_to_proc[1], buffer, input_len); + bytes_written = bh_max(bytes_written, 0); // Silently consume errors + #endif + + #ifdef _BH_WINDOWS + i32 success = WriteFile(process->host_to_proc_write, buffer, input_len, &bytes_written, NULL); + if (!success) bytes_written = 0; + #endif + + results->data[0] = WASM_I32_VAL(bytes_written); + return NULL; +} + +ONYX_DEF(__process_kill, (WASM_I64), (WASM_I32)) { + OnyxProcess *process = (OnyxProcess *) params->data[0].of.i64; + if (process == NULL || process->magic_number != ONYX_PROCESS_MAGIC_NUMBER) { + results->data[0] = WASM_I32_VAL(0); + return NULL; + } + + #ifdef _BH_LINUX + i32 failed = kill(process->pid, SIGKILL); + results->data[0] = WASM_I32_VAL(!failed); + #endif + + #ifdef _BH_WINDOWS + i32 success = TerminateProcess(process->proc_info.hProcess, 1); + results->data[0] = WASM_I32_VAL(success ? 1 : 0); + #endif + + return NULL; +} + +ONYX_DEF(__process_wait, (WASM_I64), (WASM_I32)) { + OnyxProcess *process = (OnyxProcess *) params->data[0].of.i64; + if (process == NULL || process->magic_number != ONYX_PROCESS_MAGIC_NUMBER) { + results->data[0] = WASM_I32_VAL(1); + return NULL; + } + + #ifdef _BH_LINUX + i32 status; + waitpid(process->pid, &status, 0); + + i32 exit_code = WEXITSTATUS(status); + results->data[0] = WASM_I32_VAL(exit_code != 0 ? 2 : 0); + #endif + + #ifdef _BH_WINDOWS + DWORD exitCode; + while (1) { + if (!WaitForSingleObject(process->proc_info.hProcess, INFINITE)) { + // HACK HACK HACK + DWORD error = GetLastError(); + if (error != 109 && error != 6) { + // printf("ERROR IN WAIT FOR SINGLE: %d\n", error); + results->data[0] = WASM_I32_VAL(1); + return NULL; + } + } + + if (!GetExitCodeProcess(process->proc_info.hProcess, &exitCode)) { + // HACK HACK HACK + // Apparently, I'm doing something wrong (maybe?) where the process handle becomes + // invalid and causes error 6 "invalid handle". So I think I can safely assume that + // if that is the case, then the process exited? probably successfuly? hopefully? + // Honestly I don't know and I can't find any documentation describing when a process + // handle goes invalid, other than after you close it explicitly. But in the run_tests + // script, I'm not calling either process_kill or process_destroy, which are the only + // other functions that close the process handle. So I'm left in the dark as to why this + // is happening, but oh well. This works for now. + // - brendanfh 2021/12/03 + if (GetLastError() == 6) { + exitCode = 0; + break; + } + + results->data[0] = WASM_I32_VAL(3); + return NULL; + } + + // 259 is STILL_ACTIVE (aka STATUS_PENDING), which means that the process has not yet exited + if (exitCode != 259) break; + } + + results->data[0] = WASM_I32_VAL(exitCode != 0 ? 2 : 0); + #endif + + return NULL; +} + +ONYX_DEF(__process_destroy, (WASM_I64), ()) { + OnyxProcess *process = (OnyxProcess *) params->data[0].of.i64; + if (process == NULL || process->magic_number != ONYX_PROCESS_MAGIC_NUMBER) { + return NULL; + } + + #ifdef _BH_LINUX + close(process->proc_to_host[0]); + close(process->host_to_proc[1]); + #endif + + #ifdef _BH_WINDOWS + if (!CloseHandle(process->proc_info.hThread) + || !CloseHandle(process->proc_info.hProcess)) { + // printf("ERROR CLOSING HANDLES: %d\n", GetLastError()); + } + #endif + + free(process); + + return NULL; +} + +ONYX_LIBRARY { + ONYX_FUNC(__spawn_thread) + ONYX_FUNC(__kill_thread) + + ONYX_FUNC(__process_spawn) + ONYX_FUNC(__process_read) + ONYX_FUNC(__process_write) + ONYX_FUNC(__process_kill) + ONYX_FUNC(__process_wait) + ONYX_FUNC(__process_destroy) + + NULL +}; \ No newline at end of file diff --git a/src/wasm_runtime.c b/src/wasm_runtime.c index e11332c8..ed54afe4 100644 --- a/src/wasm_runtime.c +++ b/src/wasm_runtime.c @@ -63,444 +63,8 @@ wasm_extern_t* wasm_extern_lookup_by_name(wasm_module_t* module, wasm_instance_t return exports.data[idx]; } -#define WASM_INTEROP(name) static wasm_trap_t * name (const wasm_val_vec_t *params, wasm_val_vec_t *results) -typedef struct OnyxThread { - i32 id; - i32 tls_base; - i32 funcidx; - i32 dataptr; - wasm_instance_t* instance; - - #ifdef _BH_LINUX - pthread_t thread; - #endif - - #ifdef _BH_WINDOWS - HANDLE thread_handle; - i32 thread_id; - #endif -} OnyxThread; - -static bh_arr(OnyxThread) threads = NULL; - -#ifdef _BH_LINUX -static void *onyx_run_thread(void *data) { -#endif -#ifdef _BH_WINDOWS -static i32 onyx_run_thread(void *data) { -#endif - 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); - - wasm_trap_t* trap=NULL; - - // NOTE: This is cached in a local variable because there is a tiny chance that if a lot of threads are created - // then the backing array for the thread handles will move and thread* we have well not be valid. I'm betting on this - // not happening before now in the function; however, it *could* happen before we call thread_exit. This would be bad - // because of the normal reasons why accessing memory that you don't own any more is bad. - i32 thread_id = thread->id; - - { // Call the _thread_start procedure - wasm_val_t args[] = { WASM_I32_VAL(thread_id), WASM_I32_VAL(thread->tls_base), 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); - - trap = wasm_func_call(start_func, &args_array, &results); - if (trap != NULL) { - wasm_message_t msg; - wasm_trap_message(trap, &msg); - bh_printf("TRAP: %b\n", msg.data, msg.size); - - wasm_frame_t *origin = wasm_trap_origin(trap); - bh_printf("HERE: func[%d] at %p.\n", wasm_frame_func_index(origin), wasm_frame_module_offset(origin)); - } - } - - { // Call the _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); - - trap = wasm_func_call(exit_func, &args_array, &results); - } - - return 0; -} - -WASM_INTEROP(onyx_spawn_thread_impl) { - if (threads == NULL) bh_arr_new(bh_heap_allocator(), threads, 128); - bh_arr_insert_end(threads, 1); - OnyxThread *thread = &bh_arr_last(threads); - - thread->id = params->data[0].of.i32; - thread->tls_base = params->data[1].of.i32; - thread->funcidx = params->data[2].of.i32; - thread->dataptr = params->data[3].of.i32; - - #ifdef _BH_LINUX - pthread_create(&thread->thread, NULL, onyx_run_thread, thread); - #endif - - #ifdef _BH_WINDOWS - // thread->thread_handle = CreateThread(NULL, 0, onyx_run_thread, thread, 0, &thread->thread_id); - thread->thread_handle = (HANDLE) _beginthreadex(NULL, 0, onyx_run_thread, thread, 0, &thread->thread_id); - #endif - - results->data[0] = WASM_I32_VAL(1); - return NULL; -} - -WASM_INTEROP(onyx_kill_thread_impl) { - i32 thread_id = params->data[0].of.i32; - - i32 i = 0; - bh_arr_each(OnyxThread, thread, threads) { - if (thread->id == thread_id) { - #ifdef _BH_LINUX - pthread_kill(thread->thread, SIGKILL); - #endif - - #ifdef _BH_WINDOWS - TerminateThread(thread->thread_handle, 0); - CloseHandle(thread->thread_handle); - #endif - - bh_arr_deleten(threads, i, 1); - results->data[0] = WASM_I32_VAL(1); - return NULL; - } - - i++; - } - - results->data[0] = WASM_I32_VAL(0); - return NULL; -} - -#define ONYX_PROCESS_MAGIC_NUMBER 0xdeadfadebabecafe -typedef struct OnyxProcess { - u64 magic_number; - -#ifdef _BH_LINUX - // Pipes - i32 proc_to_host[2]; - i32 host_to_proc[2]; - - pid_t pid; -#endif - -#ifdef _BH_WINDOWS - HANDLE proc_to_host_read; - HANDLE proc_to_host_write; - HANDLE host_to_proc_read; - HANDLE host_to_proc_write; - - PROCESS_INFORMATION proc_info; -#endif -} OnyxProcess; - -WASM_INTEROP(onyx_process_spawn_impl) { - char* process_str = ONYX_PTR(params->data[0].of.i32); - i32 process_len = params->data[1].of.i32; - i32 args_ptr = params->data[2].of.i32; - i32 args_len = params->data[3].of.i32; - b32 blocking_io = !params->data[4].of.i32; - - char process_path[1024]; - process_len = bh_min(1023, process_len); - memcpy(process_path, process_str, process_len); - process_path[process_len] = '\0'; - - OnyxProcess *process = malloc(sizeof(OnyxProcess)); - memset(process, 0, sizeof(*process)); - process->magic_number = ONYX_PROCESS_MAGIC_NUMBER; - - #ifdef _BH_LINUX - char **process_args = alloca(sizeof(char *) * (args_len + 2)); - byte_t* array_loc = ONYX_PTR(args_ptr); - fori (i, 0, args_len) { - char *arg_str = ONYX_PTR(*(i32 *) (array_loc + i * 2 * POINTER_SIZE)); - i32 arg_len = *(i32 *) (array_loc + i * 2 * POINTER_SIZE + POINTER_SIZE); - - char *arg = alloca(sizeof(char) * (arg_len + 1)); - memcpy(arg, arg_str, arg_len); - arg[arg_len] = '\0'; - process_args[i + 1] = arg; - } - process_args[0] = process_path; - process_args[args_len + 1] = NULL; - - if (pipe(process->proc_to_host) || pipe(process->host_to_proc)) { - wasm_val_init_ptr(&results->data[0], NULL); // Failed to run - return NULL; - } - - pid_t pid; - switch (pid = fork()) { - case -1: // Bad fork - wasm_val_init_ptr(&results->data[0], NULL); // Failed to run - - close(process->proc_to_host[0]); - close(process->proc_to_host[1]); - close(process->host_to_proc[0]); - close(process->host_to_proc[1]); - break; - - case 0: // Child process - close(process->proc_to_host[0]); - close(process->host_to_proc[1]); - dup2(process->host_to_proc[0], 0); // Map the output to the pipe - dup2(process->proc_to_host[1], 1); // Map the output to the pipe - dup2(process->proc_to_host[1], 2); // Stderr to stdout - - if (!blocking_io) { - fcntl(0, F_SETFL, O_NONBLOCK); - fcntl(1, F_SETFL, O_NONBLOCK); - } - - execv(process_path, process_args); - exit(-1); - break; - - default: { - process->pid = pid; - close(process->host_to_proc[0]); - close(process->proc_to_host[1]); - - wasm_val_init_ptr(&results->data[0], process); - break; - } - } - - #endif - - #ifdef _BH_WINDOWS - // CLEANUP CLEANUP CLEANUP: This is so freaking bad... - char cmdLine[2048]; - memset(cmdLine, 0, 2048); - strncat(cmdLine, process_path, 2047); - - byte_t* array_loc = ONYX_PTR(args_ptr); - fori (i, 0, args_len) { - char *arg_str = ONYX_PTR(*(i32 *) (array_loc + i * 2 * POINTER_SIZE)); - i32 arg_len = *(i32 *) (array_loc + i * 2 * POINTER_SIZE + 4); - - strncat(cmdLine, " ", 2047); - strncat(cmdLine, arg_str, arg_len); - } - - STARTUPINFOA startup; - memset(&startup, 0, sizeof startup); - startup.cb = sizeof(startup); - - SECURITY_ATTRIBUTES saAttr; - saAttr.nLength = sizeof(SECURITY_ATTRIBUTES); - saAttr.lpSecurityDescriptor = NULL; - saAttr.bInheritHandle = 1; - - i32 success = 1; - success = success && CreatePipe(&process->host_to_proc_read, &process->host_to_proc_write, &saAttr, 4096); - success = success && CreatePipe(&process->proc_to_host_read, &process->proc_to_host_write, &saAttr, 4096); - if (!success) { - // printf("FAILED TO CREATE PIPES: %d\n", GetLastError()); - wasm_val_init_ptr(&results->data[0], NULL); // Failed to run @LEAK - return NULL; - } - - success = SetHandleInformation(process->proc_to_host_read, 1 /* HANDLE_FLAG_INHERIT */, 0); - success = success && SetHandleInformation(process->host_to_proc_write, 1 /* HANDLE_FLAG_INHERIT */, 0); - if (!success) { - // printf("FAILED TO CONFIGURE PIPES: %d\n", GetLastError()); - wasm_val_init_ptr(&results->data[0], NULL); // Failed to run @LEAK - return NULL; - } - - startup.hStdInput = process->host_to_proc_read; - startup.hStdOutput = process->proc_to_host_write; - startup.hStdError = process->proc_to_host_write; - - startup.dwFlags |= STARTF_USESTDHANDLES; - - memset(&process->proc_info, 0, sizeof process->proc_info); - - success = CreateProcessA(process_path, cmdLine, &saAttr, &saAttr, 1, 0, NULL, NULL, &startup, &process->proc_info); - if (!success) { - // printf("FAILED TO CREATE PROCESS: %d\n", GetLastError()); - wasm_val_init_ptr(&results->data[0], NULL); // Failed to run @LEAK - return NULL; - } - - CloseHandle(process->proc_to_host_write); - CloseHandle(process->host_to_proc_read); - wasm_val_init_ptr(&results->data[0], process); - #endif - - return NULL; -} - -WASM_INTEROP(onyx_process_read_impl) { - OnyxProcess *process = (OnyxProcess *) params->data[0].of.i64; - if (process == NULL || process->magic_number != ONYX_PROCESS_MAGIC_NUMBER) { - results->data[0] = WASM_I32_VAL(0); - return NULL; - } - - i32 output_ptr = params->data[1].of.i32; - i32 output_len = params->data[2].of.i32; - u8 *buffer = ONYX_PTR(output_ptr); - - i32 bytes_read; - #ifdef _BH_LINUX - bytes_read = read(process->proc_to_host[0], buffer, output_len); - bytes_read = bh_max(bytes_read, 0); // Silently consume errors - #endif - - #ifdef _BH_WINDOWS - i32 success = ReadFile(process->proc_to_host_read, buffer, output_len, &bytes_read, NULL); - if (!success) bytes_read = 0; - #endif - - results->data[0] = WASM_I32_VAL(bytes_read); - return NULL; -} - -WASM_INTEROP(onyx_process_write_impl) { - OnyxProcess *process = (OnyxProcess *) params->data[0].of.i64; - if (process == NULL || process->magic_number != ONYX_PROCESS_MAGIC_NUMBER) { - results->data[0] = WASM_I32_VAL(0); - return NULL; - } - - i32 input_ptr = params->data[1].of.i32; - i32 input_len = params->data[2].of.i32; - u8 *buffer = ONYX_PTR(input_ptr); - - i32 bytes_written; - #ifdef _BH_LINUX - bytes_written = write(process->host_to_proc[1], buffer, input_len); - bytes_written = bh_max(bytes_written, 0); // Silently consume errors - #endif - - #ifdef _BH_WINDOWS - i32 success = WriteFile(process->host_to_proc_write, buffer, input_len, &bytes_written, NULL); - if (!success) bytes_written = 0; - #endif - - results->data[0] = WASM_I32_VAL(bytes_written); - return NULL; -} - -WASM_INTEROP(onyx_process_kill_impl) { - OnyxProcess *process = (OnyxProcess *) params->data[0].of.i64; - if (process == NULL || process->magic_number != ONYX_PROCESS_MAGIC_NUMBER) { - results->data[0] = WASM_I32_VAL(0); - return NULL; - } - - #ifdef _BH_LINUX - i32 failed = kill(process->pid, SIGKILL); - results->data[0] = WASM_I32_VAL(!failed); - #endif - - #ifdef _BH_WINDOWS - i32 success = TerminateProcess(process->proc_info.hProcess, 1); - results->data[0] = WASM_I32_VAL(success ? 1 : 0); - #endif - - return NULL; -} - -WASM_INTEROP(onyx_process_wait_impl) { - OnyxProcess *process = (OnyxProcess *) params->data[0].of.i64; - if (process == NULL || process->magic_number != ONYX_PROCESS_MAGIC_NUMBER) { - results->data[0] = WASM_I32_VAL(1); - return NULL; - } - - #ifdef _BH_LINUX - i32 status; - waitpid(process->pid, &status, 0); - - i32 exit_code = WEXITSTATUS(status); - results->data[0] = WASM_I32_VAL(exit_code != 0 ? 2 : 0); - #endif - - #ifdef _BH_WINDOWS - DWORD exitCode; - while (1) { - if (!WaitForSingleObject(process->proc_info.hProcess, INFINITE)) { - // HACK HACK HACK - DWORD error = GetLastError(); - if (error != 109 && error != 6) { - // printf("ERROR IN WAIT FOR SINGLE: %d\n", error); - results->data[0] = WASM_I32_VAL(1); - return NULL; - } - } - - if (!GetExitCodeProcess(process->proc_info.hProcess, &exitCode)) { - // HACK HACK HACK - // Apparently, I'm doing something wrong (maybe?) where the process handle becomes - // invalid and causes error 6 "invalid handle". So I think I can safely assume that - // if that is the case, then the process exited? probably successfuly? hopefully? - // Honestly I don't know and I can't find any documentation describing when a process - // handle goes invalid, other than after you close it explicitly. But in the run_tests - // script, I'm not calling either process_kill or process_destroy, which are the only - // other functions that close the process handle. So I'm left in the dark as to why this - // is happening, but oh well. This works for now. - // - brendanfh 2021/12/03 - if (GetLastError() == 6) { - exitCode = 0; - break; - } - - results->data[0] = WASM_I32_VAL(3); - return NULL; - } - - // 259 is STILL_ACTIVE (aka STATUS_PENDING), which means that the process has not yet exited - if (exitCode != 259) break; - } - - results->data[0] = WASM_I32_VAL(exitCode != 0 ? 2 : 0); - #endif - - return NULL; -} - -WASM_INTEROP(onyx_process_destroy_impl) { - OnyxProcess *process = (OnyxProcess *) params->data[0].of.i64; - if (process == NULL || process->magic_number != ONYX_PROCESS_MAGIC_NUMBER) { - return NULL; - } - - #ifdef _BH_LINUX - close(process->proc_to_host[0]); - close(process->host_to_proc[1]); - #endif - - #ifdef _BH_WINDOWS - if (!CloseHandle(process->proc_info.hThread) - || !CloseHandle(process->proc_info.hProcess)) { - // printf("ERROR CLOSING HANDLES: %d\n", GetLastError()); - } - #endif - - free(process); - - return NULL; -} - typedef void *(*LibraryLinker)(OnyxRuntime *runtime); static bh_arr(WasmFuncDefinition **) linkable_functions = NULL; static bh_arr(char *) library_paths = NULL; @@ -690,87 +254,6 @@ b32 onyx_run_wasm(bh_buffer wasm_bytes, int argc, char *argv[]) { } } - 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_4_1( - wasm_valtype_new_i32(), 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; - } - - if (wasm_name_equals_string(import_name, "__process_spawn")) { - wasm_valtype_t* ps[5] = { - wasm_valtype_new_i32(), wasm_valtype_new_i32(), - wasm_valtype_new_i32(), wasm_valtype_new_i32(), - wasm_valtype_new_i32() - }; - wasm_valtype_t* rs[1] = { wasm_valtype_new_i64() }; - wasm_valtype_vec_t params, results; - wasm_valtype_vec_new(¶ms, 5, ps); - wasm_valtype_vec_new(&results, 1, rs); - wasm_functype_t* func_type = wasm_functype_new(¶ms, &results); - - wasm_func_t* wasm_func = wasm_func_new(wasm_store, func_type, onyx_process_spawn_impl); - import = wasm_func_as_extern(wasm_func); - goto import_found; - } - - if (wasm_name_equals_string(import_name, "__process_read")) { - wasm_functype_t* func_type = wasm_functype_new_3_1( - wasm_valtype_new_i64(), 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_process_read_impl); - import = wasm_func_as_extern(wasm_func); - goto import_found; - } - - if (wasm_name_equals_string(import_name, "__process_write")) { - wasm_functype_t* func_type = wasm_functype_new_3_1( - wasm_valtype_new_i64(), 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_process_write_impl); - import = wasm_func_as_extern(wasm_func); - goto import_found; - } - - if (wasm_name_equals_string(import_name, "__process_kill")) { - wasm_functype_t* func_type = wasm_functype_new_1_1(wasm_valtype_new_i64(), wasm_valtype_new_i32()); - - wasm_func_t* wasm_func = wasm_func_new(wasm_store, func_type, onyx_process_kill_impl); - import = wasm_func_as_extern(wasm_func); - goto import_found; - } - - if (wasm_name_equals_string(import_name, "__process_wait")) { - wasm_functype_t* func_type = wasm_functype_new_1_1(wasm_valtype_new_i64(), wasm_valtype_new_i32()); - - wasm_func_t* wasm_func = wasm_func_new(wasm_store, func_type, onyx_process_wait_impl); - import = wasm_func_as_extern(wasm_func); - goto import_found; - } - - if (wasm_name_equals_string(import_name, "__process_destroy")) { - wasm_functype_t* func_type = wasm_functype_new_1_0(wasm_valtype_new_i64()); - - wasm_func_t* wasm_func = wasm_func_new(wasm_store, func_type, onyx_process_destroy_impl); - import = wasm_func_as_extern(wasm_func); - goto import_found; - } - } - bh_arr_each(WasmFuncDefinition **, library_funcs, linkable_functions) { WasmFuncDefinition **pcurrent_function = *library_funcs; while (*pcurrent_function != NULL) { @@ -815,12 +298,15 @@ b32 onyx_run_wasm(bh_buffer wasm_bytes, int argc, char *argv[]) { wasm_runtime.wasm_instance = wasm_instance; wasm_runtime.wasm_module = wasm_module; wasm_runtime.wasm_memory = wasm_memory; + wasm_runtime.wasm_store = wasm_store; + wasm_runtime.wasm_imports = wasm_imports; // See comment in onyx_library.h about us being the linker. wasm_runtime.wasm_memory_data = &wasm_memory_data; wasm_runtime.wasm_extern_lookup_by_name = &wasm_extern_lookup_by_name; wasm_runtime.wasm_extern_as_func = &wasm_extern_as_func; wasm_runtime.wasm_func_call = &wasm_func_call; + wasm_runtime.wasm_instance_new = &wasm_instance_new; wasm_extern_t* start_extern = wasm_extern_lookup_by_name(wasm_module, wasm_instance, "_start"); wasm_func_t* start_func = wasm_extern_as_func(start_extern);