Skip to content
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
20 changes: 20 additions & 0 deletions site/source/docs/tools_reference/settings_reference.rst
Original file line number Diff line number Diff line change
Expand Up @@ -2509,6 +2509,26 @@ If 1, target compiling a shared Wasm Memory.

Default value: false

.. _shared_wasmgc:

SHARED_WASMGC
=============

If true, enables support for experimental shared Wasm GC. Expects the
module to contain a mutable shared anyref global to be imported as "env"
"_shared_heap_root" and exported as "_shared_heap_root". The import will be
provided a null value on the main thread, where the user code is expected to
initialize it with some shared object during the start function. This shared
object will then be provided as the import when instantiating the module on
additional Workers. This shared anyref global can be used to bootstrap
arbitrary shared Wasm GC state. Since LLVM cannot emit Wasm GC instructions
or shared anyref globals, users are expected to use wasm-merge to add the
_shared_heap_root global and additional Wasm GC code post-link.

.. note:: This is an experimental setting

Default value: false

.. _wasm_workers:

WASM_WORKERS
Expand Down
18 changes: 17 additions & 1 deletion src/lib/libpthread.js
Original file line number Diff line number Diff line change
Expand Up @@ -435,6 +435,9 @@ var LibraryPThread = {
#if LOAD_SOURCE_MAP
wasmSourceMap,
#endif
#if SHARED_WASMGC
sharedHeapRootVal: wasmExports['_shared_heap_root'].value,
#endif
#if MAIN_MODULE
dynamicLibraries,
// Share all modules that have been loaded so far. New workers
Expand Down Expand Up @@ -1354,7 +1357,20 @@ var LibraryPThread = {
}
worker.postMessage({cmd: {{{ CMD_CHECK_MAILBOX }}}});
}
}
},

#if SHARED_WASMGC
_shared_heap_root__deps: ['$makeSharedHeapRootGlobal'],
_shared_heap_root: "makeSharedHeapRootGlobal()",
$makeSharedHeapRootGlobal: () => {
// Wasm module for acquiring a shared anyref WebAssembly.Global:
// (module (global (export "g") (mut (ref null (shared any))) (ref.null (shared any))))
var bytes = new Uint8Array([0, 97, 115, 109, 1, 0, 0, 0, 6, 9, 1, 99, 101, 110, 1, 208, 101, 113, 11, 7, 5, 1, 1, 103, 3, 0]);
var module = new WebAssembly.Module(bytes);
var instance = new WebAssembly.Instance(module, {});
return instance.exports.g;
},
#endif // SHARED_WASMGC
};

autoAddDeps(LibraryPThread, '$PThread');
Expand Down
4 changes: 4 additions & 0 deletions src/runtime_pthread.js
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,10 @@ if (ENVIRONMENT_IS_PTHREAD) {
wasmSourceMap = resetPrototype(WasmSourceMap, msgData.wasmSourceMap);
#endif

#if SHARED_WASMGC
__shared_heap_root.value = msgData.sharedHeapRootVal;
#endif

#if !WASM_ESM_INTEGRATION
#if MINIMAL_RUNTIME
// Pass the shared Wasm module in the Module object for MINIMAL_RUNTIME.
Expand Down
14 changes: 14 additions & 0 deletions src/settings.js
Original file line number Diff line number Diff line change
Expand Up @@ -1661,6 +1661,20 @@ var USE_SQLITE3 = false;
// [compile+link]
var SHARED_MEMORY = false;

// If true, enables support for experimental shared Wasm GC. Expects the
// module to contain a mutable shared anyref global to be imported as "env"
// "_shared_heap_root" and exported as "_shared_heap_root". The import will be
// provided a null value on the main thread, where the user code is expected to
// initialize it with some shared object during the start function. This shared
// object will then be provided as the import when instantiating the module on
// additional Workers. This shared anyref global can be used to bootstrap
// arbitrary shared Wasm GC state. Since LLVM cannot emit Wasm GC instructions
// or shared anyref globals, users are expected to use wasm-merge to add the
// _shared_heap_root global and additional Wasm GC code post-link.
// [link]
// [experimental]
var SHARED_WASMGC = false;

// Enables support for Wasm Workers. Wasm Workers enable applications
// to create threads using a lightweight web-specific API that builds on top
// of Wasm SharedArrayBuffer + Atomics API.
Expand Down
1 change: 1 addition & 0 deletions test/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@
EMCONFIG = exe_path_from_root('em-config')
EMRUN = exe_path_from_root('emrun')
WASM_DIS = os.path.join(building.get_binaryen_bin(), 'wasm-dis')
WASM_MERGE = os.path.join(building.get_binaryen_bin(), 'wasm-merge')
LLVM_OBJDUMP = shared.llvm_tool_path('llvm-objdump')
PYTHON = sys.executable

Expand Down
85 changes: 85 additions & 0 deletions test/test_other.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
NON_ZERO,
PYTHON,
TEST_ROOT,
WASM_MERGE,
WEBIDL_BINDER,
RunnerCore,
check_node_version,
Expand Down Expand Up @@ -13275,6 +13276,90 @@ def test_pthread_js_exception(self):
self.set_setting('EXIT_RUNTIME')
self.do_runf('other/test_pthread_js_exception.c', 'missing is not defined', assert_returncode=NON_ZERO, cflags=['-pthread'])

@requires_pthreads
@requires_node_25
def test_shared_wasmgc(self):
create_file('test_shared_wasmgc.c', r'''
#include <pthread.h>
#include <emscripten.h>
#include <emscripten/console.h>

__attribute__((import_module("wat"))) void shared_gc_main(void);

void print_int(int val) {
emscripten_console_logf("%d", val);
}

void* thread_main(void* arg) {
shared_gc_main();
return NULL;
}

int main() {
shared_gc_main();

pthread_t t1, t2, t3;
pthread_create(&t1, NULL, thread_main, NULL);
pthread_create(&t2, NULL, thread_main, NULL);
pthread_create(&t3, NULL, thread_main, NULL);

pthread_join(t1, NULL);
pthread_join(t2, NULL);
pthread_join(t3, NULL);

return 0;
}
''')

create_file('shared_gc.wat', r'''
(module
(type $counter (shared (struct (field (mut i32)))))

(import "app" "print_int" (func $print_int (param i32)))

(global $root
(export "_shared_heap_root")
(import "env" "_shared_heap_root")
(mut (ref null (shared any)))
)

(func $init
(if (ref.is_null (global.get $root))
(then
(global.set $root (struct.new $counter (i32.const 0)))
)
)
)
(start $init)

(func (export "shared_gc_main")
(call $print_int (ref.is_null (global.get $root)))
)
)
''')

out_js = self.in_dir('test_shared_wasmgc.js')
out_wasm = self.in_dir('test_shared_wasmgc.wasm')

self.run_process([
EMCC, '-pthread', '-sSHARED_WASMGC', '-sERROR_ON_UNDEFINED_SYMBOLS=0',
'-sEXIT_RUNTIME', '-sPROXY_TO_PTHREAD',
'-sEXPORTED_FUNCTIONS=_main,_print_int', 'test_shared_wasmgc.c', '-o',
out_js,
])

self.run_process([
WASM_MERGE, '--enable-threads', '--enable-reference-types',
'--enable-gc', '--enable-shared-everything', out_wasm, 'app',
'shared_gc.wat', 'wat', '-o', out_wasm,
])

self.node_args.append('--experimental-wasm-shared')

# TODO: Once multithreaded casting is fixed, increment and print the counter value.
output = self.run_js(out_js)
self.assertEqual(output.splitlines(), ['0', '0', '0', '0'])

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would it make sense to log some non-zero dummy value?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, but doing anything more interesting than just sticking an i32.eqz in there is blocked on V8 fixing casts across threads.


@crossplatform
def test_config_closure_compiler(self):
self.run_process([EMCC, test_file('hello_world.c'), '--closure=1'])
Expand Down
5 changes: 5 additions & 0 deletions tools/emscripten.py
Original file line number Diff line number Diff line change
Expand Up @@ -821,6 +821,11 @@ def add_standard_wasm_imports(send_items_map):
if settings.IMPORTED_MEMORY:
send_items_map['memory'] = 'wasmMemory'

# This import should come from user code merged into the module with
# wasm-merge post-link.
if settings.SHARED_WASMGC:
send_items_map['_shared_heap_root'] = '__shared_heap_root'

if settings.AUTODEBUG:
extra_sent_items += [
'log_execution',
Expand Down
5 changes: 5 additions & 0 deletions tools/link.py
Original file line number Diff line number Diff line change
Expand Up @@ -517,6 +517,11 @@ def setup_pthreads():
'$invokeEntryPoint',
]

# This import should come from user code merged into the module with
# wasm-merge post-link.
if settings.SHARED_WASMGC:
settings.DEFAULT_LIBRARY_FUNCS_TO_INCLUDE += ['_shared_heap_root']

if settings.MINIMAL_RUNTIME:
building.user_requested_exports.add('exit')

Expand Down
2 changes: 2 additions & 0 deletions tools/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,7 @@
('NODERAWSOCKETS', 'WASMFS', 'the node:net backend is not wired into WASMFS sockets'),
('NODERAWSOCKETS', 'PROXY_POSIX_SOCKETS', 'they are alternative socket backends'),
('NODERAWSOCKETS', 'SOCKET_WEBRTC', 'they are alternative socket backends'),
('SHARED_WASMGC', 'NO_PTHREADS', 'SHARED_WASMGC requires threads to be enabled'),
]

EXPERIMENTAL_SETTINGS = {
Expand All @@ -166,6 +167,7 @@
'CROSS_ORIGIN_STORAGE': '-sCROSS_ORIGIN_STORAGE is experimental; the underlying browser API is not yet shipped in any browser',
'SUPPORT_BIG_ENDIAN': '-sSUPPORT_BIG_ENDIAN is experimental, not all features are fully supported.',
'WASM_ESM_INTEGRATION': '-sWASM_ESM_INTEGRATION is still experimental and not yet supported in browsers',
'SHARED_WASMGC': '-sSHARED_WASMGC is experimental and subject to change',
}

# For renamed settings the format is:
Expand Down
Loading