[dart2wasm,standalone] Migrate everything except String This migrates remaining dart2wasm patches to avoid js-interop outside of passing strings. Change-Id: I92a9bb6cb97305a51858901d27966c3bd0af8fc6 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/491540 Reviewed-by: Martin Kustermann <kustermann@google.com> Reviewed-by: Slava Egorov <vegorov@google.com> Commit-Queue: Martin Kustermann <kustermann@google.com>
diff --git a/pkg/dart2wasm/test/ir_tests/standalone.wat b/pkg/dart2wasm/test/ir_tests/standalone.wat index 2a64c90..534736e 100644 --- a/pkg/dart2wasm/test/ir_tests/standalone.wat +++ b/pkg/dart2wasm/test/ir_tests/standalone.wat
@@ -1,9 +1,9 @@ (module $module0 - (type $JSExternWrapper (sub $Object (struct + (type $#Top (struct + (field $field0 i32))) + (type $JSExternWrapper (sub $#Top (struct (field $field0 i32) (field $_externRef externref)))) - (type $Object (struct - (field $field0 i32))) (global $"\"Hello world\"" (mut (ref null $JSExternWrapper)) (ref.null none)) ) \ No newline at end of file
diff --git a/pkg/test_runner/lib/src/browser.dart b/pkg/test_runner/lib/src/browser.dart index 6854ca8..7f38a98 100644 --- a/pkg/test_runner/lib/src/browser.dart +++ b/pkg/test_runner/lib/src/browser.dart
@@ -339,6 +339,7 @@ (module (type $i8array (array (mut i8))) (type $i16array (array (mut i16))) + (type $externarray (array (mut externref))) (import "wasm:js-string" "fromCharCodeArray" (func $fromCharCodeArray (param (ref null $i16array) i32 i32) (result (ref extern))) ) @@ -398,10 +399,15 @@ ) ) ) + + (func (export "emptyExternRefArray") + (result (ref $externarray)) + (array.new_default $externarray (i32.const 0)) + ) ) */ const _wasmStandaloneArrayHelper = - 'AGFzbQEAAAABIgVedwFeeAFgA2MAf38BZG9gA2QBf38BZG9gA2QAf38BZG8CJAEOd2FzbTpqcy1zdHJpbmcRZnJvbUNoYXJDb2RlQXJyYXkAAgMDAgMEBzICFHN0cmluZ0Zyb21Bc2NpaUJ5dGVzAAEXc3RyaW5nRnJvbUNoYXJDb2RlQXJyYXkAAgpTAkMCAX8BZAAgAiEDQQAgAvsGACEEAkADQCADQX9qIQMgAyABSA0BIAQgAyAAIAP7DQH7DgAMAAsACyAEQQAgBPsPEAALDQAgACABIAEgAmoQAAs='; + 'AGFzbQEAAAABKgdedwFeeAFebwFgA2MAf38BZG9gA2QBf38BZG9gA2QAf38BZG9gAAFkAgIkAQ53YXNtOmpzLXN0cmluZxFmcm9tQ2hhckNvZGVBcnJheQADAwQDBAUGB0gDFHN0cmluZ0Zyb21Bc2NpaUJ5dGVzAAEXc3RyaW5nRnJvbUNoYXJDb2RlQXJyYXkAAhNlbXB0eUV4dGVyblJlZkFycmF5AAMKWwNDAgF/AWQAIAIhA0EAIAL7BgAhBAJAA0AgA0F/aiEDIAMgAUgNASAEIAMgACAD+w0B+w4ADAALAAsgBEEAIAT7DxAACw0AIAAgASABIAJqEAALBwBBAPsHAgs='; String dart2wasmHtml( String title, @@ -487,6 +493,7 @@ } }, tryParseResultGetDouble: ({result}) => result, + doubleParseInfallible: (str) => +str, i64ToString: (source, radix) => source.toString(radix), f64ToExponential: (source) => source.toExponential(), f64ToExponentialWithFractionDigits: (source, digits) => { @@ -583,9 +590,47 @@ // local time and UTC. return -date.getTimezoneOffset() * 60; }, + mathPow: Math.pow, + mathAtan2: Math.atan2, + mathSin: Math.sin, + mathCos: Math.cos, + mathTan: Math.tan, + mathAcos: Math.acos, + mathAsin: Math.asin, + mathAtan: Math.atan, + mathExp: Math.exp, + mathLog: Math.log, + randomInt: () => { + const low = (Math.random() * 4294967295.0) | 0; + const high = (Math.random() * 4294967295.0) | 0; + + return (BigInt(high) << 32n) | BigInt(low); + }, + randomIntSecure: () => { + const typedArray = new BigUint64Array(1); + crypto.getRandomValues(typedArray); + return typedArray[0]; + }, + print: (line) => { + if (typeof dartPrint == "function") { + dartPrint(line); + } else { + console.log(line); + } + }, + jsonEncodeString: JSON.stringify, + debugger: () => { + debugger; + }, + inspect: (ref) => {}, + timelineStreamEnabled: () => false, + reportTaskEvent: (taskId, flowId, type, name, jsonArgs) => {}, }; """; final additionalImports = standalone ? '{ dart: dartEmbedder }' : '{}'; + final mainInvocation = standalone + ? r'appInstance.instantiatedModule.exports.$invokeMain(helperInstance.exports.emptyExternRefArray())' + : 'appInstance.invokeMain();'; return """ <!DOCTYPE html> @@ -635,7 +680,7 @@ Promise.all(modules.map((m) => fetch(m).then((b) => handleWasmBytes(m, b)))), }); dartMainRunner(() => { - appInstance.invokeMain(); + $mainInvocation }); }
diff --git a/sdk/lib/_internal/wasm/lib/convert_js_patch.dart b/sdk/lib/_internal/wasm/lib/convert_js_patch.dart new file mode 100644 index 0000000..4366fac --- /dev/null +++ b/sdk/lib/_internal/wasm/lib/convert_js_patch.dart
@@ -0,0 +1,55 @@ +// Copyright (c) 2026, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +import "dart:_error_utils"; +import "dart:_internal" show patch, unsafeCast; +import "dart:_js_string_convert"; +import "dart:_js_types"; +import "dart:_js_helper" show JS, jsStringFromDartString, JSExternWrapperExt; +import "dart:_object_helper"; +import "dart:_string"; +import "dart:_typed_data"; +import "dart:_wasm"; + +@patch +class _Utf8Decoder { + @patch + String? _convertSingleFastPath(List<int> codeUnits, int start, int end) { + if (codeUnits is JSUint8ArrayImpl) { + return decodeUtf8JS(codeUnits, start, end, allowMalformed); + } + } +} + +@patch +class _StringParser { + @patch + static WasmArray<WasmI16> stringToCharCodeArray(String string, int end) { + final externRef = jsStringFromDartString(string).wrappedExternRef; + final array = WasmArray<WasmI16>(end); + if (string.length == end) { + jsStringIntoCharCodeArray(externRef, array, 0.toWasmI32()); + } else { + for (int i = 0; i < end; ++i) array.write(i, jsCharCodeAt(externRef, i)); + } + + return array; + } +} + +// Assumes the given [string] is a valid float, so it can rely on the implicit +// string to number conversion in JS using `+<string-of-number>`. +@patch +double _parseValidFloat(String string) => + JS<double>('(s) => +s', jsStringFromDartString(string).wrappedExternRef); + +@patch +String _stringFromCharCodeArray(WasmArray<WasmI16> array, int start, int end) { + return JSStringImpl.fromCharCodeArray(array, start, end); +} + +@patch +String _stringFromAsciiBytes(WasmArray<WasmI8> source, int start, int end) { + return JSStringImpl.fromAsciiBytes(source, start, end); +}
diff --git a/sdk/lib/_internal/wasm/lib/convert_patch.dart b/sdk/lib/_internal/wasm/lib/convert_patch.dart index 475e738..07fb129 100644 --- a/sdk/lib/_internal/wasm/lib/convert_patch.dart +++ b/sdk/lib/_internal/wasm/lib/convert_patch.dart
@@ -7,14 +7,10 @@ import "dart:_error_utils"; import "dart:_internal" show patch, POWERS_OF_TEN, unsafeCast, pushWasmArray, popWasmArray; -import "dart:_js_string_convert"; -import "dart:_js_types"; -import "dart:_js_helper" show JS, jsStringFromDartString, JSExternWrapperExt; import "dart:_list" show GrowableList, WasmListBaseUnsafeExtensions, WasmListBase; import "dart:_string"; import "dart:_string_helper"; -import "dart:_object_helper"; import "dart:_typed_data"; import "dart:_object_helper"; import "dart:_wasm"; @@ -26,7 +22,7 @@ Object? Function(Object? key, Object? value)? reviver, ) { final listener = _JsonListener(reviver); - final parser = _JSStringImplParser(listener); + final parser = _StringParser(listener); parser.setNewChunk(unsafeCast<JSStringImpl>(source), source.length); parser.parse(0); parser.close(); @@ -291,13 +287,15 @@ this.array = newArray; } - String getString() => JSStringImpl.fromAsciiBytes(array, 0, length); + String getString() { + return _stringFromAsciiBytes(array, 0, length); + } // TODO(lrn): See if parsing of numbers can be abstracted to something // not only working on strings, but also on char-code lists, without losing // performance. num parseNum() => num.parse(getString()); - double parseDouble() => _jsParseValidFloat(getString()); + double parseDouble() => _parseValidFloat(getString()); } /** @@ -696,9 +694,7 @@ * built exactly during parsing. */ double parseDouble(int start, int end) { - return _jsParseValidFloat( - getString(start, end, 0x7f, _emptyCodeUnitsCache), - ); + return _parseValidFloat(getString(start, end, 0x7f, _emptyCodeUnitsCache)); } /** @@ -1659,34 +1655,32 @@ } /** - * Chunked JSON parser that parses [JSStringImpl] chunks. + * Chunked JSON parser that parses [String] chunks. */ -class _JSStringImplParser extends _ChunkedJsonParserState - with _ChunkedJsonParser<JSStringImpl> { +class _StringParser extends _ChunkedJsonParserState + with _ChunkedJsonParser<String> { @pragma('wasm:initialize-at-startup') static WasmArray<WasmI16> _emptyChunk = WasmArray<WasmI16>(0); - JSStringImpl _chunkAsString = unsafeCast<JSStringImpl>(''); + external static WasmArray<WasmI16> stringToCharCodeArray( + String string, + int end, + ); + + String _chunkAsString = ''; WasmArray<WasmI16> _chunkAsArray = _emptyChunk; int chunkEnd = 0; - _JSStringImplParser(_JsonListener listener) : super(listener); + _StringParser(_JsonListener listener) : super(listener); - void setNewChunk(JSStringImpl string, int end) { - final externRef = string.wrappedExternRef; - final array = WasmArray<WasmI16>(end); - if (string.length == end) { - jsStringIntoCharCodeArray(externRef, array, 0.toWasmI32()); - } else { - for (int i = 0; i < end; ++i) array.write(i, jsCharCodeAt(externRef, i)); - } + void setNewChunk(String string, int end) { _chunkAsString = string; - _chunkAsArray = array; + _chunkAsArray = stringToCharCodeArray(string, end); chunkEnd = end; } @override - JSStringImpl get chunk => _chunkAsString; + String get chunk => _chunkAsString; @pragma('wasm:prefer-inline') bool get isUtf16Input => true; @@ -1708,13 +1702,7 @@ } String getJsonObjectKeyString(int start, int end, int bits, int stringHash) { - return _internJSString( - _chunkAsString, - _chunkAsArray, - stringHash, - start, - end, - ); + return _internString(_chunkAsString, _chunkAsArray, stringHash, start, end); } void beginString() { @@ -1757,14 +1745,12 @@ } double parseDouble(int start, int end) { - return _jsParseValidFloat( - getString(start, end, 0x7f, _emptyCodeUnitsCache), - ); + return _parseValidFloat(getString(start, end, 0x7f, _emptyCodeUnitsCache)); } void close() { super.close(); - _jsStringInternCache.fill(0, null, _jsStringInternCacheSize); + _stringInternCache.fill(0, null, _stringInternCacheSize); } } @@ -1776,21 +1762,21 @@ @pragma("wasm:initialize-at-startup") final _codeUnitsCache = WasmArray<WasmI16>(_codeUnitsCacheSize); -const int _jsStringInternCacheSize = 512; +const int _stringInternCacheSize = 512; @pragma("wasm:initialize-at-startup") -final WasmArray<JSStringImpl?> _jsStringInternCache = WasmArray<JSStringImpl?>( - _jsStringInternCacheSize, +final WasmArray<String?> _stringInternCache = WasmArray<String?>( + _stringInternCacheSize, ); -JSStringImpl _internJSString( - JSStringImpl source, +String _internString( + String source, WasmArray<WasmI16> sourceAsArray, int stringHash, int start, int end, ) { final length = end - start; - final int index = stringHash & (_jsStringInternCacheSize - 1); - final existing = _jsStringInternCache[index]; + final int index = stringHash & (_stringInternCacheSize - 1); + final existing = _stringInternCache[index]; insert: { @@ -1810,23 +1796,21 @@ } } - final result = unsafeCast<JSStringImpl>( - source.substringUnchecked(start, end), - ); + final result = source.substringUnchecked(start, end); setIdentityHashField(result, stringHash); - _jsStringInternCache[index] = result; + _stringInternCache[index] = result; return result; } -JSStringImpl _internJSStringFromAsciiSlice( +String _internJSStringFromAsciiSlice( U8List source, int stringHash, int start, int end, ) { final length = end - start; - final int index = stringHash & (_jsStringInternCacheSize - 1); - final existing = _jsStringInternCache[index]; + final int index = stringHash & (_stringInternCacheSize - 1); + final existing = _stringInternCache[index]; insert: { @@ -1845,14 +1829,30 @@ } } - final result = JSStringImpl.fromAsciiBytes( + final result = _stringFromAsciiList(source, start, end); + setIdentityHashField(result, stringHash); + _stringInternCache[index] = result; + return result; +} + +external String _stringFromCharCodeArray( + WasmArray<WasmI16> array, + int start, + int end, +); + +external String _stringFromAsciiBytes( + WasmArray<WasmI8> source, + int start, + int end, +); + +String _stringFromAsciiList(U8List source, int start, int end) { + return _stringFromAsciiBytes( source.data, source.offsetInElements + start, source.offsetInElements + end, ); - setIdentityHashField(result, stringHash); - _jsStringInternCache[index] = result; - return result; } @patch @@ -1871,7 +1871,7 @@ class _JsonStringDecoderSink extends StringConversionSinkBase { final _JsonListener _listener; - late final _JSStringImplParser _stringParser; + late final _StringParser _stringParser; final Object? Function(Object? key, Object? value)? _reviver; @@ -1879,12 +1879,12 @@ _JsonStringDecoderSink(this._reviver, this._sink) : _listener = _JsonListener(_reviver) { - _stringParser = _JSStringImplParser(_listener); + _stringParser = _StringParser(_listener); } void addSlice(String chunk, int start, int end, bool isLast) { final parser = _stringParser; - parser.setNewChunk(unsafeCast<JSStringImpl>(chunk), end); + parser.setNewChunk(chunk, end); parser.parse(start); if (isLast) parser.close(); } @@ -1989,7 +1989,7 @@ addStringSliceToString(decoder.convertChunked(chunk, start, end)); } - JSStringImpl _stringFromBytesOrCache( + String _stringFromBytesOrCache( int start, int end, WasmArray<WasmI16> codeUnitsCache, @@ -2004,13 +2004,9 @@ chunk.offsetInElements + end, ), ); - return JSStringImpl.fromCharCodeArray(codeUnitsCache, 0, length); + return _stringFromCharCodeArray(codeUnitsCache, 0, length); } - return JSStringImpl.fromAsciiBytes( - chunk.data, - chunk.offsetInElements + start, - chunk.offsetInElements + end, - ); + return _stringFromAsciiList(chunk, start, end); } void addStringSliceToString(String string) { @@ -2040,12 +2036,8 @@ } double parseDouble(int start, int end) { - final result = JSStringImpl.fromAsciiBytes( - chunk.data, - chunk.offsetInElements + start, - chunk.offsetInElements + end, - ); - return _jsParseValidFloat(result); + final string = _stringFromAsciiList(chunk, start, end); + return _parseValidFloat(string); } } @@ -2179,6 +2171,12 @@ 82, 82, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, 98, ]); + external String? _convertSingleFastPath( + List<int> codeUnits, + int start, + int end, + ); + /// Reset the decoder to a state where it is ready to decode a new string but /// will not skip a leading BOM. Used by the fused UTF-8 / JSON decoder. void reset() { @@ -2214,14 +2212,9 @@ ); if (start == end) return ""; - if (codeUnits is JSUint8ArrayImpl) { - JSStringImpl? decoded = decodeUtf8JS( - codeUnits, - start, - end, - allowMalformed, - ); - if (decoded != null) return decoded; + if (_convertSingleFastPath(codeUnits, start, end) case final converted?) { + // Platform-specific fast path + return converted; } final WasmArray<WasmI8> bytes; @@ -2581,7 +2574,7 @@ return true; })(), ); - return JSStringImpl.fromCharCodeArray(_characterArray, 0, size); + return _stringFromCharCodeArray(_characterArray, 0, size); } String decode8(WasmArray<WasmI8> bytes, int start, int end, int size) { @@ -2628,7 +2621,7 @@ _characterArray.write(j++, byte & 0xFF); } assert(j == size); - return JSStringImpl.fromCharCodeArray(_characterArray, 0, size); + return _stringFromCharCodeArray(_characterArray, 0, size); } // This table is the Wasm array version of `_Utf8Decoder.transitionTable`, @@ -2727,7 +2720,7 @@ _charOrIndex = char; assert(j == size); - return JSStringImpl.fromCharCodeArray(_characterArray, 0, size); + return _stringFromCharCodeArray(_characterArray, 0, size); } String _decodeGeneral( @@ -2891,10 +2884,7 @@ return bytes; } -// Assumes the given [string] is a valid float, so it can rely on the implicit -// string to number conversion in JS using `+<string-of-number>`. -double _jsParseValidFloat(String string) => - JS<double>('(s) => +s', jsStringFromDartString(string).wrappedExternRef); +external double _parseValidFloat(String string); const ImmutableWasmArray<BoxedInt> _intBoxes256 = ImmutableWasmArray.literal([ 0, 1, 2, 3, 4, 5, 6, 7, //
diff --git a/sdk/lib/_internal/wasm_standalone/lib/convert_standalone_patch.dart b/sdk/lib/_internal/wasm_standalone/lib/convert_standalone_patch.dart new file mode 100644 index 0000000..26aca3f --- /dev/null +++ b/sdk/lib/_internal/wasm_standalone/lib/convert_standalone_patch.dart
@@ -0,0 +1,51 @@ +// Copyright (c) 2026, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +import "dart:_embedder" as embedder; +import "dart:_internal" show patch, unsafeCast; +import "dart:_js_helper" show jsStringFromDartString, JSExternWrapperExt; +import "dart:_string"; +import "dart:_wasm"; + +@patch +class _Utf8Decoder { + @patch + String? _convertSingleFastPath(List<int> codeUnits, int start, int end) { + // Always decode utf-8 in Dart. + return null; + } +} + +@patch +class _StringParser { + @patch + static WasmArray<WasmI16> stringToCharCodeArray(String string, int end) { + final externRef = unsafeCast<JSStringImpl>(string).wrappedExternRef; + final array = WasmArray<WasmI16>(end); + if (string.length == end) { + jsStringIntoCharCodeArray(externRef, array, 0.toWasmI32()); + } else { + for (int i = 0; i < end; ++i) array.write(i, jsCharCodeAt(externRef, i)); + } + + return array; + } +} + +@patch +double _parseValidFloat(String string) { + return embedder + .doubleParseInfallible(unsafeCast<JSStringImpl>(string).wrappedExternRef) + .toDouble(); +} + +@patch +String _stringFromCharCodeArray(WasmArray<WasmI16> array, int start, int end) { + return JSStringImpl.fromCharCodeArray(array, start, end); +} + +@patch +String _stringFromAsciiBytes(WasmArray<WasmI8> source, int start, int end) { + return JSStringImpl.fromAsciiBytes(source, start, end); +}
diff --git a/sdk/lib/_internal/wasm_standalone/lib/deferred_patch.dart b/sdk/lib/_internal/wasm_standalone/lib/deferred_patch.dart new file mode 100644 index 0000000..7fded7d --- /dev/null +++ b/sdk/lib/_internal/wasm_standalone/lib/deferred_patch.dart
@@ -0,0 +1,30 @@ +// Copyright (c) 2026, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +final Set<int> _loaded = {}; + +// The standalone target doesn't support loading deferred libraries, dart2wasm +// emits an error when `--enable-deferred-loading` is enabled with this target. +// These methods are still referenced in the compiler, but we can simply make +// them do nothing. + +Future<void> loadLibraryFromLoadId(int loadId) { + _loaded.add(loadId); + return Future.value(); +} + +bool checkLibraryIsLoadedFromLoadId(int loadId) { + if (_loaded.contains(loadId)) { + return true; + } + throw DeferredLoadIdNotLoadedError(); +} + +class DeferredLoadIdNotLoadedError extends Error implements NoSuchMethodError { + DeferredLoadIdNotLoadedError(); + + String toString() { + return 'Deferred loading is not available with dart2wasm standalone'; + } +}
diff --git a/sdk/lib/_internal/wasm_standalone/lib/developer_patch.dart b/sdk/lib/_internal/wasm_standalone/lib/developer_patch.dart new file mode 100644 index 0000000..6498679 --- /dev/null +++ b/sdk/lib/_internal/wasm_standalone/lib/developer_patch.dart
@@ -0,0 +1,199 @@ +// Copyright (c) 2026, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +import 'dart:_embedder' as embedder; +import 'dart:_internal' show patch; +import 'dart:_js_helper' show jsStringFromDartString, JSExternWrapperExt; +import 'dart:_wasm'; +import 'dart:async' show Zone; +import 'dart:isolate'; + +@patch +bool debugger({bool when = true, String? message}) { + if (when) { + embedder.debugger( + message == null + ? WasmExternRef.nullRef + : jsStringFromDartString(message).wrappedExternRef, + ); + } + + return when; +} + +@patch +Object? inspect(Object? object) { + embedder.inspect(object == null ? null : WasmAnyRef.fromObject(object)); + return object; +} + +@patch +void log( + String message, { + DateTime? time, + int? sequenceNumber, + int level = 0, + String name = '', + Zone? zone, + Object? error, + StackTrace? stackTrace, +}) { + // TODO. +} + +@patch +int get reachabilityBarrier => 0; + +@patch +abstract final class NativeRuntime { + @patch + static String? get buildId => null; + + @patch + static void writeHeapSnapshotToFile(String filepath) => + throw UnsupportedError( + "Generating heap snapshots is not supported for WebAssembly.", + ); + + @patch + static void streamTimelineTo( + TimelineRecorder recorder, { + String? path, + String streams = "Dart,GC,Compiler", + bool enableProfiler = false, + Duration samplingInterval = const Duration(microseconds: 1000), + }) => throw UnsupportedError( + "Streaming timelines is not supported for WebAssembly.", + ); + + @patch + static void stopStreamingTimeline() => throw UnsupportedError( + "Streaming timelines is not supported for WebAssembly.", + ); +} + +@patch +bool get extensionStreamHasListener => false; + +final _extensions = <String, ServiceExtensionHandler>{}; + +@patch +void _postEvent(String eventKind, String eventData) { + // TODO. +} + +@patch +ServiceExtensionHandler? _lookupExtension(String method) { + return _extensions[method]; +} + +@patch +_registerExtension(String method, ServiceExtensionHandler handler) { + _extensions[method] = handler; +} + +@patch +void _getServerInfo(SendPort sendPort) { + sendPort.send(null); +} + +@patch +void _webServerControl(SendPort sendPort, bool enable, bool? silenceOutput) { + sendPort.send(null); +} + +@patch +int _getServiceMajorVersion() { + return 0; +} + +@patch +int _getServiceMinorVersion() { + return 0; +} + +@patch +String? _getIsolateIdFromSendPort(SendPort sendPort) { + return null; +} + +@patch +String? _getObjectId(Object object) { + return null; +} + +@patch +class UserTag { + @patch + factory UserTag(String label) = _FakeUserTag; + + @patch + static UserTag get defaultTag => _FakeUserTag._defaultTag; +} + +final class _FakeUserTag implements UserTag { + static final _instances = <String, _FakeUserTag>{}; + + _FakeUserTag.real(this.label); + + factory _FakeUserTag(String label) { + // Canonicalize by name. + var existingTag = _instances[label]; + if (existingTag != null) { + return existingTag; + } + // Throw an exception if we've reached the maximum number of user tags. + if (_instances.length == UserTag.maxUserTags) { + throw UnsupportedError( + 'UserTag instance limit (${UserTag.maxUserTags}) reached.', + ); + } + return _instances[label] = _FakeUserTag.real(label); + } + + final String label; + + UserTag makeCurrent() { + var old = _currentTag; + _currentTag = this; + return old; + } + + static final UserTag _defaultTag = _FakeUserTag('Default'); +} + +var _currentTag = _FakeUserTag._defaultTag; + +@patch +UserTag getCurrentTag() => _currentTag; + +@patch +bool _isDartStreamEnabled() => embedder.dartTimelineStreamEnabled().toBool(); + +int _taskId = 1; + +@patch +int _getNextTaskId() { + return _taskId++; +} + +@patch +int _getTraceClock() => embedder.monotonicClockTicks().toInt(); + +@patch +void _reportTaskEvent( + int taskId, + int flowId, + int type, + String name, + String argumentsAsJson, +) { + embedder.reportTaskEvent( + WasmI32.fromInt(taskId), + WasmI32.fromInt(flowId), + WasmI32.fromInt(type), + jsStringFromDartString(name).wrappedExternRef, + jsStringFromDartString(argumentsAsJson).wrappedExternRef, + ); +}
diff --git a/sdk/lib/_internal/wasm_standalone/lib/embedder.dart b/sdk/lib/_internal/wasm_standalone/lib/embedder.dart index b355af7..e9c2a9a 100644 --- a/sdk/lib/_internal/wasm_standalone/lib/embedder.dart +++ b/sdk/lib/_internal/wasm_standalone/lib/embedder.dart
@@ -186,6 +186,10 @@ @pragma("wasm:import", "dart.tryParseResultGetDouble") external WasmF64 tryParseResultGetDouble(WasmExternRef? parseResult); +/// Parses a double from a string known to contain a a valid double. +@pragma("wasm:import", "dart.doubleParseInfallible") +external WasmF64 doubleParseInfallible(WasmExternRef? string); + /// The implementation of [int.toRadixString]. /// /// The SDK will only call this with radix values between 2 and 36 (inclusive). @@ -333,3 +337,53 @@ external WasmI32 timeZoneOffsetInSecondsForClampedSeconds( WasmI64 secondsSinceEpoch, ); + +@pragma("wasm:import", "dart.mathPow") +external WasmF64 mathPow(WasmF64 base, WasmF64 exponent); +@pragma("wasm:import", "dart.mathAtan2") +external WasmF64 mathAtan2(WasmF64 a, WasmF64 b); +@pragma("wasm:import", "dart.mathSin") +external WasmF64 mathSin(WasmF64 x); +@pragma("wasm:import", "dart.mathCos") +external WasmF64 mathCos(WasmF64 x); +@pragma("wasm:import", "dart.mathTan") +external WasmF64 mathTan(WasmF64 x); +@pragma("wasm:import", "dart.mathAcos") +external WasmF64 mathAcos(WasmF64 x); +@pragma("wasm:import", "dart.mathAsin") +external WasmF64 mathAsin(WasmF64 x); +@pragma("wasm:import", "dart.mathAtan") +external WasmF64 mathAtan(WasmF64 x); +@pragma("wasm:import", "dart.mathExp") +external WasmF64 mathExp(WasmF64 a); +@pragma("wasm:import", "dart.mathLog") +external WasmF64 mathLog(WasmF64 a); + +@pragma("wasm:import", "dart.randomInt") +external WasmI64 randomInt(); +@pragma("wasm:import", "dart.randomIntSecure") +external WasmI64 randomIntSecure(); + +@pragma("wasm:import", "dart.print") +external WasmVoid print(WasmExternRef? line); +@pragma("wasm:import", "dart.jsonEncodeString") +external WasmExternRef jsonEncodeString(WasmExternRef? line); + +@pragma("wasm:import", "dart.debugger") +external WasmVoid debugger(WasmExternRef? message); + +/// Send a reference to [object] to any attached debuggers. +@pragma("wasm:import", "dart.inspect") +external WasmVoid inspect(WasmAnyRef? object); + +@pragma("wasm:import", "dart.timelineStreamEnabled") +external WasmI32 dartTimelineStreamEnabled(); + +@pragma("wasm:import", "dart.reportTaskEvent") +external WasmI32 reportTaskEvent( + WasmI32 taskId, + WasmI32 flowId, + WasmI32 type, + WasmExternRef? name, + WasmExternRef? argumentsAsJson, +);
diff --git a/sdk/lib/_internal/wasm_standalone/lib/internal_json_encode_patch.dart b/sdk/lib/_internal/wasm_standalone/lib/internal_json_encode_patch.dart new file mode 100644 index 0000000..9c9d308 --- /dev/null +++ b/sdk/lib/_internal/wasm_standalone/lib/internal_json_encode_patch.dart
@@ -0,0 +1,12 @@ +// Copyright (c) 2026, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +import 'dart:_embedder' as embedder; +import "dart:_js_helper" show jsStringFromDartString, JSExternWrapperExt; +import 'dart:_string' show JSStringImpl; +import 'dart:_wasm'; + +String jsonEncode(String object) => JSStringImpl.fromRef( + embedder.jsonEncodeString(jsStringFromDartString(object).wrappedExternRef), +);
diff --git a/sdk/lib/_internal/wasm_standalone/lib/invoke_main_patch.dart b/sdk/lib/_internal/wasm_standalone/lib/invoke_main_patch.dart new file mode 100644 index 0000000..cefb7d4 --- /dev/null +++ b/sdk/lib/_internal/wasm_standalone/lib/invoke_main_patch.dart
@@ -0,0 +1,49 @@ +// Copyright (c) 2026, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +import 'dart:_string'; +import 'dart:_wasm'; + +@pragma("wasm:prefer-inline") +void _invokeMainArg0(List<String> args, Function() mainMethod) { + mainMethod(); + return; +} + +@pragma("wasm:prefer-inline") +void _invokeMainArg1(List<String> args, Function(List<String>) mainMethod) { + mainMethod(args); + return; +} + +@pragma("wasm:prefer-inline") +void _invokeMainArg2( + List<String> args, + Function(List<String>, Null) mainMethod, +) { + mainMethod(args, null); + return; +} + +// Will be patched in `pkg/dart2wasm/lib/compile.dart` right before TFA. +external void _invokeMainInternal(List<String> args); + +/// Used to invoke the `main` function from JS, printing any exceptions that +/// escape. +@pragma("wasm:export", "\$invokeMain") +WasmVoid _invokeMain(WasmArray<WasmExternRef?> args) { + try { + final dartArgs = <String>[]; + for (var i = 0; i < args.length; i++) { + dartArgs.add(JSStringImpl.fromRefUnchecked(args[i])); + } + + _invokeMainInternal(dartArgs); + return WasmVoid(); + } catch (e, s) { + print(e); + print(s); + rethrow; + } +}
diff --git a/sdk/lib/_internal/wasm_standalone/lib/math_externs_patch.dart b/sdk/lib/_internal/wasm_standalone/lib/math_externs_patch.dart new file mode 100644 index 0000000..e962831 --- /dev/null +++ b/sdk/lib/_internal/wasm_standalone/lib/math_externs_patch.dart
@@ -0,0 +1,102 @@ +// Copyright (c) 2026, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +import "dart:_error_utils"; +import 'dart:_embedder' as embedder; +import "dart:_internal" show patch; +import "dart:_wasm"; + +@patch +double atan2(num a, num b) => embedder + .mathAtan2( + WasmF64.fromDouble(a.toDouble()), + WasmF64.fromDouble(b.toDouble()), + ) + .toDouble(); +@patch +double sin(num radians) => + embedder.mathSin(WasmF64.fromDouble(radians.toDouble())).toDouble(); +@patch +double cos(num radians) => + embedder.mathCos(WasmF64.fromDouble(radians.toDouble())).toDouble(); +@patch +double tan(num radians) => + embedder.mathTan(WasmF64.fromDouble(radians.toDouble())).toDouble(); +@patch +double acos(num x) => + embedder.mathAcos(WasmF64.fromDouble(x.toDouble())).toDouble(); +@patch +double asin(num x) => + embedder.mathAsin(WasmF64.fromDouble(x.toDouble())).toDouble(); +@patch +double atan(num x) => + embedder.mathAtan(WasmF64.fromDouble(x.toDouble())).toDouble(); +@patch +double sqrt(num x) => x.toDouble().sqrt(); +@patch +double exp(num x) => + embedder.mathExp(WasmF64.fromDouble(x.toDouble())).toDouble(); +@patch +double log(num x) => + embedder.mathLog(WasmF64.fromDouble(x.toDouble())).toDouble(); + +double _doublePow(double base, double exponent) => embedder + .mathPow( + WasmF64.fromDouble(base.toDouble()), + WasmF64.fromDouble(exponent.toDouble()), + ) + .toDouble(); + +@patch +class _Random { + @patch + static int _initialSeed() => embedder.randomInt().toInt(); +} + +class _SecureRandom implements Random { + _SecureRandom() { + // Throw early in constructor if entropy source is not hooked up. + _getBytes(1); + } + + // Return count bytes of entropy as an integer; count <= 8. + int _getBytes(int count) { + final random = embedder.randomIntSecure().toInt(); + return random.toUnsigned(64); + } + + int nextInt(int max) { + RangeErrorUtils.checkValueInInterval( + max, + 1, + _POW2_32, + "max", + "Must be positive and <= 2^32", + ); + final byteCount = + ((max - 1).bitLength + 7) >> 3; // Divide number of bits by 8, round up. + if (byteCount == 0) { + return 0; // Not random if max == 1. + } + var rnd; + var result; + do { + rnd = _getBytes(byteCount); + result = rnd % max; + } while ((rnd - result + max) > (1 << (byteCount << 3))); + return result; + } + + double nextDouble() { + return (_getBytes(7) >> 3) / _POW2_53_D; + } + + bool nextBool() { + return _getBytes(1).isEven; + } + + // Constants used by the algorithm. + static const _POW2_32 = 1 << 32; + static const _POW2_53_D = 1.0 * (1 << 53); +}
diff --git a/sdk/lib/_internal/wasm_standalone/lib/print_patch.dart b/sdk/lib/_internal/wasm_standalone/lib/print_patch.dart new file mode 100644 index 0000000..31a5d70 --- /dev/null +++ b/sdk/lib/_internal/wasm_standalone/lib/print_patch.dart
@@ -0,0 +1,10 @@ +// Copyright (c) 2026, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +import 'dart:_embedder' as embedder; +import 'dart:_js_helper' show jsStringFromDartString, JSExternWrapperExt; + +@patch +void printToConsole(String line) => + embedder.print(jsStringFromDartString(line).wrappedExternRef);
diff --git a/sdk/lib/_internal/wasm_standalone/lib/typed_data_copy_from_js.dart b/sdk/lib/_internal/wasm_standalone/lib/typed_data_copy_from_js.dart new file mode 100644 index 0000000..14d7f8c --- /dev/null +++ b/sdk/lib/_internal/wasm_standalone/lib/typed_data_copy_from_js.dart
@@ -0,0 +1,30 @@ +// Copyright (c) 2026, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +import 'dart:_internal' show patch; + +// The standalone platform doesn't support interacting with JS typed array +// objects. + +@patch +bool tryCopyExternalIntTypedData( + Iterable<int> from, + _IntListMixin to, + int start, + int skipCount, + int count, +) { + return false; +} + +@patch +bool tryCopyExternalFloatTypedData( + Iterable<double> from, + _DoubleListMixin to, + int start, + int skipCount, + int count, +) { + return false; +}
diff --git a/sdk/lib/libraries.json b/sdk/lib/libraries.json index 9cd2f87..5b247d6 100644 --- a/sdk/lib/libraries.json +++ b/sdk/lib/libraries.json
@@ -168,7 +168,8 @@ "convert": { "uri": "convert/convert.dart", "patches": [ - "_internal/wasm/lib/convert_patch.dart" + "_internal/wasm/lib/convert_patch.dart", + "_internal/wasm/lib/convert_js_patch.dart" ] }, "typed_data": { @@ -305,13 +306,14 @@ "convert": { "uri": "convert/convert.dart", "patches": [ - "_internal/wasm/lib/convert_patch.dart" + "_internal/wasm/lib/convert_patch.dart", + "_internal/wasm_standalone/lib/convert_standalone_patch.dart" ] }, "developer": { "uri": "developer/developer.dart", "patches": [ - "_internal/js_runtime/lib/developer_patch.dart" + "_internal/wasm_standalone/lib/developer_patch.dart" ] }, "typed_data": { @@ -337,7 +339,7 @@ "_typed_data": { "uri": "_internal/wasm/lib/typed_data.dart", "patches": [ - "_internal/wasm/lib/typed_data_copy_from_js.dart" + "_internal/wasm_standalone/lib/typed_data_copy_from_js.dart" ] }, "_js_helper": { @@ -350,10 +352,10 @@ "uri": "internal/internal.dart", "patches": [ "_internal/wasm/lib/internal_patch.dart", - "_internal/wasm/lib/deferred_patch.dart", - "_internal/wasm/lib/internal_json_encode_patch.dart", - "_internal/wasm/lib/invoke_main_patch.dart", - "_internal/wasm/lib/print_patch.dart", + "_internal/wasm_standalone/lib/deferred_patch.dart", + "_internal/wasm_standalone/lib/internal_json_encode_patch.dart", + "_internal/wasm_standalone/lib/invoke_main_patch.dart", + "_internal/wasm_standalone/lib/print_patch.dart", "_internal/vm_shared/lib/check_valid_weak_target_patch.dart" ] }, @@ -365,7 +367,7 @@ "uri": "math/math.dart", "patches": [ "_internal/wasm/lib/math_patch.dart", - "_internal/wasm/lib/math_externs_patch.dart" + "_internal/wasm_standalone/lib/math_externs_patch.dart" ] } }
diff --git a/sdk/lib/libraries.yaml b/sdk/lib/libraries.yaml index 175a55b..e23e2f2 100644 --- a/sdk/lib/libraries.yaml +++ b/sdk/lib/libraries.yaml
@@ -156,6 +156,7 @@ uri: convert/convert.dart patches: - _internal/wasm/lib/convert_patch.dart + - _internal/wasm/lib/convert_js_patch.dart typed_data: uri: typed_data/typed_data.dart patches: @@ -261,11 +262,12 @@ convert: uri: convert/convert.dart patches: - - _internal/wasm/lib/convert_patch.dart # TODO(53884): Rewrite without JS interop in _internal/wasm_standalone + - _internal/wasm/lib/convert_patch.dart + - _internal/wasm_standalone/lib/convert_standalone_patch.dart developer: uri: developer/developer.dart patches: - - _internal/js_runtime/lib/developer_patch.dart # TODO(53884): Rewrite without JS interop in _internal/wasm_standalone + - _internal/wasm_standalone/lib/developer_patch.dart typed_data: uri: typed_data/typed_data.dart patches: @@ -284,7 +286,7 @@ _typed_data: uri: _internal/wasm/lib/typed_data.dart patches: - - _internal/wasm/lib/typed_data_copy_from_js.dart # TODO(53884): Rewrite without JS interop in _internal/wasm_standalone + - _internal/wasm_standalone/lib/typed_data_copy_from_js.dart _js_helper: # TODO(53884): Remove once other wasm_standalone patches no longer reference this uri: _internal/wasm/lib/js_helper.dart patches: @@ -293,10 +295,10 @@ uri: internal/internal.dart patches: - _internal/wasm/lib/internal_patch.dart - - _internal/wasm/lib/deferred_patch.dart # TODO(53884): Rewrite without JS interop in _internal/wasm_standalone - - _internal/wasm/lib/internal_json_encode_patch.dart # Needs to be migrated - - _internal/wasm/lib/invoke_main_patch.dart # TODO(53884): Rewrite without JS interop in _internal/wasm_standalone - - _internal/wasm/lib/print_patch.dart # TODO(53884): Rewrite without JS interop in _internal/wasm_standalone + - _internal/wasm_standalone/lib/deferred_patch.dart + - _internal/wasm_standalone/lib/internal_json_encode_patch.dart + - _internal/wasm_standalone/lib/invoke_main_patch.dart + - _internal/wasm_standalone/lib/print_patch.dart - _internal/vm_shared/lib/check_valid_weak_target_patch.dart _wasm: uri: _wasm/wasm_types.dart @@ -305,7 +307,7 @@ uri: math/math.dart patches: - _internal/wasm/lib/math_patch.dart - - _internal/wasm/lib/math_externs_patch.dart # TODO(53884): Rewrite without JS interop in _internal/wasm_standalone + - _internal/wasm_standalone/lib/math_externs_patch.dart wasm_js_common: include: