summaryrefslogtreecommitdiff
path: root/wasm
diff options
context:
space:
mode:
authorYuta Saito <kateinoigakukun@gmail.com>2022-01-15 23:10:48 +0900
committerYuta Saito <kateinoigakukun@gmail.com>2022-01-19 11:19:06 +0900
commit65f95f26ff0e7b4be4704fedc52344a26d22a4e2 (patch)
treee4bd17869d8dd479855592f3a571aacae19e964c /wasm
parente41b121e94ccce9877824e55f865885bbabe40c3 (diff)
[wasm] add asyncify based setjmp, fiber, register scan emulation
configure.ac: setup build tools and register objects main.c: wrap main with rb_wasm_rt_start to handle asyncify unwinds tool/m4/ruby_wasm_tools.m4: setup default command based on WASI_SDK_PATH environment variable. checks wasm-opt which is used for asyncify. tool/wasm-clangw wasm/wasm-opt: a clang wrapper which replaces real wasm-opt with do-nothing wasm-opt to avoid misoptimization before asyncify. asyncify is performed at POSTLINK, but clang linker driver tries to run optimization by wasm-opt unconditionally. inlining pass at wasm level breaks asyncify's assumption, so should not optimize before POSTLIK. wasm/GNUmakefile.in: wasm specific rules to compile objects
Notes
Notes: Merged: https://github.com/ruby/ruby/pull/5407
Diffstat (limited to 'wasm')
-rw-r--r--wasm/GNUmakefile.in19
-rw-r--r--wasm/asyncify.h13
-rw-r--r--wasm/fiber.c83
-rw-r--r--wasm/fiber.h43
-rw-r--r--wasm/machine.c62
-rw-r--r--wasm/machine.h26
-rw-r--r--wasm/machine_core.S25
-rw-r--r--wasm/runtime.c47
-rw-r--r--wasm/setjmp.c134
-rw-r--r--wasm/setjmp.h61
-rw-r--r--wasm/setjmp_core.S27
-rwxr-xr-xwasm/wasm-opt36
12 files changed, 576 insertions, 0 deletions
diff --git a/wasm/GNUmakefile.in b/wasm/GNUmakefile.in
new file mode 100644
index 0000000000..b3b427e023
--- /dev/null
+++ b/wasm/GNUmakefile.in
@@ -0,0 +1,19 @@
+include Makefile
+include $(srcdir)/template/GNUmakefile.in
+
+wasmdir = $(srcdir)/wasm
+GNUmakefile: $(wasmdir)/GNUmakefile.in
+WASMOPT = @WASMOPT@
+wasmoptflags = @wasmoptflags@
+
+WASM_OBJS = $(wasmdir)/machine_core.o $(wasmdir)/machine.o $(wasmdir)/setjmp.o $(wasmdir)/setjmp_core.o $(wasmdir)/fiber.o $(wasmdir)/runtime.o
+
+wasm/fiber.$(OBJEXT): $(wasmdir)/fiber.c $(wasmdir)/fiber.h $(wasmdir)/asyncify.h $(PLATFORM_D)
+wasm/machine.$(OBJEXT): $(wasmdir)/machine.c $(srcdir)/wasm/machine.h $(wasmdir)/asyncify.h $(PLATFORM_D)
+wasm/setjmp.$(OBJEXT): $(wasmdir)/setjmp.c $(wasmdir)/setjmp.h $(wasmdir)/machine.h $(wasmdir)/asyncify.h $(PLATFORM_D)
+wasm/runtime.$(OBJEXT): $(wasmdir)/runtime.c $(wasmdir)/machine.h $(wasmdir)/asyncify.h $(wasmdir)/setjmp.h $(PLATFORM_D)
+
+wasm/%.$(OBJEXT): $(wasmdir)/%.S $(PLATFORM_D)
+ @$(ECHO) compiling $<
+ $(Q) $(CC) $(CFLAGS) $(COUTFLAG)$@ -c $<
+
diff --git a/wasm/asyncify.h b/wasm/asyncify.h
new file mode 100644
index 0000000000..834fc8b570
--- /dev/null
+++ b/wasm/asyncify.h
@@ -0,0 +1,13 @@
+#ifndef RB_WASM_SUPPORT_ASYNCIFY_H
+#define RB_WASM_SUPPORT_ASYNCIFY_H
+
+__attribute__((import_module("asyncify"), import_name("start_unwind")))
+void asyncify_start_unwind(void *buf);
+__attribute__((import_module("asyncify"), import_name("stop_unwind")))
+void asyncify_stop_unwind(void);
+__attribute__((import_module("asyncify"), import_name("start_rewind")))
+void asyncify_start_rewind(void *buf);
+__attribute__((import_module("asyncify"), import_name("stop_rewind")))
+void asyncify_stop_rewind(void);
+
+#endif
diff --git a/wasm/fiber.c b/wasm/fiber.c
new file mode 100644
index 0000000000..ecc481b0ee
--- /dev/null
+++ b/wasm/fiber.c
@@ -0,0 +1,83 @@
+/*
+ This is a ucontext-like userland context switching API for WebAssembly based on Binaryen's Asyncify.
+
+ * NOTE:
+ * This mechanism doesn't take care of stack state. Just save and restore program counter and
+ * registers (rephrased as locals by Wasm term). So use-site need to save and restore the C stack pointer.
+ * This Asyncify based implementation is not much efficient and will be replaced with future stack-switching feature.
+ */
+
+#include <stdlib.h>
+#include "wasm/fiber.h"
+#include "wasm/asyncify.h"
+
+#ifdef RB_WASM_ENABLE_DEBUG_LOG
+# include <stdio.h>
+# define RB_WASM_DEBUG_LOG(...) fprintf(stderr, __VA_ARGS__)
+#else
+# define RB_WASM_DEBUG_LOG(...)
+#endif
+
+void
+rb_wasm_init_context(rb_wasm_fiber_context *fcp, void (*func)(void *, void *), void *arg0, void *arg1)
+{
+ fcp->asyncify_buf.top = &fcp->asyncify_buf.buffer[0];
+ fcp->asyncify_buf.end = &fcp->asyncify_buf.buffer[WASM_FIBER_STACK_BUFFER_SIZE];
+ fcp->is_rewinding = false;
+ fcp->is_started = false;
+ fcp->entry_point = func;
+ fcp->arg0 = arg0;
+ fcp->arg1 = arg1;
+ RB_WASM_DEBUG_LOG("[%s] fcp->asyncify_buf %p\n", __func__, &fcp->asyncify_buf);
+}
+
+static rb_wasm_fiber_context *_rb_wasm_active_next_fiber;
+
+void
+rb_wasm_swapcontext(rb_wasm_fiber_context *ofcp, rb_wasm_fiber_context *fcp)
+{
+ RB_WASM_DEBUG_LOG("[%s] enter ofcp = %p fcp = %p\n", __func__, ofcp, fcp);
+ if (ofcp->is_rewinding) {
+ asyncify_stop_rewind();
+ ofcp->is_rewinding = false;
+ return;
+ }
+ _rb_wasm_active_next_fiber = fcp;
+ RB_WASM_DEBUG_LOG("[%s] start unwinding asyncify_buf = %p\n", __func__, &ofcp->asyncify_buf);
+ asyncify_start_unwind(&ofcp->asyncify_buf);
+}
+
+void *
+rb_wasm_handle_fiber_unwind(void (**new_fiber_entry)(void *, void *),
+ void **arg0, void **arg1, bool *is_new_fiber_started)
+{
+ rb_wasm_fiber_context *next_fiber;
+ if (!_rb_wasm_active_next_fiber) {
+ RB_WASM_DEBUG_LOG("[%s] no next fiber\n", __func__);
+ *is_new_fiber_started = false;
+ return NULL;
+ }
+
+ next_fiber = _rb_wasm_active_next_fiber;
+ _rb_wasm_active_next_fiber = NULL;
+
+ RB_WASM_DEBUG_LOG("[%s] next_fiber->asyncify_buf = %p\n", __func__, &next_fiber->asyncify_buf);
+
+ *new_fiber_entry = next_fiber->entry_point;
+ *arg0 = next_fiber->arg0;
+ *arg1 = next_fiber->arg1;
+
+ if (!next_fiber->is_started) {
+ RB_WASM_DEBUG_LOG("[%s] new fiber started\n", __func__);
+ // start a new fiber if not started yet.
+ next_fiber->is_started = true;
+ *is_new_fiber_started = true;
+ return NULL;
+ } else {
+ RB_WASM_DEBUG_LOG("[%s] resume a fiber\n", __func__);
+ // resume a fiber again
+ next_fiber->is_rewinding = true;
+ *is_new_fiber_started = false;
+ return &next_fiber->asyncify_buf;
+ }
+}
diff --git a/wasm/fiber.h b/wasm/fiber.h
new file mode 100644
index 0000000000..0f3a336332
--- /dev/null
+++ b/wasm/fiber.h
@@ -0,0 +1,43 @@
+#ifndef RB_WASM_SUPPORT_FIBER_H
+#define RB_WASM_SUPPORT_FIBER_H
+
+#include <stdbool.h>
+
+#ifndef WASM_FIBER_STACK_BUFFER_SIZE
+# define WASM_FIBER_STACK_BUFFER_SIZE 6144
+#endif
+
+struct __rb_wasm_asyncify_fiber_ctx {
+ void* top;
+ void* end;
+ char buffer[WASM_FIBER_STACK_BUFFER_SIZE];
+};
+
+// Fiber execution context needed to perform context switch
+typedef struct {
+ // Fiber entry point called when the fiber started for the first time.
+ // NULL if the entry point is main
+ void (*entry_point)(void *, void *);
+ // Opaque argument pointers passed to the entry point function
+ void *arg0, *arg1;
+
+ // Internal asyncify buffer space
+ struct __rb_wasm_asyncify_fiber_ctx asyncify_buf;
+
+ bool is_rewinding;
+ bool is_started;
+} rb_wasm_fiber_context;
+
+// Initialize a given fiber context to be ready to pass to `rb_wasm_swapcontext`
+void rb_wasm_init_context(rb_wasm_fiber_context *fcp, void (*func)(void *, void *), void *arg0, void *arg1);
+
+// Swap the execution control with `target_fiber` and save the current context in `old_fiber`
+// NOTE: `old_fiber` must be the current executing fiber context
+void rb_wasm_swapcontext(rb_wasm_fiber_context *old_fiber, rb_wasm_fiber_context *target_fiber);
+
+// Returns the Asyncify buffer of next fiber if unwound for fiber context switch.
+// Used by the top level Asyncify handling in wasm/runtime.c
+void *rb_wasm_handle_fiber_unwind(void (**new_fiber_entry)(void *, void *),
+ void **arg0, void **arg1, bool *is_new_fiber_started);
+
+#endif
diff --git a/wasm/machine.c b/wasm/machine.c
new file mode 100644
index 0000000000..238041f93e
--- /dev/null
+++ b/wasm/machine.c
@@ -0,0 +1,62 @@
+#include <stdlib.h>
+#include "wasm/machine.h"
+#include "wasm/asyncify.h"
+
+#ifndef WASM_SCAN_STACK_BUFFER_SIZE
+# define WASM_SCAN_STACK_BUFFER_SIZE 6144
+#endif
+
+struct asyncify_buf {
+ void *top;
+ void *end;
+ uint8_t buffer[WASM_SCAN_STACK_BUFFER_SIZE];
+};
+
+static void
+init_asyncify_buf(struct asyncify_buf* buf)
+{
+ buf->top = &buf->buffer[0];
+ buf->end = &buf->buffer[WASM_SCAN_STACK_BUFFER_SIZE];
+}
+
+static void *_rb_wasm_active_scan_buf = NULL;
+
+void
+rb_wasm_scan_locals(rb_wasm_scan_func scan)
+{
+ static struct asyncify_buf buf;
+ static int spilling = 0;
+ if (!spilling) {
+ spilling = 1;
+ init_asyncify_buf(&buf);
+ _rb_wasm_active_scan_buf = &buf;
+ asyncify_start_unwind(&buf);
+ } else {
+ asyncify_stop_rewind();
+ spilling = 0;
+ _rb_wasm_active_scan_buf = NULL;
+ scan(buf.top, buf.end);
+ }
+}
+
+static void *rb_wasm_stack_base = NULL;
+
+__attribute__((constructor))
+int
+rb_wasm_record_stack_base(void)
+{
+ rb_wasm_stack_base = rb_wasm_get_stack_pointer();
+ return 0;
+}
+
+void
+_rb_wasm_scan_stack(rb_wasm_scan_func scan, void *current)
+{
+ scan(current, rb_wasm_stack_base);
+}
+
+void *
+rb_wasm_handle_scan_unwind(void)
+{
+ return _rb_wasm_active_scan_buf;
+}
diff --git a/wasm/machine.h b/wasm/machine.h
new file mode 100644
index 0000000000..4cf7228684
--- /dev/null
+++ b/wasm/machine.h
@@ -0,0 +1,26 @@
+#ifndef RB_WASM_SUPPORT_MACHINE_H
+#define RB_WASM_SUPPORT_MACHINE_H
+
+// Function pointer used as scan callbacks
+typedef void (*rb_wasm_scan_func)(void*, void*);
+
+// Scan WebAssembly locals in the all call stack (like registers) spilled by Asyncify
+// 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 the current stack pointer
+void *rb_wasm_get_stack_pointer(void);
+
+// Set the current stack pointer
+void rb_wasm_set_stack_pointer(void *sp);
+
+// Returns the Asyncify buffer of next rewinding if unwound for spilling locals.
+// Used by the top level Asyncify handling in wasm/runtime.c
+void *rb_wasm_handle_scan_unwind(void);
+
+#endif
diff --git a/wasm/machine_core.S b/wasm/machine_core.S
new file mode 100644
index 0000000000..f0050536f5
--- /dev/null
+++ b/wasm/machine_core.S
@@ -0,0 +1,25 @@
+ # extern int __stack_pointer;
+ .globaltype __stack_pointer, i32
+
+ # NOTE: Implement this in raw assembly to avoid stack pointer
+ # operations in C-prologue and epilogue.
+
+ # void *rb_wasm_get_stack_pointer(void);
+ .section .text.rb_wasm_get_stack_pointer,"",@
+ .globl rb_wasm_get_stack_pointer
+ .type rb_wasm_get_stack_pointer,@function
+rb_wasm_get_stack_pointer:
+ .functype rb_wasm_get_stack_pointer () -> (i32)
+ global.get __stack_pointer
+ end_function
+
+ # void rb_wasm_set_stack_pointer(void *sp);
+ .section .text.rb_wasm_set_stack_pointer,"",@
+ .globl rb_wasm_set_stack_pointer
+ .type rb_wasm_set_stack_pointer,@function
+rb_wasm_set_stack_pointer:
+ .functype rb_wasm_set_stack_pointer (i32) -> ()
+ local.get 0
+ global.set __stack_pointer
+ end_function
+
diff --git a/wasm/runtime.c b/wasm/runtime.c
new file mode 100644
index 0000000000..b5b0a1a966
--- /dev/null
+++ b/wasm/runtime.c
@@ -0,0 +1,47 @@
+#include "wasm/machine.h"
+#include "wasm/setjmp.h"
+#include "wasm/fiber.h"
+#include "wasm/asyncify.h"
+#include <stdlib.h>
+
+int rb_wasm_rt_start(int (main)(int argc, char **argv), int argc, char **argv) {
+ int result;
+ void *asyncify_buf;
+
+ bool new_fiber_started = false;
+ void *arg0 = NULL, *arg1 = NULL;
+ void (*fiber_entry_point)(void *, void *) = NULL;
+
+ while (1) {
+ if (fiber_entry_point) {
+ fiber_entry_point(arg0, arg1);
+ } else {
+ result = main(argc, argv);
+ }
+
+ // 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();
+
+ if ((asyncify_buf = rb_wasm_handle_jmp_unwind()) != NULL) {
+ asyncify_start_rewind(asyncify_buf);
+ continue;
+ }
+ if ((asyncify_buf = rb_wasm_handle_scan_unwind()) != NULL) {
+ asyncify_start_rewind(asyncify_buf);
+ continue;
+ }
+
+ asyncify_buf = rb_wasm_handle_fiber_unwind(&fiber_entry_point, &arg0, &arg1, &new_fiber_started);
+ // Newly starting fiber doesn't have asyncify buffer yet, so don't rewind it for the first time entry
+ if (asyncify_buf) {
+ asyncify_start_rewind(asyncify_buf);
+ continue;
+ } else if (new_fiber_started) {
+ continue;
+ }
+
+ break;
+ }
+ return result;
+}
diff --git a/wasm/setjmp.c b/wasm/setjmp.c
new file mode 100644
index 0000000000..3aadfc0cd6
--- /dev/null
+++ b/wasm/setjmp.c
@@ -0,0 +1,134 @@
+/*
+ This is a WebAssembly userland setjmp/longjmp implementation based on Binaryen's Asyncify.
+ Inspired by Alon Zakai's snippet released under the MIT License:
+ * https://github.com/kripken/talks/blob/991fb1e4b6d7e4b0ea6b3e462d5643f11d422771/jmp.c
+
+ WebAssembly doesn't have context-switching mechanism for now, so emulate it by Asyncify,
+ which transforms WebAssembly binary to unwind/rewind the execution point and store/restore
+ locals.
+
+ The basic concept of this implementation is:
+ 1. setjmp captures the current execution context by unwinding to the root frame, then immediately
+ rewind to the setjmp call using the captured context. The context is saved in jmp_buf.
+ 2. longjmp unwinds to the root frame and rewinds to a setjmp call re-using a passed jmp_buf.
+
+ This implementation also supports switching context across different call stack (non-standard)
+
+ This approach is good at behavior reproducibility and self-containedness compared to Emscripten's
+ JS exception approach. However this is super expensive because Asyncify inserts many glue code to
+ control execution point in userland.
+
+ This implementation will be replaced with future stack-switching feature.
+ */
+#include <stdint.h>
+#include <stdlib.h>
+#include <assert.h>
+#include <stdbool.h>
+#include "wasm/asyncify.h"
+#include "wasm/machine.h"
+#include "wasm/setjmp.h"
+
+#ifdef RB_WASM_ENABLE_DEBUG_LOG
+# include <stdio.h>
+# define RB_WASM_DEBUG_LOG(...) fprintf(stderr, __VA_ARGS__)
+#else
+# define RB_WASM_DEBUG_LOG(...)
+#endif
+
+enum rb_wasm_jmp_buf_state {
+ // Initial state
+ JMP_BUF_STATE_INITIALIZED = 0,
+ // Unwinding to the root or rewinding to the setjmp call
+ // to capture the current execution context
+ JMP_BUF_STATE_CAPTURING = 1,
+ // Ready for longjmp
+ JMP_BUF_STATE_CAPTURED = 2,
+ // Unwinding to the root or rewinding to the setjmp call
+ // to restore the execution context
+ JMP_BUF_STATE_RETURNING = 3,
+};
+
+void
+async_buf_init(struct __rb_wasm_asyncify_jmp_buf* buf)
+{
+ buf->top = &buf->buffer[0];
+ buf->end = &buf->buffer[WASM_SETJMP_STACK_BUFFER_SIZE];
+}
+
+// Global unwinding/rewinding jmpbuf state
+static rb_wasm_jmp_buf *_rb_wasm_active_jmpbuf;
+
+__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);
+ switch (env->state) {
+ case JMP_BUF_STATE_INITIALIZED: {
+ RB_WASM_DEBUG_LOG("[%s] JMP_BUF_STATE_INITIALIZED\n", __func__);
+ env->state = JMP_BUF_STATE_CAPTURING;
+ env->payload = 0;
+ _rb_wasm_active_jmpbuf = env;
+ async_buf_init(&env->setjmp_buf);
+ asyncify_start_unwind(&env->setjmp_buf);
+ return -1; // return a dummy value
+ }
+ case JMP_BUF_STATE_CAPTURING: {
+ asyncify_stop_rewind();
+ RB_WASM_DEBUG_LOG("[%s] JMP_BUF_STATE_CAPTURING\n", __func__);
+ 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__);
+ env->state = JMP_BUF_STATE_CAPTURED;
+ _rb_wasm_active_jmpbuf = NULL;
+ return env->payload;
+ }
+ default:
+ assert(0 && "unexpected state");
+ }
+ return 0;
+}
+
+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);
+ assert(env->state == JMP_BUF_STATE_CAPTURED);
+ assert(value != 0);
+ env->state = JMP_BUF_STATE_RETURNING;
+ env->payload = value;
+ _rb_wasm_active_jmpbuf = env;
+ async_buf_init(&env->longjmp_buf);
+ asyncify_start_unwind(&env->longjmp_buf);
+}
+
+void *
+rb_wasm_handle_jmp_unwind(void)
+{
+ RB_WASM_DEBUG_LOG("[%s] _rb_wasm_active_jmpbuf = %p\n", __func__, _rb_wasm_active_jmpbuf);
+ 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__);
+ // 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__);
+ // 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");
+ }
+ return &_rb_wasm_active_jmpbuf->setjmp_buf;
+}
diff --git a/wasm/setjmp.h b/wasm/setjmp.h
new file mode 100644
index 0000000000..30ea23ca12
--- /dev/null
+++ b/wasm/setjmp.h
@@ -0,0 +1,61 @@
+#ifndef RB_WASM_SUPPORT_SETJMP_H
+#define RB_WASM_SUPPORT_SETJMP_H
+
+#include "ruby/internal/config.h"
+#include <stdbool.h>
+
+#ifndef WASM_SETJMP_STACK_BUFFER_SIZE
+# define WASM_SETJMP_STACK_BUFFER_SIZE 6144
+#endif
+
+struct __rb_wasm_asyncify_jmp_buf {
+ void* top;
+ void* end;
+ char buffer[WASM_SETJMP_STACK_BUFFER_SIZE];
+};
+
+typedef struct {
+ // Internal Asyncify buffer space to save execution context
+ 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;
+ // Used to save top address of Asyncify stack `setjmp_buf`, which is
+ // overwritten during first rewind.
+ void *dst_buf_top;
+ // A payload value given by longjmp and returned by setjmp for the second time
+ int payload;
+ // Internal state field
+ int state;
+} rb_wasm_jmp_buf;
+
+// noinline to avoid breaking Asyncify assumption
+NOINLINE(int _rb_wasm_setjmp(rb_wasm_jmp_buf *env));
+NOINLINE(void _rb_wasm_longjmp(rb_wasm_jmp_buf *env, int payload));
+
+#define rb_wasm_setjmp(env) ((env).state = 0, _rb_wasm_setjmp(&(env)))
+
+// NOTE: Why is `_rb_wasm_longjmp` not `noreturn`? Why put `unreachable` in the call site?
+// Asyncify expects that `_rb_wasm_longjmp` returns its control, and Asyncify inserts a return
+// for unwinding after the call. This means that "`_rb_wasm_longjmp` returns its control but the
+// next line in the caller (C level) won't be executed".
+// On the other hand, `noreturn` means the callee won't return its control to the caller,
+// so compiler can assume that a function with the attribute won't reach the end of the function.
+// Therefore `_rb_wasm_longjmp`'s semantics is not exactly same as `noreturn`.
+#define rb_wasm_longjmp(env, payload) (_rb_wasm_longjmp(&env, payload), __builtin_unreachable())
+
+// Returns the Asyncify buffer of next rewinding if unwound for setjmp capturing or longjmp.
+// Used by the top level Asyncify handling in wasm/runtime.c
+void *rb_wasm_handle_jmp_unwind(void);
+
+
+//
+// POSIX-compatible declarations
+//
+
+typedef rb_wasm_jmp_buf jmp_buf;
+
+#define setjmp(env) rb_wasm_setjmp(env)
+#define longjmp(env, payload) rb_wasm_longjmp(env, payload)
+
+#endif
diff --git a/wasm/setjmp_core.S b/wasm/setjmp_core.S
new file mode 100644
index 0000000000..4a7194056e
--- /dev/null
+++ b/wasm/setjmp_core.S
@@ -0,0 +1,27 @@
+ # extern int _rb_wasm_setjmp_internal(rb_wasm_jmp_buf *env);
+ .functype _rb_wasm_setjmp_internal (i32) -> (i32)
+ # extern int __stack_pointer;
+ .globaltype __stack_pointer, i32
+
+ # A wrapper of _rb_wasm_setjmp_internal to save and restore stack pointer
+ # This cannot be implemented in C because there is no way to manipulate stack pointer
+ # without C-epilogue.
+
+ # extern int _rb_wasm_setjmp(rb_wasm_jmp_buf *env);
+ .section .text._rb_wasm_setjmp,"",@
+ .globl _rb_wasm_setjmp
+ .type _rb_wasm_setjmp,@function
+_rb_wasm_setjmp:
+ .functype _rb_wasm_setjmp (i32) -> (i32)
+ .local i32, i32
+ # save sp (this local is stored in asyncify stack and restored when rewinding)
+ global.get __stack_pointer
+ local.set 1
+
+ local.get 0
+ call _rb_wasm_setjmp_internal
+
+ # restore sp
+ local.get 1
+ global.set __stack_pointer
+ end_function
diff --git a/wasm/wasm-opt b/wasm/wasm-opt
new file mode 100755
index 0000000000..3ae21b8206
--- /dev/null
+++ b/wasm/wasm-opt
@@ -0,0 +1,36 @@
+#!/bin/sh
+# A fake wasm-opt, which does nothing at all
+# See also: tool/wasm-clangw
+
+set -e
+input=
+output=
+while [ $# -ne 0 ]; do
+ case "$1" in
+ -o)
+ shift
+ output=$1
+ ;;
+ -*)
+ # ignore other options
+ ;;
+ *)
+ input=$1
+ ;;
+ esac
+ shift
+done
+
+if [ -z "$input" ]; then
+ echo "missing input binary"
+ exit 1
+fi
+
+if [ -z "$output" ]; then
+ echo "missing output binary"
+ exit 1
+fi
+
+if [ "$input" != "$output" ]; then
+ cp "$input" "$output"
+fi