1
0
Fork 0
mirror of https://github.com/ruby/ruby.git synced 2022-11-09 12:17:21 -05:00

[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
This commit is contained in:
Yuta Saito 2022-01-15 23:10:48 +09:00
parent e41b121e94
commit 65f95f26ff
Notes: git 2022-01-19 11:19:45 +09:00
16 changed files with 640 additions and 1 deletions

View file

@ -42,6 +42,7 @@ m4_include([tool/m4/ruby_try_cflags.m4])dnl
m4_include([tool/m4/ruby_try_cxxflags.m4])dnl
m4_include([tool/m4/ruby_try_ldflags.m4])dnl
m4_include([tool/m4/ruby_universal_arch.m4])dnl
m4_include([tool/m4/ruby_wasm_tools.m4])dnl
m4_include([tool/m4/ruby_werror_flag.m4])dnl
AC_ARG_VAR([cflags], [additional CFLAGS (ignored when CFLAGS is given)])dnl
@ -136,6 +137,9 @@ AS_IF([test ! -z "$ac_cv_prog_CC" -a ! -z "$CC" -a "$CC" != "$ac_cv_prog_CC"], [
AC_MSG_ERROR(cached CC is different -- throw away $cache_file
(it is also a good idea to do 'make clean' before compiling))
])
RUBY_WASM_TOOLS
AS_CASE(["${build_os}"],
[linux*|cygwin*|msys*], [
# Naruse prefers GCC on Linux
@ -402,6 +406,15 @@ AS_CASE(["$build_os"],
])
rm -fr conftest*
])
AS_CASE(["$target_os"],
[wasi*], [
# Clang linker automatically uses wasm-opt with -O if it found.
# https://github.com/llvm/llvm-project/blob/812828984c10857a4cd260eb638c52a4411f9143/clang/lib/Driver/ToolChains/WebAssembly.cpp#L95-L118
# However optimization before asyncify causes misoptimization,
# so wrap clang to insert our fake wasm-opt, which does nothing, in PATH.
CC_WRAPPER=`cd -P "${tooldir}" && pwd`/wasm-clangw
CC="$CC_WRAPPER $CC"
])
cc_version=
for option in --version -v -V -qversion; do
@ -1218,6 +1231,7 @@ main()
[wasi*],[ LIBS="-lm -lwasi-emulated-mman -lwasi-emulated-signal -lwasi-emulated-getpid -lwasi-emulated-process-clocks $LIBS"
RUBY_APPEND_OPTIONS(CFLAGS, -D_WASI_EMULATED_SIGNAL -D_WASI_EMULATED_MMAN -D_WASI_EMULATED_GETPID -D_WASI_EMULATED_PROCESS_CLOCKS)
RUBY_APPEND_OPTIONS(CPPFLAGS, -D_WASI_EMULATED_SIGNAL -D_WASI_EMULATED_MMAN -D_WASI_EMULATED_GETPID -D_WASI_EMULATED_PROCESS_CLOCKS)
POSTLINK="\$(WASMOPT) --asyncify \$(wasmoptflags) --pass-arg=asyncify-ignore-imports -o \$@ \$@${POSTLINK:+; $POSTLINK}"
]
[ LIBS="-lm $LIBS"])
: ${ORIG_LIBS=$LIBS}
@ -3790,6 +3804,16 @@ AS_CASE(["$target_os"],
LIBRUBY='lib$(RUBY_SO_NAME).a'
LIBRUBYARG='-l$(RUBY_SO_NAME)'
])
],
[wasi*], [
FIRSTMAKEFILE=GNUmakefile:wasm/GNUmakefile.in
AC_LIBOBJ([wasm/runtime])
AC_LIBOBJ([wasm/fiber])
AC_LIBOBJ([wasm/machine])
AC_LIBOBJ([wasm/setjmp])
AC_LIBOBJ([wasm/machine_core])
AC_LIBOBJ([wasm/setjmp_core])
PLATFORM_DIR=wasm
])
MINIOBJS="$MINIDLNOBJ"

11
main.c
View file

@ -31,7 +31,7 @@
#endif
int
main(int argc, char **argv)
rb_main(int argc, char **argv)
{
#ifdef RUBY_DEBUG_ENV
ruby_set_debug_option(getenv("RUBY_DEBUG"));
@ -47,3 +47,12 @@ main(int argc, char **argv)
return ruby_run_node(ruby_options(argc, argv));
}
}
int main(int argc, char **argv) {
#if defined(__wasm__) && !defined(__EMSCRIPTEN__)
int rb_wasm_rt_start(int (main)(int argc, char **argv), int argc, char **argv);
return rb_wasm_rt_start(rb_main, argc, argv);
#else
return rb_main(argc, argv);
#endif
}

View file

@ -0,0 +1,21 @@
dnl -*- Autoconf -*-
AC_DEFUN([RUBY_WASM_TOOLS],
[AS_CASE(["$target_os"],
[wasi*], [
AC_CHECK_TOOL(WASMOPT, wasm-opt)
AS_IF([test x"${WASMOPT}" = x], [
AC_MSG_ERROR([wasm-opt is required])
])
AC_SUBST(wasmoptflags)
: ${wasmoptflags=-O3}
AC_MSG_CHECKING([wheather \$WASI_SDK_PATH is set])
AS_IF([test x"${WASI_SDK_PATH}" = x], [AC_MSG_RESULT([no])], [
AC_MSG_RESULT([yes])
CC="${WASI_SDK_PATH}/bin/clang"
LD="${WASI_SDK_PATH}/bin/clang"
AR="${WASI_SDK_PATH}/bin/llvm-ar"
RANLIB="${WASI_SDK_PATH}/bin/llvm-ranlib"
])
])
])dnl

9
tool/wasm-clangw Executable file
View file

@ -0,0 +1,9 @@
#!/bin/sh
# A Clang wrapper script to fake the clang linker driver.
# Clang linker automatically uses wasm-opt with -O if it found.
# However optimization before asyncify causes misoptimization,
# so wrap clang to insert our fake wasm-opt, which does nothing, in PATH.
src_dir="$(cd "$(dirname "$0")/../wasm" && pwd)"
export PATH="$src_dir:$PATH"
exec "$@"

19
wasm/GNUmakefile.in Normal file
View file

@ -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 $<

13
wasm/asyncify.h Normal file
View file

@ -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

83
wasm/fiber.c Normal file
View file

@ -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;
}
}

43
wasm/fiber.h Normal file
View file

@ -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

62
wasm/machine.c Normal file
View file

@ -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;
}

26
wasm/machine.h Normal file
View file

@ -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

25
wasm/machine_core.S Normal file
View file

@ -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

47
wasm/runtime.c Normal file
View file

@ -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;
}

134
wasm/setjmp.c Normal file
View file

@ -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;
}

61
wasm/setjmp.h Normal file
View file

@ -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

27
wasm/setjmp_core.S Normal file
View file

@ -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

36
wasm/wasm-opt Executable file
View file

@ -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