diff --git a/pkg/dart2wasm/bin/run_wasm.js b/pkg/dart2wasm/bin/run_wasm.js index e51890bb89d..6915ff1b539 100644 --- a/pkg/dart2wasm/bin/run_wasm.js +++ b/pkg/dart2wasm/bin/run_wasm.js @@ -363,8 +363,20 @@ const main = async () => { } const dart2wasm = await import(args[jsRuntimeArg]); - function compile(filename) { - // Create a Wasm module from the binary wasm file. + + /// Returns whether the `js-string` built-in is supported. + function detectImportedStrings() { + let bytes = [ + 0, 97, 115, 109, 1, 0, 0, 0, 1, 4, 1, 96, 0, + 0, 2, 23, 1, 14, 119, 97, 115, 109, 58, 106, 115, 45, + 115, 116, 114, 105, 110, 103, 4, 99, 97, 115, 116, 0, 0 + ]; + return !WebAssembly.validate( + new Uint8Array(bytes), {builtins: ['js-string']}); + } + + function compile(filename, withJsStringBuiltins) { + // Create a Wasm module from the binary Wasm file. var bytes; if (isJSC) { bytes = readFile(filename, "binary"); @@ -373,11 +385,10 @@ const main = async () => { } else { bytes = readRelativeToScript(filename, "binary"); } - return new WebAssembly.Module(bytes); - } - - function instantiate(filename, imports) { - return new WebAssembly.Instance(compile(filename), imports); + return WebAssembly.compile( + bytes, + withJsStringBuiltins ? {builtins: ['js-string']} : {} + ); } globalThis.window ??= globalThis; @@ -386,14 +397,17 @@ const main = async () => { // Is an FFI module specified? if (args.length > 2) { - // instantiate FFI module - var ffiInstance = instantiate(args[ffiArg], {}); - // Make its exports available as imports under the 'ffi' module name + // Instantiate FFI module. + var ffiInstance = await WebAssembly.instantiate(await compile(args[ffiArg], false), {}); + // Make its exports available as imports under the 'ffi' module name. importObject.ffi = ffiInstance.exports; } // Instantiate the Dart module, importing from the global scope. - var dartInstance = await dart2wasm.instantiate(Promise.resolve(compile(args[wasmArg])), Promise.resolve(importObject)); + var dartInstance = await dart2wasm.instantiate( + compile(args[wasmArg], detectImportedStrings()), + Promise.resolve(importObject), + ); // Call `main`. If tasks are placed into the event loop (by scheduling tasks // explicitly or awaiting Futures), these will automatically keep the script diff --git a/pkg/dart2wasm/lib/js/runtime_blob.dart b/pkg/dart2wasm/lib/js/runtime_blob.dart index 721d2cb9372..7bcd3f433b2 100644 --- a/pkg/dart2wasm/lib/js/runtime_blob.dart +++ b/pkg/dart2wasm/lib/js/runtime_blob.dart @@ -113,22 +113,6 @@ const jsRuntimeBlobPart3 = r''' return wrapped; } - if (WebAssembly.String === undefined) { - WebAssembly.String = { - "charCodeAt": (s, i) => s.charCodeAt(i), - "compare": (s1, s2) => { - if (s1 < s2) return -1; - if (s1 > s2) return 1; - return 0; - }, - "concat": (s1, s2) => s1 + s2, - "equals": (s1, s2) => s1 === s2, - "fromCharCode": (i) => String.fromCharCode(i), - "length": (s) => s.length, - "substring": (s, a, b) => s.substring(a, b), - }; - } - // Imports const dart2wasm = { '''; @@ -150,9 +134,25 @@ const jsRuntimeBlobPart5 = r''' Array: Array, Reflect: Reflect, }; + + const jsStringPolyfill = { + "charCodeAt": (s, i) => s.charCodeAt(i), + "compare": (s1, s2) => { + if (s1 < s2) return -1; + if (s1 > s2) return 1; + return 0; + }, + "concat": (s1, s2) => s1 + s2, + "equals": (s1, s2) => s1 === s2, + "fromCharCode": (i) => String.fromCharCode(i), + "length": (s) => s.length, + "substring": (s, a, b) => s.substring(a, b), + }; + dartInstance = await WebAssembly.instantiate(await modulePromise, { ...baseImports, ...(await importObjectPromise), + "wasm:js-string": jsStringPolyfill, }); return dartInstance; diff --git a/sdk/lib/_internal/wasm/lib/js_string.dart b/sdk/lib/_internal/wasm/lib/js_string.dart index af47437df9d..d6dd00e3469 100644 --- a/sdk/lib/_internal/wasm/lib/js_string.dart +++ b/sdk/lib/_internal/wasm/lib/js_string.dart @@ -81,7 +81,8 @@ final class JSStringImpl implements String { @override String operator +(String other) { if (other is JSStringImpl) { - return JSStringImpl(_jsConcat(toExternRef, other.toExternRef)); + return JSStringImpl( + _jsStringConcatImport(toExternRef, other.toExternRef)); } // TODO(joshualitt): Refactor `string_patch.dart` so we can directly @@ -712,32 +713,51 @@ bool _jsIdentical(WasmExternRef? ref1, WasmExternRef? ref2) => js.JS('Object.is', ref1, ref2); @pragma("wasm:prefer-inline") -int _jsCharCodeAt(WasmExternRef? stringRef, int index) => js - .JS( - 'WebAssembly.String.charCodeAt', stringRef, WasmI32.fromInt(index)) - .toIntUnsigned(); - -WasmExternRef _jsConcat(WasmExternRef? s1, WasmExternRef? s2) => - js.JS('WebAssembly.String.concat', s1, s2); +int _jsCharCodeAt(WasmExternRef? stringRef, int index) => + _jsStringCharCodeAtImport(stringRef, WasmI32.fromInt(index)) + .toIntUnsigned(); @pragma("wasm:prefer-inline") WasmExternRef _jsSubstring( WasmExternRef? stringRef, int startIndex, int endIndex) => - js.JS('WebAssembly.String.substring', stringRef, - WasmI32.fromInt(startIndex), WasmI32.fromInt(endIndex)); + _jsStringSubstringImport( + stringRef, WasmI32.fromInt(startIndex), WasmI32.fromInt(endIndex)); @pragma("wasm:prefer-inline") int _jsLength(WasmExternRef? stringRef) => - js.JS('WebAssembly.String.length', stringRef).toIntUnsigned(); + _jsStringLengthImport(stringRef).toIntUnsigned(); @pragma("wasm:prefer-inline") bool _jsEquals(WasmExternRef? s1, WasmExternRef? s2) => - js.JS('WebAssembly.String.equals', s1, s2).toBool(); + _jsStringEqualsImport(s1, s2).toBool(); @pragma("wasm:prefer-inline") int _jsCompare(WasmExternRef? s1, WasmExternRef? s2) => - js.JS('WebAssembly.String.compare', s1, s2).toIntSigned(); + _jsStringCompareImport(s1, s2).toIntSigned(); @pragma("wasm:prefer-inline") -WasmExternRef _jsFromCharCode(int charCode) => js.JS( - 'WebAssembly.String.fromCharCode', WasmI32.fromInt(charCode)); +WasmExternRef _jsFromCharCode(int charCode) => + _jsStringFromCharCodeImport(WasmI32.fromInt(charCode)); + +@pragma("wasm:import", "wasm:js-string.charCodeAt") +external WasmI32 _jsStringCharCodeAtImport(WasmExternRef? s, WasmI32 index); + +@pragma("wasm:import", "wasm:js-string.compare") +external WasmI32 _jsStringCompareImport(WasmExternRef? s1, WasmExternRef? s2); + +@pragma("wasm:import", "wasm:js-string.concat") +external WasmExternRef _jsStringConcatImport( + WasmExternRef? s1, WasmExternRef? s2); + +@pragma("wasm:import", "wasm:js-string.equals") +external WasmI32 _jsStringEqualsImport(WasmExternRef? s1, WasmExternRef? s2); + +@pragma("wasm:import", "wasm:js-string.fromCharCode") +external WasmExternRef _jsStringFromCharCodeImport(WasmI32 c); + +@pragma("wasm:import", "wasm:js-string.length") +external WasmI32 _jsStringLengthImport(WasmExternRef? s); + +@pragma("wasm:import", "wasm:js-string.substring") +external WasmExternRef _jsStringSubstringImport( + WasmExternRef? s, WasmI32 startIndex, WasmI32 endIndex);