Skip to content
Open
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
3 changes: 3 additions & 0 deletions site/source/docs/tools_reference/settings_reference.rst
Original file line number Diff line number Diff line change
Expand Up @@ -2536,6 +2536,9 @@ are not present. Since LLVM cannot emit Wasm GC instructions or shared anyref
globals, users are expected to use wasm-merge to add these globals and
additional Wasm GC code post-link.

Additionally, WebAssembly JS string builtins and imported string constants
(with module "'") are enabled when compiling the WebAssembly module.

.. note:: This is an experimental setting

Default value: false
Expand Down
22 changes: 17 additions & 5 deletions src/lib/libdylink.js
Original file line number Diff line number Diff line change
Expand Up @@ -935,22 +935,34 @@ var LibraryDylink = {
return moduleExports;
}

#if SHARED_WASMGC
var compileOptions = {
builtins: ['js-string'],
importedStringConstants: "'",
};
#endif

if (flags.loadAsync) {
return (async () => {
var instance;
if (binary instanceof WebAssembly.Module) {
instance = new WebAssembly.Instance(binary, info);
} else {
// Destructuring assignment without declaration has to be wrapped
// with parens or parser will treat the l-value as an object
// literal instead.
({ module: binary, instance } = await WebAssembly.instantiate(binary, info));
({ module: binary, instance } = await WebAssembly.instantiate(binary, info
#if SHARED_WASMGC
, compileOptions
#endif
));
}
return postInstantiation(binary, instance);
})();
}

var module = binary instanceof WebAssembly.Module ? binary : new WebAssembly.Module(binary);
var module = binary instanceof WebAssembly.Module ? binary : new WebAssembly.Module(binary
#if SHARED_WASMGC
, compileOptions
#endif
);
var instance = new WebAssembly.Instance(module, info);
return postInstantiation(module, instance);
}
Expand Down
30 changes: 26 additions & 4 deletions src/postamble_minimal.js
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,12 @@ const moduleUrl = `ENVIRONMENT_IS_AUDIO_WORKLET ? '${TARGET_BASENAME}.wasm' : ne
// precompiled WebAssembly Module.
assert(WebAssembly.instantiateStreaming || Module['wasm'], 'Must load WebAssembly Module in to variable Module.wasm before adding compiled output .js script to the DOM');
#endif
#if SHARED_WASMGC
var compileOptions = {
builtins: ['js-string'],
importedStringConstants: "'",
};
#endif
#if MODULARIZE || AUDIO_WORKLET
instantiatePromise =
#endif
Expand All @@ -210,13 +216,25 @@ instantiatePromise =
// Node's fetch API cannot be used for local files, so we cannot use instantiateStreaming
&& !ENVIRONMENT_IS_NODE
#endif
? WebAssembly.instantiateStreaming(fetch({{{ moduleUrl }}}), imports)
: WebAssembly.instantiate(Module['wasm'], imports)).then((output) => {
? WebAssembly.instantiateStreaming(fetch({{{ moduleUrl }}}), imports
#if SHARED_WASMGC
, compileOptions
#endif
)
: WebAssembly.instantiate(Module['wasm'], imports
#if SHARED_WASMGC
, compileOptions
#endif
)).then((output) => {
#else
#if MODULARIZE || AUDIO_WORKLET
instantiatePromise =
#endif
WebAssembly.instantiateStreaming(fetch({{{ moduleUrl }}}), imports).then((output) => {
WebAssembly.instantiateStreaming(fetch({{{ moduleUrl }}}), imports
#if SHARED_WASMGC
, compileOptions
#endif
).then((output) => {
#endif

#else // Non-streaming instantiation
Expand All @@ -235,7 +253,11 @@ assert(Module['wasm'], 'Must load WebAssembly Module in to variable Module.wasm
#if MODULARIZE || AUDIO_WORKLET
instantiatePromise =
#endif
WebAssembly.instantiate(Module['wasm'], imports).then(/** @suppress {missingProperties} */ (output) => {
WebAssembly.instantiate(Module['wasm'], imports
#if SHARED_WASMGC
, compileOptions
#endif
).then(/** @suppress {missingProperties} */ (output) => {
#endif

#if !LibraryManager.has('libexports.js') && ASYNCIFY != 1
Expand Down
43 changes: 37 additions & 6 deletions src/preamble.js
Original file line number Diff line number Diff line change
Expand Up @@ -543,6 +543,13 @@ var splitModuleProxyHandler = {
};
#endif

#if SHARED_WASMGC
var compileOptions = {
builtins: ['js-string'],
importedStringConstants: "'",
};
#endif

#if SPLIT_MODULE || !WASM_ASYNC_COMPILATION
function instantiateSync(file, info) {
var module;
Expand All @@ -569,15 +576,23 @@ function instantiateSync(file, info) {
}
}
}
module ||= new WebAssembly.Module(binary);
module ||= new WebAssembly.Module(binary
#if SHARED_WASMGC
, compileOptions
#endif
);
if (ENVIRONMENT_IS_NODE && !hasCached) {
#if RUNTIME_DEBUG
dbg('NODE_CODE_CACHING: saving module');
#endif
fs.writeFileSync(cachedCodeFile, v8.serialize(module));
}
#else // NODE_CODE_CACHING
module = new WebAssembly.Module(binary);
module = new WebAssembly.Module(binary
#if SHARED_WASMGC
, compileOptions
#endif
);
#endif // NODE_CODE_CACHING
var instance = new WebAssembly.Instance(module, info);
return [instance, module];
Expand All @@ -588,7 +603,11 @@ function instantiateSync(file, info) {
async function instantiateArrayBuffer(binaryFile, imports) {
try {
var binary = await getWasmBinary(binaryFile);
var instance = await WebAssembly.instantiate(binary, imports);
var instance = await WebAssembly.instantiate(binary, imports
#if SHARED_WASMGC
, compileOptions
#endif
);
return instance;
} catch (reason) {
err(`failed to asynchronously prepare wasm: ${reason}`);
Expand Down Expand Up @@ -637,7 +656,11 @@ async function instantiateAsync(binary, binaryFile, imports) {
#if expectToReceiveOnModule('onCOSCacheHit')
Module['onCOSCacheHit']?.(cosHash.value);
#endif
return WebAssembly.instantiate(cosBytes, imports);
return WebAssembly.instantiate(cosBytes, imports
#if SHARED_WASMGC
, compileOptions
#endif
);
} catch {
// Any error (not found, not allowed, …) — fetch from the network and
// attempt to store in COS for future page loads.
Expand Down Expand Up @@ -670,7 +693,11 @@ async function instantiateAsync(binary, binaryFile, imports) {
err(`COS store failed: ${storeErr}`);
}
})();
return WebAssembly.instantiate(wasmBytes, imports);
return WebAssembly.instantiate(wasmBytes, imports
#if SHARED_WASMGC
, compileOptions
#endif
);
} catch (fetchErr) {
// Network fetch failed; fall through to the standard path below.
err(`COS fallback fetch failed: ${fetchErr}`);
Expand Down Expand Up @@ -704,7 +731,11 @@ async function instantiateAsync(binary, binaryFile, imports) {
) {
try {
var response = fetch(binaryFile, {{{ makeModuleReceiveExpr('fetchSettings', "{ credentials: 'same-origin' }") }}});
var instantiationResult = await WebAssembly.instantiateStreaming(response, imports);
var instantiationResult = await WebAssembly.instantiateStreaming(response, imports
#if SHARED_WASMGC
, compileOptions
#endif
);
return instantiationResult;
} catch (reason) {
// We expect the most common failure cause to be a bad MIME type for the binary,
Expand Down
3 changes: 3 additions & 0 deletions src/settings.js
Original file line number Diff line number Diff line change
Expand Up @@ -1682,6 +1682,9 @@ var SHARED_MEMORY = false;
// are not present. Since LLVM cannot emit Wasm GC instructions or shared anyref
// globals, users are expected to use wasm-merge to add these globals and
// additional Wasm GC code post-link.
//
// Additionally, WebAssembly JS string builtins and imported string constants
// (with module "'") are enabled when compiling the WebAssembly module.
// [link]
// [experimental]
var SHARED_WASMGC = false;
Expand Down
54 changes: 54 additions & 0 deletions test/test_other.py
Original file line number Diff line number Diff line change
Expand Up @@ -13452,6 +13452,60 @@ def test_shared_wasmgc_thread_state(self):
output = self.run_js(out_js)
self.assertEqual(sorted(output.splitlines()), ['100', '300', '42'])

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

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

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

int main() {
run_test();
return 0;
}
''')

create_file('strings.wat', r'''
(module
(import "app" "print_int" (func $print_int (param i32)))
(import "wasm:js-string" "length" (func $length (param (ref null (shared extern))) (result i32)))
(import "wasm:js-string" "fromCharCode" (func $fromCharCode (param i32) (result (ref (shared extern)))))
(import "'" "hello world" (global $msg (ref (shared extern))))

(func (export "run_test")
(call $print_int (call $length (global.get $msg)))
(call $print_int (call $length (call $fromCharCode (i32.const 65))))
)
)

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.

I wonder if this could be written in assembly? (Thus avoiding the need to wasm-merge?) If not, I wonder how hard it would be to enable llvm assembly to support this?

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.

Almost, or even C given that clang nominally supports externrefs. The thing that prevents it from working in this case is that we're using shared externrefs in the import signatures to test the use of strings allocated on the shared heap. Clang definitely doesn't support that.

''')

out_js = self.in_dir('test_shared_wasmgc_strings.js')
out_wasm = self.in_dir('test_shared_wasmgc_strings.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_strings.c', '-o', out_js,
])

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

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

output = self.run_js(out_js)
self.assertEqual(output.splitlines(), ['11', '1'])

@crossplatform
def test_config_closure_compiler(self):
self.run_process([EMCC, test_file('hello_world.c'), '--closure=1'])
Expand Down
Loading