diff options
Diffstat (limited to 'wasm')
-rw-r--r-- | wasm/README.md | 24 | ||||
-rw-r--r-- | wasm/asyncify.h | 10 | ||||
-rw-r--r-- | wasm/machine.c | 6 | ||||
-rw-r--r-- | wasm/machine.h | 5 | ||||
-rw-r--r-- | wasm/runtime.c | 7 | ||||
-rw-r--r-- | wasm/setjmp.c | 115 | ||||
-rw-r--r-- | wasm/setjmp.h | 36 |
7 files changed, 175 insertions, 28 deletions
diff --git a/wasm/README.md b/wasm/README.md index b18429a381..0f9ca1a3d5 100644 --- a/wasm/README.md +++ b/wasm/README.md @@ -7,8 +7,7 @@ - Ruby (the same version as the building target version) (baseruby) - GNU make - [WASI SDK](https://github.com/WebAssembly/wasi-sdk) 14.0 or later -- [Binaryen](https://github.com/WebAssembly/binaryen) **version 91** - - See also: https://github.com/WebAssembly/binaryen/issues/4401 +- [Binaryen](https://github.com/WebAssembly/binaryen) version 106 or later - Linux or macOS build machine ### Steps @@ -23,7 +22,14 @@ $ export WASI_SDK_PATH=/path/to/wasi-sdk-X.Y ```console $ export PATH=path/to/binaryen:$PATH ``` -5. Configure +5. Download the latest `config.guess` with WASI support, and run `./autogen.sh` to generate configure when you + are building from the source checked out from Git repository +```console +$ ruby tool/downloader.rb -d tool -e gnu config.guess config.sub +$ ./autogen.sh +``` + +6. Configure - You can select which extensions you want to build. - If you got `Out of bounds memory access` while running the produced ruby, you may need to increase the maximum size of stack. ```console @@ -34,7 +40,7 @@ $ ./configure LDFLAGS="-Xlinker -zstack-size=16777216" \ --with-ext=ripper,monitor ``` -6. Make +7. Make ```console $ make install ``` @@ -48,6 +54,16 @@ $ wasmtime ruby-wasm32-wasi/usr/local/bin/ruby --mapdir /::./ruby-wasm32-wasi/ - wasm32-wasi ``` +Note: you cannot run the built ruby without a WebAssembly runtime, because of the difference of the binary file type. + +``` +$ ruby-wasm32-wasi/usr/local/bin/ruby -e 'puts "a"' +bash: ruby-wasm32-wasi/usr/local/bin/ruby: cannot execute binary file: Exec format error + +$ file ruby-wasm32-wasi/usr/local/bin/ruby +ruby-wasm32-wasi/usr/local/bin/ruby: WebAssembly (wasm) binary module version 0x1 (MVP) +``` + ## Current Limitation - No `Thread` support for now. diff --git a/wasm/asyncify.h b/wasm/asyncify.h index 834fc8b570..49eb125593 100644 --- a/wasm/asyncify.h +++ b/wasm/asyncify.h @@ -3,8 +3,18 @@ __attribute__((import_module("asyncify"), import_name("start_unwind"))) void asyncify_start_unwind(void *buf); +#define asyncify_start_unwind(buf) do { \ + extern void *rb_asyncify_unwind_buf; \ + rb_asyncify_unwind_buf = (buf); \ + asyncify_start_unwind((buf)); \ + } while (0) __attribute__((import_module("asyncify"), import_name("stop_unwind"))) void asyncify_stop_unwind(void); +#define asyncify_stop_unwind() do { \ + extern void *rb_asyncify_unwind_buf; \ + rb_asyncify_unwind_buf = NULL; \ + asyncify_stop_unwind(); \ + } while (0) __attribute__((import_module("asyncify"), import_name("start_rewind"))) void asyncify_start_rewind(void *buf); __attribute__((import_module("asyncify"), import_name("stop_rewind"))) diff --git a/wasm/machine.c b/wasm/machine.c index 238041f93e..2ca8462502 100644 --- a/wasm/machine.c +++ b/wasm/machine.c @@ -49,10 +49,10 @@ rb_wasm_record_stack_base(void) return 0; } -void -_rb_wasm_scan_stack(rb_wasm_scan_func scan, void *current) +void * +rb_wasm_stack_get_base(void) { - scan(current, rb_wasm_stack_base); + return rb_wasm_stack_base; } void * diff --git a/wasm/machine.h b/wasm/machine.h index 4cf7228684..1a60e51d11 100644 --- a/wasm/machine.h +++ b/wasm/machine.h @@ -8,9 +8,8 @@ typedef void (*rb_wasm_scan_func)(void*, void*); // Used by conservative GC void rb_wasm_scan_locals(rb_wasm_scan_func scan); -// Scan userland C-stack memory space in WebAssembly. Used by conservative GC -#define rb_wasm_scan_stack(scan) _rb_wasm_scan_stack((scan), rb_wasm_get_stack_pointer()) -void _rb_wasm_scan_stack(rb_wasm_scan_func scan, void *current); +// Get base address of userland C-stack memory space in WebAssembly. Used by conservative GC +void *rb_wasm_stack_get_base(void); // Get the current stack pointer diff --git a/wasm/runtime.c b/wasm/runtime.c index b5b0a1a966..89b06be6ad 100644 --- a/wasm/runtime.c +++ b/wasm/runtime.c @@ -19,6 +19,13 @@ int rb_wasm_rt_start(int (main)(int argc, char **argv), int argc, char **argv) { result = main(argc, argv); } + extern void *rb_asyncify_unwind_buf; + // Exit Asyncify loop if there is no unwound buffer, which + // means that main function has returned normally. + if (rb_asyncify_unwind_buf == NULL) { + break; + } + // NOTE: it's important to call 'asyncify_stop_unwind' here instead in rb_wasm_handle_jmp_unwind // because unless that, Asyncify inserts another unwind check here and it unwinds to the root frame. asyncify_stop_unwind(); diff --git a/wasm/setjmp.c b/wasm/setjmp.c index 3f017e202a..ebbf8949c1 100644 --- a/wasm/setjmp.c +++ b/wasm/setjmp.c @@ -29,10 +29,25 @@ #include "wasm/setjmp.h" #ifdef RB_WASM_ENABLE_DEBUG_LOG -# include <stdio.h> -# define RB_WASM_DEBUG_LOG(...) fprintf(stderr, __VA_ARGS__) +# include <wasi/api.h> +# include <unistd.h> +// NOTE: We can't use printf() and most of library function that are +// Asyncified due to the use of them in the application itself. +// Use of printf() causes "unreachable" error because Asyncified +// function misunderstands Asyncify's internal state during +// start_unwind()...stop_unwind() and start_rewind()...stop_rewind(). +# define RB_WASM_DEBUG_LOG_INTERNAL(msg) do { \ + const uint8_t *msg_start = (uint8_t *)msg; \ + const uint8_t *msg_end = msg_start; \ + for (; *msg_end != '\0'; msg_end++) {} \ + __wasi_ciovec_t iov = {.buf = msg_start, .buf_len = msg_end - msg_start}; \ + size_t nwritten; \ + __wasi_fd_write(STDERR_FILENO, &iov, 1, &nwritten); \ +} while (0) +# define RB_WASM_DEBUG_LOG(msg) \ + RB_WASM_DEBUG_LOG_INTERNAL(__FILE__ ":" STRINGIZE(__LINE__) ": " msg "\n") #else -# define RB_WASM_DEBUG_LOG(...) +# define RB_WASM_DEBUG_LOG(msg) #endif enum rb_wasm_jmp_buf_state { @@ -57,17 +72,19 @@ async_buf_init(struct __rb_wasm_asyncify_jmp_buf* buf) // Global unwinding/rewinding jmpbuf state static rb_wasm_jmp_buf *_rb_wasm_active_jmpbuf; +void *rb_asyncify_unwind_buf; __attribute__((noinline)) int _rb_wasm_setjmp_internal(rb_wasm_jmp_buf *env) { - RB_WASM_DEBUG_LOG("[%s] env = %p, env->state = %d, _rb_wasm_active_jmpbuf = %p\n", __func__, env, env->state, _rb_wasm_active_jmpbuf); + RB_WASM_DEBUG_LOG("enter _rb_wasm_setjmp_internal"); switch (env->state) { case JMP_BUF_STATE_INITIALIZED: { - RB_WASM_DEBUG_LOG("[%s] JMP_BUF_STATE_INITIALIZED\n", __func__); + RB_WASM_DEBUG_LOG(" JMP_BUF_STATE_INITIALIZED"); env->state = JMP_BUF_STATE_CAPTURING; env->payload = 0; + env->longjmp_buf_ptr = NULL; _rb_wasm_active_jmpbuf = env; async_buf_init(&env->setjmp_buf); asyncify_start_unwind(&env->setjmp_buf); @@ -75,14 +92,14 @@ _rb_wasm_setjmp_internal(rb_wasm_jmp_buf *env) } case JMP_BUF_STATE_CAPTURING: { asyncify_stop_rewind(); - RB_WASM_DEBUG_LOG("[%s] JMP_BUF_STATE_CAPTURING\n", __func__); + RB_WASM_DEBUG_LOG(" JMP_BUF_STATE_CAPTURING"); env->state = JMP_BUF_STATE_CAPTURED; _rb_wasm_active_jmpbuf = NULL; return 0; } case JMP_BUF_STATE_RETURNING: { asyncify_stop_rewind(); - RB_WASM_DEBUG_LOG("[%s] JMP_BUF_STATE_RETURNING\n", __func__); + RB_WASM_DEBUG_LOG(" JMP_BUF_STATE_RETURNING"); env->state = JMP_BUF_STATE_CAPTURED; _rb_wasm_active_jmpbuf = NULL; return env->payload; @@ -96,37 +113,101 @@ _rb_wasm_setjmp_internal(rb_wasm_jmp_buf *env) void _rb_wasm_longjmp(rb_wasm_jmp_buf* env, int value) { - RB_WASM_DEBUG_LOG("[%s] env = %p, env->state = %d, value = %d\n", __func__, env, env->state, value); + RB_WASM_DEBUG_LOG("enter _rb_wasm_longjmp"); assert(env->state == JMP_BUF_STATE_CAPTURED); assert(value != 0); env->state = JMP_BUF_STATE_RETURNING; env->payload = value; + // Asyncify buffer built during unwinding for longjmp will not + // be used to rewind, so re-use static-variable. + static struct __rb_wasm_asyncify_jmp_buf tmp_longjmp_buf; + env->longjmp_buf_ptr = &tmp_longjmp_buf; _rb_wasm_active_jmpbuf = env; - async_buf_init(&env->longjmp_buf); - asyncify_start_unwind(&env->longjmp_buf); + async_buf_init(env->longjmp_buf_ptr); + asyncify_start_unwind(env->longjmp_buf_ptr); +} + + +enum try_catch_phase { + TRY_CATCH_PHASE_MAIN = 0, + TRY_CATCH_PHASE_RESCUE = 1, +}; + +void +rb_wasm_try_catch_init(struct rb_wasm_try_catch *try_catch, + rb_wasm_try_catch_func_t try_f, + rb_wasm_try_catch_func_t catch_f, + void *context) +{ + try_catch->state = TRY_CATCH_PHASE_MAIN; + try_catch->try_f = try_f; + try_catch->catch_f = catch_f; + try_catch->context = context; +} + +// NOTE: This function is not processed by Asyncify due to a call of asyncify_stop_rewind +void +rb_wasm_try_catch_loop_run(struct rb_wasm_try_catch *try_catch, rb_wasm_jmp_buf *target) +{ + extern void *rb_asyncify_unwind_buf; + extern rb_wasm_jmp_buf *_rb_wasm_active_jmpbuf; + + target->state = JMP_BUF_STATE_CAPTURED; + + switch ((enum try_catch_phase)try_catch->state) { + case TRY_CATCH_PHASE_MAIN: + // may unwind + try_catch->try_f(try_catch->context); + break; + case TRY_CATCH_PHASE_RESCUE: + if (try_catch->catch_f) { + // may unwind + try_catch->catch_f(try_catch->context); + } + break; + } + + { + // catch longjmp with target jmp_buf + while (rb_asyncify_unwind_buf && _rb_wasm_active_jmpbuf == target) { + // do similar steps setjmp does when JMP_BUF_STATE_RETURNING + + // stop unwinding + // (but call stop_rewind to update the asyncify state to "normal" from "unwind") + asyncify_stop_rewind(); + // clear the active jmpbuf because it's already stopped + _rb_wasm_active_jmpbuf = NULL; + // reset jmpbuf state to be able to unwind again + target->state = JMP_BUF_STATE_CAPTURED; + // move to catch loop phase + try_catch->state = TRY_CATCH_PHASE_RESCUE; + if (try_catch->catch_f) { + try_catch->catch_f(try_catch->context); + } + } + // no unwind or unrelated unwind, then exit + } } void * rb_wasm_handle_jmp_unwind(void) { - RB_WASM_DEBUG_LOG("[%s] _rb_wasm_active_jmpbuf = %p\n", __func__, _rb_wasm_active_jmpbuf); + RB_WASM_DEBUG_LOG("enter rb_wasm_handle_jmp_unwind"); if (!_rb_wasm_active_jmpbuf) { return NULL; } switch (_rb_wasm_active_jmpbuf->state) { - case JMP_BUF_STATE_CAPTURING: { - RB_WASM_DEBUG_LOG("[%s] JMP_BUF_STATE_CAPTURING\n", __func__); + case JMP_BUF_STATE_CAPTURING: + RB_WASM_DEBUG_LOG(" JMP_BUF_STATE_CAPTURING"); // save the captured Asyncify stack top _rb_wasm_active_jmpbuf->dst_buf_top = _rb_wasm_active_jmpbuf->setjmp_buf.top; break; - } - case JMP_BUF_STATE_RETURNING: { - RB_WASM_DEBUG_LOG("[%s] JMP_BUF_STATE_RETURNING\n", __func__); + case JMP_BUF_STATE_RETURNING: + RB_WASM_DEBUG_LOG(" JMP_BUF_STATE_RETURNING"); // restore the saved Asyncify stack top _rb_wasm_active_jmpbuf->setjmp_buf.top = _rb_wasm_active_jmpbuf->dst_buf_top; break; - } default: assert(0 && "unexpected state"); } diff --git a/wasm/setjmp.h b/wasm/setjmp.h index 30ea23ca12..cc14df33be 100644 --- a/wasm/setjmp.h +++ b/wasm/setjmp.h @@ -19,7 +19,7 @@ typedef struct { struct __rb_wasm_asyncify_jmp_buf setjmp_buf; // Internal Asyncify buffer space used while unwinding from longjmp // but never used for rewinding. - struct __rb_wasm_asyncify_jmp_buf longjmp_buf; + struct __rb_wasm_asyncify_jmp_buf *longjmp_buf_ptr; // Used to save top address of Asyncify stack `setjmp_buf`, which is // overwritten during first rewind. void *dst_buf_top; @@ -58,4 +58,38 @@ typedef rb_wasm_jmp_buf jmp_buf; #define setjmp(env) rb_wasm_setjmp(env) #define longjmp(env, payload) rb_wasm_longjmp(env, payload) + +typedef void (*rb_wasm_try_catch_func_t)(void *ctx); + +struct rb_wasm_try_catch { + rb_wasm_try_catch_func_t try_f; + rb_wasm_try_catch_func_t catch_f; + void *context; + int state; +}; + +// +// Lightweight try-catch API without unwinding to root frame. +// + +void +rb_wasm_try_catch_init(struct rb_wasm_try_catch *try_catch, + rb_wasm_try_catch_func_t try_f, + rb_wasm_try_catch_func_t catch_f, + void *context); + +// Run, catch longjmp thrown by run, and re-catch longjmp thrown by catch, ... +// +// 1. run try_f of try_catch struct +// 2. catch longjmps with the given target jmp_buf or exit +// 3. run catch_f if not NULL, otherwise exit +// 4. catch longjmps with the given target jmp_buf or exit +// 5. repeat from step 3 +// +// NOTICE: This API assumes that all longjmp targeting the given jmp_buf are NOT called +// after the function that called this function has exited. +// +void +rb_wasm_try_catch_loop_run(struct rb_wasm_try_catch *try_catch, rb_wasm_jmp_buf *target); + #endif |