[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>
This commit is contained in:
Simon Binder
2026-05-06 06:41:53 -07:00
committed by dart-scoped@luci-project-accounts.iam.gserviceaccount.com
parent 69b84908a5
commit 9514710249
15 changed files with 738 additions and 107 deletions
+3 -3
View File
@@ -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))
)
+47 -2
View File
@@ -339,6 +339,7 @@ and `base64 -w 0`:
(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 @@ and `base64 -w 0`:
)
)
)
(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 @@ String dart2wasmHtml(
}
},
tryParseResultGetDouble: ({result}) => result,
doubleParseInfallible: (str) => +str,
i64ToString: (source, radix) => source.toString(radix),
f64ToExponential: (source) => source.toExponential(),
f64ToExponentialWithFractionDigits: (source, digits) => {
@@ -583,9 +590,47 @@ String dart2wasmHtml(
// 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 @@ ${standalone ? standaloneEmbedder : ''}
Promise.all(modules.map((m) => fetch(m).then((b) => handleWasmBytes(m, b)))),
});
dartMainRunner(() => {
appInstance.invokeMain();
$mainInvocation
});
}
@@ -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);
}
+75 -85
View File
@@ -7,14 +7,10 @@ import "dart:_compact_hash" show createMapFromStringKeyValueListUnsafe;
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 @@ dynamic _parseJson(
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 @@ class _NumberBuffer {
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 @@ mixin _ChunkedJsonParser<T> on _ChunkedJsonParserState {
* 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 @@ mixin _ChunkedJsonParser<T> on _ChunkedJsonParserState {
}
/**
* 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 @@ class _JSStringImplParser extends _ChunkedJsonParserState
}
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 @@ class _JSStringImplParser extends _ChunkedJsonParserState
}
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 @@ const int _codeUnitsCacheSize = 512;
@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 @@ JSStringImpl _internJSString(
}
}
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 @@ JSStringImpl _internJSStringFromAsciiSlice(
}
}
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 JsonDecoder {
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 @@ class _JsonStringDecoderSink extends StringConversionSinkBase {
_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 @@ class _JsonUtf8Parser extends _ChunkedJsonParserState
addStringSliceToString(decoder.convertChunked(chunk, start, end));
}
JSStringImpl _stringFromBytesOrCache(
String _stringFromBytesOrCache(
int start,
int end,
WasmArray<WasmI16> codeUnitsCache,
@@ -2004,13 +2004,9 @@ class _JsonUtf8Parser extends _ChunkedJsonParserState
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 @@ class _JsonUtf8Parser extends _ChunkedJsonParserState
}
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 @@ class _Utf8Decoder {
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 @@ class _Utf8Decoder {
);
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 @@ class _Utf8Decoder {
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 @@ class _Utf8Decoder {
_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 @@ class _Utf8Decoder {
_charOrIndex = char;
assert(j == size);
return JSStringImpl.fromCharCodeArray(_characterArray, 0, size);
return _stringFromCharCodeArray(_characterArray, 0, size);
}
String _decodeGeneral(
@@ -2891,10 +2884,7 @@ WasmArray<WasmI8> _makeI8ArrayFromWasmI8ArrayBase(
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, //
@@ -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);
}
@@ -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';
}
}
@@ -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,
);
}
@@ -186,6 +186,10 @@ external WasmExternRef? doubleTryParse(WasmExternRef? string);
@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 WasmExternRef timeZoneNameForClampedSeconds(WasmI64 secondsSinceEpoch);
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,
);
@@ -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),
);
@@ -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;
}
}
@@ -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);
}
@@ -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);
@@ -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;
}
+11 -9
View File
@@ -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"
]
}
}
+10 -8
View File
@@ -156,6 +156,7 @@ wasm:
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 @@ wasm_standalone:
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 @@ wasm_standalone:
_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 @@ wasm_standalone:
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 @@ wasm_standalone:
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: