-
Notifications
You must be signed in to change notification settings - Fork 3.5k
SHARED_WASMGC mechanism for sending data to new threads #27472
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
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -436,7 +436,7 @@ var LibraryPThread = { | |
| wasmSourceMap, | ||
| #endif | ||
| #if SHARED_WASMGC | ||
| sharedHeapRootVal: wasmExports['_shared_heap_root'].value, | ||
| sharedHeapRootVal: wasmExports['_shared_heap_root']?.value ?? null, | ||
| #endif | ||
| #if MAIN_MODULE | ||
| dynamicLibraries, | ||
|
|
@@ -725,6 +725,9 @@ var LibraryPThread = { | |
| start_routine: threadParams.startRoutine, | ||
| arg: threadParams.arg, | ||
| pthread_ptr: threadParams.pthread_ptr, | ||
| #if SHARED_WASMGC | ||
| gcSpawnArg: threadParams.gcSpawnArg, | ||
| #endif | ||
| }; | ||
| #if OFFSCREENCANVAS_SUPPORT | ||
| // Note that we do not need to quote these names because they are only used | ||
|
|
@@ -901,10 +904,18 @@ var LibraryPThread = { | |
| } | ||
| #endif // OFFSCREENCANVAS_SUPPORT | ||
|
|
||
| #if SHARED_WASMGC | ||
| var gcSpawnArg = wasmExports['_gc_spawn_arg']?.value ?? null; | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ditto |
||
| #endif | ||
|
|
||
| // Synchronously proxy the thread creation to main thread if possible. If we | ||
| // need to transfer ownership of objects, then proxy asynchronously via | ||
| // postMessage. | ||
| if (ENVIRONMENT_IS_PTHREAD && (!transferList.length || error)) { | ||
| if (ENVIRONMENT_IS_PTHREAD && (!transferList.length || error) | ||
| #if SHARED_WASMGC | ||
| && gcSpawnArg === null | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Does
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. No, it can just be added as a normal property of the message. |
||
| #endif | ||
| ) { | ||
| return pthreadCreateProxied(pthread_ptr, attr, startRoutine, arg); | ||
| } | ||
|
|
||
|
|
@@ -925,6 +936,9 @@ var LibraryPThread = { | |
| startRoutine, | ||
| pthread_ptr, | ||
| arg, | ||
| #if SHARED_WASMGC | ||
| gcSpawnArg, | ||
| #endif | ||
| #if OFFSCREENCANVAS_SUPPORT | ||
| moduleCanvasId, | ||
| offscreenCanvases, | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -138,6 +138,11 @@ if (ENVIRONMENT_IS_PTHREAD) { | |
| #if ASSERTIONS | ||
| assert(msgData.pthread_ptr); | ||
| assert(wasmMemory, "CMD_RUN received before CMD_LOAD"); | ||
| #endif | ||
| #if SHARED_WASMGC | ||
| if (wasmExports['_gc_thread_state']) { | ||
| wasmExports['_gc_thread_state'].value = msgData.gcSpawnArg ?? null; | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do you need the |
||
| } | ||
| #endif | ||
| // Call inside JS module to set up the stack frame for this pthread in JS module scope. | ||
| // This needs to be the first thing that we do, as we cannot call to any C/C++ functions | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -13349,6 +13349,109 @@ def test_shared_wasmgc(self): | |
| output = self.run_js(out_js) | ||
| self.assertEqual(output.splitlines(), ['0', '0', '0', '0']) | ||
|
|
||
| @requires_pthreads | ||
| @requires_node_25 | ||
| def test_shared_wasmgc_thread_state(self): | ||
| create_file('test_shared_wasmgc_thread_state.c', r''' | ||
| #include <pthread.h> | ||
| #include <emscripten.h> | ||
| #include <emscripten/console.h> | ||
|
|
||
| __attribute__((import_module("wat"))) void run_test(void); | ||
| __attribute__((import_module("wat"))) void thread_main(void); | ||
|
|
||
| void print_int(int val) { | ||
| emscripten_console_logf("%d", val); | ||
| } | ||
|
|
||
| static void* thread_entry(void* arg) { | ||
| thread_main(); | ||
| return NULL; | ||
| } | ||
|
|
||
| pthread_t spawn_pthread(void) { | ||
| pthread_t thread; | ||
| pthread_create(&thread, NULL, thread_entry, NULL); | ||
| return thread; | ||
| } | ||
|
|
||
| void join_pthread(pthread_t thread) { | ||
| pthread_join(thread, NULL); | ||
| } | ||
|
|
||
| int main() { | ||
| run_test(); | ||
| return 0; | ||
| } | ||
| ''') | ||
|
|
||
| create_file('thread_state.wat', r''' | ||
| (module | ||
| (import "app" "print_int" (func $print_int (param i32))) | ||
| (import "app" "spawn_pthread" (func $spawn_pthread (result i32))) | ||
| (import "app" "join_pthread" (func $join_pthread (param i32))) | ||
|
|
||
| (global $state (export "_gc_thread_state") (mut (ref null (shared any))) (ref.null (shared none))) | ||
| (global $spawn_arg (export "_gc_spawn_arg") (mut (ref null (shared any))) (ref.null (shared none))) | ||
|
|
||
| (func $spawn_thread (param $arg (ref null (shared any))) (result i32) | ||
| (local $tid i32) | ||
| (global.set $spawn_arg (local.get $arg)) | ||
| (local.set $tid (call $spawn_pthread)) | ||
| (global.set $spawn_arg (ref.null (shared none))) | ||
| (local.get $tid) | ||
| ) | ||
|
|
||
| ;; TODO: Once multithreaded casting is fixed, have $spawn_thread take a | ||
| ;; function reference and an anyref argument. | ||
| (func (export "thread_main") | ||
| (call $print_int | ||
| (i31.get_u | ||
| (ref.cast (ref (shared i31)) | ||
| (global.get $state) | ||
| ) | ||
| ) | ||
| ) | ||
| ) | ||
|
|
||
| (func (export "run_test") | ||
| (local $t1 i32) | ||
| (local $t2 i32) | ||
| (local $t3 i32) | ||
|
|
||
| (local.set $t1 (call $spawn_thread (ref.i31_shared (i32.const 42)))) | ||
| (call $join_pthread (local.get $t1)) | ||
|
|
||
| (local.set $t2 (call $spawn_thread (ref.i31_shared (i32.const 100)))) | ||
| (call $join_pthread (local.get $t2)) | ||
|
|
||
| (local.set $t3 (call $spawn_thread (ref.i31_shared (i32.const 300)))) | ||
| (call $join_pthread (local.get $t3)) | ||
| ) | ||
| ) | ||
| ''') | ||
|
|
||
| out_js = self.in_dir('test_shared_wasmgc_thread_state.js') | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You don't (or at least you shouldn't) need |
||
| out_wasm = self.in_dir('test_shared_wasmgc_thread_state.wasm') | ||
|
|
||
| self.run_process([ | ||
| EMCC, '-pthread', '-sSHARED_WASMGC', '-sERROR_ON_UNDEFINED_SYMBOLS=0', | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why is |
||
| '-sEXIT_RUNTIME', '-sPROXY_TO_PTHREAD', | ||
| '-sEXPORTED_FUNCTIONS=_main,_print_int,_spawn_pthread,_join_pthread', | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Instead of |
||
| 'test_shared_wasmgc_thread_state.c', '-o', out_js, | ||
| ]) | ||
|
|
||
| self.run_process([ | ||
| WASM_MERGE, '--enable-threads', '--enable-reference-types', | ||
| '--enable-gc', '--enable-shared-everything', out_wasm, 'app', | ||
| 'thread_state.wat', 'wat', '-o', out_wasm, | ||
| ]) | ||
|
|
||
| self.node_args.append('--experimental-wasm-shared') | ||
|
|
||
| output = self.run_js(out_js) | ||
| self.assertEqual(sorted(output.splitlines()), ['100', '300', '42']) | ||
|
|
||
| @crossplatform | ||
| def test_config_closure_compiler(self): | ||
| self.run_process([EMCC, test_file('hello_world.c'), '--closure=1']) | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do you need the
?? nullit seems redundant to because if thenullwould only used if the LHS is already null-ish.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If we didn't have the
??and there was no export, this would end up sendingundefined. But then I guess the receiving thread would not do anything with theundefined, so it would be ok. I'll simplify this.