Skip to content

Commit

Permalink
w2c runtime: add per-thread init/free API
Browse files Browse the repository at this point in the history
  • Loading branch information
keithw committed Dec 3, 2023
1 parent 6780262 commit 60c2c7e
Show file tree
Hide file tree
Showing 8 changed files with 171 additions and 7 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@ jobs:
env:
USE_NINJA: "1"
CC: "clang" # used by the wasm2c tests
WASM2C_CFLAGS: "-march=x86-64-v2 -fsanitize=address -DWASM_RT_USE_MMAP=0"
WASM2C_CFLAGS: "-march=x86-64-v2 -fsanitize=address -DWASM_RT_USE_MMAP=0 -DWASM_RT_USE_STACK_DEPTH_COUNT"
steps:
- uses: actions/setup-python@v1
with:
Expand Down
5 changes: 5 additions & 0 deletions wasm2c/.gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
wasm-rt-impl.o
wasm-rt-exceptions-impl.o
examples/**/*.o
examples/fac/fac
examples/rot13/rot13
Expand All @@ -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
7 changes: 7 additions & 0 deletions wasm2c/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
20 changes: 20 additions & 0 deletions wasm2c/examples/threads/Makefile
Original file line number Diff line number Diff line change
@@ -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
6 changes: 6 additions & 0 deletions wasm2c/examples/threads/sample.wat
Original file line number Diff line number Diff line change
@@ -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))
105 changes: 105 additions & 0 deletions wasm2c/examples/threads/threads.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
#include <pthread.h>
#include <stdio.h>

#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(&param, 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;
}
20 changes: 14 additions & 6 deletions wasm2c/wasm-rt-impl.c
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
}

Expand Down
13 changes: 13 additions & 0 deletions wasm2c/wasm-rt.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
*/
Expand Down

0 comments on commit 60c2c7e

Please sign in to comment.