diff --git a/wasm2c/.gitignore b/wasm2c/.gitignore index 094559b61..77757ac57 100644 --- a/wasm2c/.gitignore +++ b/wasm2c/.gitignore @@ -1,4 +1,5 @@ wasm-rt-impl.o +wasm-rt-exceptions-impl.o examples/**/*.o examples/fac/fac examples/rot13/rot13 @@ -9,3 +10,7 @@ examples/callback/callback examples/callback/callback.c examples/callback/callback.h examples/callback/callback.wasm +examples/threads/threads +examples/threads/sample.c +examples/threads/sample.h +examples/threads/sample.wasm diff --git a/wasm2c/README.md b/wasm2c/README.md index 5271e8927..e0739bfb4 100644 --- a/wasm2c/README.md +++ b/wasm2c/README.md @@ -295,6 +295,8 @@ void wasm_rt_allocate_externref_table(wasm_rt_externref_table_t*, uint32_t eleme void wasm_rt_free_funcref_table(wasm_rt_table_t*); void wasm_rt_free_externref_table(wasm_rt_table_t*); uint32_t wasm_rt_call_stack_depth; /* on platforms that don't use the signal handler to detect exhaustion */ +void wasm_rt_init_thread(void); +void wasm_rt_free_thread(void); ``` `wasm_rt_init` must be called by the embedder before anything else, to @@ -339,6 +341,11 @@ shared between modules, it must be defined only once, by the embedder. It is only used on platforms that don't use the signal handler to detect exhaustion. +`wasm_rt_init_thread` and `wasm_rt_free_thread` are used to initialize +and free the runtime state for a given thread (other than the one that +called `wasm_rt_init`). An example can be found in +`wasm2c/examples/threads`. + ### Runtime support for exception handling Several additional symbols must be defined if wasm2c is being run with support diff --git a/wasm2c/examples/threads/Makefile b/wasm2c/examples/threads/Makefile new file mode 100644 index 000000000..f6eb99986 --- /dev/null +++ b/wasm2c/examples/threads/Makefile @@ -0,0 +1,20 @@ +#SANITIZERS=-fsanitize=address -fsanitize=undefined -fno-sanitize-recover=all +CFLAGS=-I../.. -g -O2 -Wall -Wextra -Wno-unused -Wno-unused-parameter -Wno-array-bounds -Wno-ignored-optimization-argument -Wno-tautological-constant-out-of-range-compare -Wno-infinite-recursion -fno-optimize-sibling-calls -frounding-math -fsignaling-nans ${SANITIZERS} -pthread +LDFLAGS=${SANITIZERS} -pthread + +all: threads + +threads: threads.o sample.o ../../wasm-rt-impl.o ../../wasm-rt-exceptions-impl.o -lm + +clean: + rm -rf threads sample.wasm sample.c sample.h *.o ../../*.o + +sample.wasm: sample.wat ../../../bin/wat2wasm + ../../../bin/wat2wasm --debug-names $< -o $@ + +sample.c: sample.wasm ../../../bin/wasm2c + ../../../bin/wasm2c $< -o $@ + +threads.o: sample.c + +.PHONY: all clean diff --git a/wasm2c/examples/threads/sample.wat b/wasm2c/examples/threads/sample.wat new file mode 100644 index 000000000..f9680dfff --- /dev/null +++ b/wasm2c/examples/threads/sample.wat @@ -0,0 +1,6 @@ +;; Module for demonstrating multi-threaded runtime + +(func (export "multiplyby3") (param i32) (result i32) (i32.mul (local.get 0) (i32.const 3))) + +(func $stackoverflow (export "stackoverflow") + (call $stackoverflow)) diff --git a/wasm2c/examples/threads/threads.c b/wasm2c/examples/threads/threads.c new file mode 100644 index 000000000..14587ed6c --- /dev/null +++ b/wasm2c/examples/threads/threads.c @@ -0,0 +1,105 @@ +#include +#include + +#include "sample.h" +#include "wasm-rt-exceptions.h" +#include "wasm-rt-impl.h" + +#define NUM_THREADS 1024 + +void* do_thread(void* arg); + +/** + * Example demonstrating use of the wasm2c runtime in multithreaded code. + * + * The program calls wasm_rt_init() on startup, and each new thread calls + * wasm_rt_init_thread() before instantiating a Wasm module. The sample + * module is designed to trap with stack exhaustion; this example tests + * that each thread can successfully catch the trap (in its own altstack) + * independently. + */ + +int main(int argc, char** argv) { + pthread_t threads[NUM_THREADS]; + int arguments[NUM_THREADS]; + + /* Initialize the Wasm runtime. */ + wasm_rt_init(); + + /* Create and launch threads running the `do_thread` function. */ + for (int i = 0; i < NUM_THREADS; ++i) { + arguments[i] = i; + if (pthread_create(&threads[i], NULL, do_thread, &arguments[i])) { + perror("pthread_create"); + exit(EXIT_FAILURE); + } + } + + /* Join each thread. */ + for (int i = 0; i < NUM_THREADS; ++i) { + void* retval; + if (pthread_join(threads[i], &retval)) { + perror("pthread_join"); + exit(EXIT_FAILURE); + } + + /* Verify returned value is as expected */ + if ((retval != &arguments[i]) || (arguments[i] != 3 * i)) { + fprintf(stderr, "Unexpected return value from thread.\n"); + exit(EXIT_FAILURE); + } + } + + /* Free the Wasm runtime's state. */ + wasm_rt_free(); + + printf("%d/%d threads trapped successfully.\n", NUM_THREADS, NUM_THREADS); + + return EXIT_SUCCESS; +} + +void* do_thread(void* arg) { + int param; + memcpy(¶m, arg, sizeof(int)); + + /* Initialize the per-thread context for the Wasm runtime (in + practice, this allocates and installs an alternate stack for + catching segfaults caused by stack exhaustion or out-of-bounds + memory access). */ + wasm_rt_init_thread(); + + /* Instantiate the Wasm module. */ + w2c_sample inst; + wasm2c_sample_instantiate(&inst); + + /* Expect a stack-exhaustion trap. (N.B. in a pthreads-created stack, Linux's + segfault for stack overflow appears the same as one for memory OOB. This is + similar to the behavior of macOS when exhausting a non-pthreads stack. */ + wasm_rt_trap_t code = wasm_rt_impl_try(); + if (code != 0) { + if (code == WASM_RT_TRAP_OOB || code == WASM_RT_TRAP_EXHAUSTION) { + /* Trap arrived as expected. Now call the "real" function. */ + int returnval = w2c_sample_multiplyby3(&inst, param); + memcpy(arg, &returnval, sizeof(int)); + + /* Free the module instance. */ + wasm2c_sample_free(&inst); + + /* Free the per-thread runtime context. */ + wasm_rt_free_thread(); + + return arg; + } else { + fprintf(stderr, "Expected OOB or exhaustion trap but got %s\n", + wasm_rt_strerror(code)); + return NULL; + } + } + + /* Call the stack-overflow function. Expect a trap back to above. */ + w2c_sample_stackoverflow(&inst); + + /* If no trap... */ + fprintf(stderr, "Expected trap but did not get one\n"); + return NULL; +} diff --git a/wasm2c/wasm-rt-impl.c b/wasm2c/wasm-rt-impl.c index 8cbe0e4a4..197c53f80 100644 --- a/wasm2c/wasm-rt-impl.c +++ b/wasm2c/wasm-rt-impl.c @@ -280,12 +280,10 @@ static void os_cleanup_signal_handler(void) { #endif void wasm_rt_init(void) { + wasm_rt_init_thread(); #if WASM_RT_INSTALL_SIGNAL_HANDLER if (!g_signal_handler_installed) { g_signal_handler_installed = true; -#if !WASM_RT_USE_STACK_DEPTH_COUNT - os_allocate_and_install_altstack(); -#endif os_install_signal_handler(); } #endif @@ -309,10 +307,20 @@ void wasm_rt_free(void) { assert(wasm_rt_is_initialized()); #if WASM_RT_INSTALL_SIGNAL_HANDLER os_cleanup_signal_handler(); -#if !WASM_RT_USE_STACK_DEPTH_COUNT - os_disable_and_deallocate_altstack(); -#endif g_signal_handler_installed = false; +#endif + wasm_rt_free_thread(); +} + +void wasm_rt_init_thread(void) { +#if WASM_RT_INSTALL_SIGNAL_HANDLER && !WASM_RT_USE_STACK_DEPTH_COUNT + os_allocate_and_install_altstack(); +#endif +} + +void wasm_rt_free_thread(void) { +#if WASM_RT_INSTALL_SIGNAL_HANDLER && !WASM_RT_USE_STACK_DEPTH_COUNT + os_disable_and_deallocate_altstack(); #endif } diff --git a/wasm2c/wasm-rt.h b/wasm2c/wasm-rt.h index 341ac4457..a79271e39 100644 --- a/wasm2c/wasm-rt.h +++ b/wasm2c/wasm-rt.h @@ -331,6 +331,19 @@ bool wasm_rt_is_initialized(void); /** Free the runtime's state. */ void wasm_rt_free(void); +/* + * Initialize the multithreaded runtime for a given thread. Must be + * called by each thread (other than the one that called wasm_rt_init) + * before initializing a Wasm module or calling an exported + * function. + */ +void wasm_rt_init_thread(void); + +/* + * Free the individual thread's state. + */ +void wasm_rt_free_thread(void); + /** * A hardened jmp_buf that allows checking for initialization before use */