Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

wasm2c runtime: add per-thread initialization/free entrypoint #2332

Merged
merged 1 commit into from
Dec 4, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading