summaryrefslogtreecommitdiff
path: root/wasm
diff options
context:
space:
mode:
Diffstat (limited to 'wasm')
-rw-r--r--wasm/README.md24
-rw-r--r--wasm/asyncify.h10
-rw-r--r--wasm/machine.c6
-rw-r--r--wasm/machine.h5
-rw-r--r--wasm/runtime.c7
-rw-r--r--wasm/setjmp.c115
-rw-r--r--wasm/setjmp.h36
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