From 65f95f26ff0e7b4be4704fedc52344a26d22a4e2 Mon Sep 17 00:00:00 2001 From: Yuta Saito Date: Sat, 15 Jan 2022 23:10:48 +0900 Subject: [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 --- wasm/GNUmakefile.in | 19 ++++++++ wasm/asyncify.h | 13 +++++ wasm/fiber.c | 83 ++++++++++++++++++++++++++++++++ wasm/fiber.h | 43 +++++++++++++++++ wasm/machine.c | 62 ++++++++++++++++++++++++ wasm/machine.h | 26 ++++++++++ wasm/machine_core.S | 25 ++++++++++ wasm/runtime.c | 47 ++++++++++++++++++ wasm/setjmp.c | 134 ++++++++++++++++++++++++++++++++++++++++++++++++++++ wasm/setjmp.h | 61 ++++++++++++++++++++++++ wasm/setjmp_core.S | 27 +++++++++++ wasm/wasm-opt | 36 ++++++++++++++ 12 files changed, 576 insertions(+) create mode 100644 wasm/GNUmakefile.in create mode 100644 wasm/asyncify.h create mode 100644 wasm/fiber.c create mode 100644 wasm/fiber.h create mode 100644 wasm/machine.c create mode 100644 wasm/machine.h create mode 100644 wasm/machine_core.S create mode 100644 wasm/runtime.c create mode 100644 wasm/setjmp.c create mode 100644 wasm/setjmp.h create mode 100644 wasm/setjmp_core.S create mode 100755 wasm/wasm-opt (limited to 'wasm') 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 +#include "wasm/fiber.h" +#include "wasm/asyncify.h" + +#ifdef RB_WASM_ENABLE_DEBUG_LOG +# include +# 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 + +#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 +#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 + +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 +#include +#include +#include +#include "wasm/asyncify.h" +#include "wasm/machine.h" +#include "wasm/setjmp.h" + +#ifdef RB_WASM_ENABLE_DEBUG_LOG +# include +# 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 + +#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 -- cgit v1.2.3