diff --git a/pkg/dart2wasm/test/ir_tests/standalone.wat b/pkg/dart2wasm/test/ir_tests/standalone.wat index 534736e5530..2a64c9096d0 100644 --- a/pkg/dart2wasm/test/ir_tests/standalone.wat +++ b/pkg/dart2wasm/test/ir_tests/standalone.wat @@ -1,9 +1,9 @@ (module $module0 - (type $#Top (struct - (field $field0 i32))) - (type $JSExternWrapper (sub $#Top (struct + (type $JSExternWrapper (sub $Object (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 59326851da2..8eda559f8ab 100644 --- a/pkg/test_runner/lib/src/browser.dart +++ b/pkg/test_runner/lib/src/browser.dart @@ -424,6 +424,148 @@ String dart2wasmHtml(String title, String wasmPath, String mjsPath, const str = helperInstance.exports.stringFromCharCodeArray(chars, start, length); return str; }, + monotonicClockFrequency: () => 1_000_000, + monotonicClockTicks: () => BigInt(Math.round(performance.now() * 1000)), + weakRefCreate: (dartValue) => new WeakRef(dartValue), + weakRefGet: (weakRef) => weakRef.deref() ?? null, + expandoCreate: () => new WeakMap(), + expandoGet: (expando, target, _hash) => expando.get(target) ?? null, + expandoSet: (expando, target, _hash, value) => expando.set(target, value), + finalizerCreate: (callback, firstParameter) => { + return new FinalizationRegistry((heldValue) => { + callback(heldValue, firstParameter); + }); + }, + finalizerAttach: (finalizer, object, token, detachToken) => { + if (detachToken) { + finalizer.register(object, token, detachToken); + } else { + finalizer.register(object, token); + } + }, + finalizerDetach: (finalizer, detachToken) => finalizer.unregister(detachToken), + baseUri: () => globalThis.location.href, + isWindows: () => false, + stackTraceGetCurrent: () => new Error().stack, + stackTraceToString: (trace) => { + const stackString = trace.toString(); + const frames = stackString.split('\\n'); + // Format of stack traces is: + // 1. stackTraceGetCurrent (from this embedder object) + // 2. module0.StackTrace.current + // 3. The callsite we care about. + const drop = 1 + frames.findIndex((l) => l.indexOf('stackTraceGetCurrent') > 0); + return frames.slice(drop).join('\\n'); + }, + doubleTryParse: (source) => { + if (!/${r'^\s*[+-]?(?:Infinity|NaN|(?:\.\d+|\d+(?:\.\d*)?)(?:[eE][+-]?\d+)?)\s*$'}/.test(source)) { + const trimmed = source.trim(); + // parseFloat is more lenient than double.tryParse, see wasm/lib/double_patch.dart for details. + if (!(trimmed == 'NaN' || trimmed == '+NaN' || trimmed == '-NaN')) { + return null; + } + return { result: NaN }; + } else { + return { result: parseFloat(source) }; + } + }, + tryParseResultGetDouble: ({result}) => result, + i64ToString: (source, radix) => source.toString(radix), + f64ToExponential: (source) => source.toExponential(), + f64ToExponentialWithFractionDigits: (source, digits) => { + return source.toExponential(digits); + }, + f64ToPrecision: (source, digits) => source.toPrecision(digits), + f64ToFixed: (source, digits) => source.toFixed(digits), + f64ToString: (source) => { + if (Object.is(source, -0)) return '-0.0'; + if (Number.isNaN(source)) return 'NaN'; + if (source == Number.NEGATIVE_INFINITY) return '-Infinity'; + if (source == Number.POSITIVE_INFINITY) return 'Infinity'; + + let formatted = source.toString(); + if (source % 1.0 == 0 && formatted.indexOf('e') == -1) { + formatted += '.0'; + } + return formatted; + }, + stringBufferCreate: () => ({ contents: '' }), + stringBufferWriteString: (buffer, append) => { + buffer.contents += append; + }, + stringBufferWriteCharCode: (buffer, code) => { + buffer.contents += String.fromCodePoint(code); + }, + stringBufferClear: (buffer) => { + buffer.contents = '' + }, + stringBufferLength: (buffer) => buffer.contents.length, + stringBufferToString: ({contents}) => contents, + regexpCreateOrFailWithString: (pattern, multiLine, caseSensitive, unicode, dotAll) => { + let flags = ''; + if (multiLine) flags += 'm'; + if (!caseSensitive) flags += 'i'; + if (unicode) flags += 'u'; + if (dotAll) flags += 's'; + + try { + // Prepare two regular expressions, one for regular matches and one + // for matchAsPrefix. + return { + regular: new RegExp(pattern, flags + 'g'), + asPrefix: new RegExp(pattern, flags + 'y'), + }; + } catch (e) { + return String(e); + } + }, + regexpIsRegexp: (source) => typeof(source) !== 'string', + regexpEscape: (source) => { + // Note: We can't use RegExp.escape here, it escapes too much and we + // have tests expecting that e.g. \t isn't escaped. + if (/${r'[[\]{}()*+?.\\^$|]'}/.test(source)) { + return source.replace(/${r'[[\]{}()*+?.\\^$|]'}/g, "${r'\\$&'}"); + } else { + return source; + } + }, + regexpMatch: (regexp, string, start, asPrefix) => { + const regex = asPrefix ? regexp.asPrefix : regexp.regular; + regex.lastIndex = start; + const match = regex.exec(string); + if (match) { + return { + start: match.index, + end: match.index + match[0].length, + groupNames: match.groups ? Object.keys(match.groups) : [], + groups: match, + }; + } else { + return null; + } + }, + regexpMatchGetStart: (match) => match.start, + regexpMatchGetEnd: (match) => match.end, + regexpMatchGetGroupCount: (match) => match.groups.length - 1, + regexpMatchGetGroup: (match, index) => match.groups[index] ?? null, + regexpMatchGetNamedGroups: (match) => match.groupNames.length, + regexpMatchGetGroupName: (match, index) => match.groupNames[index], + regexpMatchGetGroupByName: (match, index) => match.groups.groups[match.groupNames[index]] ?? null, + timeZoneNameForClampedSeconds: (secondsSinceEpoch) => { + const date = new Date(Number(secondsSinceEpoch * 1000n)); + const match = /\\((.*)\\)/.exec(date.toString()); + if (match == null) { + // This should never happen on any recent browser. + return ''; + } + return match[1]; + }, + timeZoneOffsetInSecondsForClampedSeconds: (secondsSinceEpoch) => { + const date = new Date(Number(secondsSinceEpoch * 1000n)); + // This needs to be negated because Dart wants the difference between + // local time and UTC. + return -date.getTimezoneOffset() * 60; + }, }; """; final additionalImports = standalone ? '{ dart: dartEmbedder }' : '{}'; diff --git a/sdk/lib/_internal/vm/lib/internal_patch.dart b/sdk/lib/_internal/vm/lib/internal_patch.dart index 3454107fc96..94f733413db 100644 --- a/sdk/lib/_internal/vm/lib/internal_patch.dart +++ b/sdk/lib/_internal/vm/lib/internal_patch.dart @@ -225,23 +225,6 @@ class LateError { } } -void checkValidWeakTarget(object, name) { - if ((object == null) || - (object is bool) || - (object is num) || - (object is String) || - (object is Record) || - (object is Pointer) || - (object is Struct) || - (object is Union)) { - throw ArgumentError.value( - object, - name, - "Cannot be a string, number, boolean, record, null, Pointer, Struct or Union", - ); - } -} - @pragma("vm:entry-point") class FinalizerBase { /// The list of finalizers of this isolate. diff --git a/sdk/lib/_internal/vm/lib/uri_patch.dart b/sdk/lib/_internal/vm/lib/uri_patch.dart index fdac9439990..c3ee351d137 100644 --- a/sdk/lib/_internal/vm/lib/uri_patch.dart +++ b/sdk/lib/_internal/vm/lib/uri_patch.dart @@ -31,60 +31,4 @@ class _Uri { @patch static bool get _isWindows => _isWindowsCached; - - @patch - static String _uriEncode( - int canonicalMask, - String text, - Encoding encoding, - bool spaceToPlus, - ) { - // First check if the text will be changed by encoding. - int i = 0; - if (identical(encoding, utf8) || - identical(encoding, latin1) || - identical(encoding, ascii)) { - // Encoding is compatible with the original string. - // Find first character that needs encoding. - for (; i < text.length; i++) { - var char = text.codeUnitAt(i); - if (char >= 128 || _charTables.codeUnitAt(char) & canonicalMask == 0) { - break; - } - } - } - if (i == text.length) return text; - - // Encode the string into bytes then generate an ASCII only string - // by percent encoding selected bytes. - StringBuffer result = StringBuffer(); - for (int j = 0; j < i; j++) { - result.writeCharCode(text.codeUnitAt(j)); - } - - // TODO(lrn): Is there a way to only encode from index i and forwards. - var bytes = encoding.encode(text); - for (; i < bytes.length; i++) { - int byte = bytes[i]; - if (byte < 128 && ((_charTables.codeUnitAt(byte) & canonicalMask) != 0)) { - result.writeCharCode(byte); - } else if (spaceToPlus && byte == _SPACE) { - result.writeCharCode(_PLUS); - } else { - const String hexDigits = '0123456789ABCDEF'; - result - ..writeCharCode(_PERCENT) - ..writeCharCode(hexDigits.codeUnitAt(byte >> 4)) - ..writeCharCode(hexDigits.codeUnitAt(byte & 0x0f)); - } - } - return result.toString(); - } - - @patch - static String _makeQueryFromParameters( - Map*/> queryParameters, - ) { - return _makeQueryFromParametersDefault(queryParameters); - } } diff --git a/sdk/lib/_internal/vm_shared/lib/check_valid_weak_target_patch.dart b/sdk/lib/_internal/vm_shared/lib/check_valid_weak_target_patch.dart new file mode 100644 index 00000000000..da67c43a9cf --- /dev/null +++ b/sdk/lib/_internal/vm_shared/lib/check_valid_weak_target_patch.dart @@ -0,0 +1,22 @@ +// 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:ffi' show Pointer, Struct, Union; + +void checkValidWeakTarget(object, name) { + if ((object == null) || + (object is bool) || + (object is num) || + (object is String) || + (object is Record) || + (object is Pointer) || + (object is Struct) || + (object is Union)) { + throw ArgumentError.value( + object, + name, + "Cannot be a string, number, boolean, record, null, Pointer, Struct or Union", + ); + } +} diff --git a/sdk/lib/_internal/vm_shared/lib/uri_encode_patch.dart b/sdk/lib/_internal/vm_shared/lib/uri_encode_patch.dart new file mode 100644 index 00000000000..0d55958df5b --- /dev/null +++ b/sdk/lib/_internal/vm_shared/lib/uri_encode_patch.dart @@ -0,0 +1,65 @@ +// 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; +import 'dart:convert' show Encoding, utf8, latin1, ascii; + +@patch +class _Uri { + @patch + static String _uriEncode( + int canonicalMask, + String text, + Encoding encoding, + bool spaceToPlus, + ) { + // First check if the text will be changed by encoding. + int i = 0; + if (identical(encoding, utf8) || + identical(encoding, latin1) || + identical(encoding, ascii)) { + // Encoding is compatible with the original string. + // Find first character that needs encoding. + for (; i < text.length; i++) { + var char = text.codeUnitAt(i); + if (char >= 128 || _charTables.codeUnitAt(char) & canonicalMask == 0) { + break; + } + } + } + if (i == text.length) return text; + + // Encode the string into bytes then generate an ASCII only string + // by percent encoding selected bytes. + StringBuffer result = StringBuffer(); + for (int j = 0; j < i; j++) { + result.writeCharCode(text.codeUnitAt(j)); + } + + // TODO(lrn): Is there a way to only encode from index i and forwards. + var bytes = encoding.encode(text); + for (; i < bytes.length; i++) { + int byte = bytes[i]; + if (byte < 128 && ((_charTables.codeUnitAt(byte) & canonicalMask) != 0)) { + result.writeCharCode(byte); + } else if (spaceToPlus && byte == _SPACE) { + result.writeCharCode(_PLUS); + } else { + const String hexDigits = '0123456789ABCDEF'; + result + ..writeCharCode(_PERCENT) + ..writeCharCode(hexDigits.codeUnitAt(byte >> 4)) + ..writeCharCode(hexDigits.codeUnitAt(byte & 0x0f)); + } + } + return result.toString(); + } + + @patch + static String _makeQueryFromParameters( + Map*/> queryParameters, + ) { + return _makeQueryFromParametersDefault(queryParameters); + } +} diff --git a/sdk/lib/_internal/wasm/lib/boxed_double.dart b/sdk/lib/_internal/wasm/lib/boxed_double.dart index 728fdd03faa..16549253a22 100644 --- a/sdk/lib/_internal/wasm/lib/boxed_double.dart +++ b/sdk/lib/_internal/wasm/lib/boxed_double.dart @@ -313,23 +313,6 @@ final class BoxedDouble implements double { return this; } - static const int CACHE_SIZE_LOG2 = 3; - static const int CACHE_LENGTH = 1 << (CACHE_SIZE_LOG2 + 1); - static const int CACHE_MASK = CACHE_LENGTH - 1; - // Each cached double value, represented as it's 64-bits. - @pragma("wasm:initialize-at-startup") - static final WasmArray _cacheKeys = WasmArray.filled( - CACHE_LENGTH, - doubleToIntBits(1.0), - ); - // The toString() of the double value with same index in [_cacheKeys]. - @pragma("wasm:initialize-at-startup") - static final WasmArray _cacheValues = WasmArray.filled( - CACHE_LENGTH, - '1.0', - ); - static int _cacheEvictIndex = 0; - external String toString(); external toStringAsFixed(int fractionDigits); diff --git a/sdk/lib/_internal/wasm/lib/boxed_double_patch.dart b/sdk/lib/_internal/wasm/lib/boxed_double_patch.dart index 6d17641f201..1269ba44fa2 100644 --- a/sdk/lib/_internal/wasm/lib/boxed_double_patch.dart +++ b/sdk/lib/_internal/wasm/lib/boxed_double_patch.dart @@ -8,6 +8,23 @@ import 'dart:_string'; @patch class BoxedDouble { + static const int CACHE_SIZE_LOG2 = 3; + static const int CACHE_LENGTH = 1 << (CACHE_SIZE_LOG2 + 1); + static const int CACHE_MASK = CACHE_LENGTH - 1; + // Each cached double value, represented as it's 64-bits. + @pragma("wasm:initialize-at-startup") + static final WasmArray _cacheKeys = WasmArray.filled( + CACHE_LENGTH, + doubleToIntBits(1.0), + ); + // The toString() of the double value with same index in [_cacheKeys]. + @pragma("wasm:initialize-at-startup") + static final WasmArray _cacheValues = WasmArray.filled( + CACHE_LENGTH, + '1.0', + ); + static int _cacheEvictIndex = 0; + @patch String toString() { final int bits = doubleToIntBits(value); diff --git a/sdk/lib/_internal/wasm/lib/weak_patch.dart b/sdk/lib/_internal/wasm/lib/weak_patch.dart index d151a4cce6c..2d6e1ae1205 100644 --- a/sdk/lib/_internal/wasm/lib/weak_patch.dart +++ b/sdk/lib/_internal/wasm/lib/weak_patch.dart @@ -2,28 +2,11 @@ // 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, unsafeCast; +import 'dart:_internal' show patch, unsafeCast, checkValidWeakTarget; import 'dart:_js_helper' show isJSUndefined, JS; import 'dart:_wasm'; import 'dart:js_interop' hide JS; import 'dart:js_interop' as js_interop; -import 'dart:ffi' show Pointer, Struct, Union; - -void _checkValidWeakTarget(Object object) { - if ((object is bool) || - (object is num) || - (object is String) || - (object is Record) || - (object is Pointer) || - (object is Struct) || - (object is Union)) { - throw ArgumentError.value( - object, - "A string, number, boolean, record, Pointer, Struct or Union " - "can't be a weak target", - ); - } -} @patch class Expando { @@ -36,7 +19,7 @@ class Expando { @patch T? operator [](Object object) { - _checkValidWeakTarget(object); + checkValidWeakTarget(object, 'object'); final result = JS( "(map, o) => map.get(o)", _jsWeakMap, @@ -49,7 +32,7 @@ class Expando { @patch void operator []=(Object object, T? value) { - _checkValidWeakTarget(object); + checkValidWeakTarget(object, 'object'); JS( "(map, o, v) => map.set(o, v)", _jsWeakMap, @@ -74,7 +57,7 @@ class WeakReference { @patch factory WeakReference(T target) { if (_supportsWeakRef) { - _checkValidWeakTarget(target); + checkValidWeakTarget(target, 'target'); return _WeakReferenceWrapper(target); } // The polyfill does not validate [target]. This lets the tests distinguish @@ -153,9 +136,9 @@ class _FinalizationRegistryWrapper implements Finalizer { ); void attach(Object value, T peer, {Object? detach}) { - _checkValidWeakTarget(value); + checkValidWeakTarget(value, 'value'); if (detach != null) { - _checkValidWeakTarget(detach); + checkValidWeakTarget(detach, 'detach'); _jsFinalizationRegistry.registerWithDetach( value.toExternalReference, peer.toExternalReference, @@ -170,7 +153,7 @@ class _FinalizationRegistryWrapper implements Finalizer { } void detach(Object detach) { - _checkValidWeakTarget(detach); + checkValidWeakTarget(detach, 'detach'); _jsFinalizationRegistry.unregister(detach.toExternalReference); } } diff --git a/sdk/lib/_internal/wasm_standalone/lib/boxed_double_patch.dart b/sdk/lib/_internal/wasm_standalone/lib/boxed_double_patch.dart new file mode 100644 index 00000000000..940aee73390 --- /dev/null +++ b/sdk/lib/_internal/wasm_standalone/lib/boxed_double_patch.dart @@ -0,0 +1,122 @@ +// 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 doubleToIntBits, intBitsToDouble, patch; +import 'dart:_string'; +import 'dart:_wasm'; + +@patch +class BoxedDouble { + @patch + String toString() { + return JSStringImpl.fromRefUnchecked( + embedder.f64ToString(WasmF64.fromDouble(value)), + ); + } + + @patch + String toStringAsFixed(int fractionDigits) { + // See ECMAScript-262, 15.7.4.5 for details. + + // Step 2. + // 0 <= fractionDigits <= 20 + RangeErrorUtils.checkValueBetweenZeroAndPositiveMax( + fractionDigits, + 20, + "fractionDigits", + ); + + // Step 3. + double x = this; + + // Step 4. + if (isNaN) return "NaN"; + if (this == double.infinity) return "Infinity"; + if (this == -double.infinity) return "-Infinity"; + + // Step 5 and 6 skipped. Will be dealt with by native function. + + // Step 7. + if (x >= 1e21 || x <= -1e21) { + return x.toString(); + } + + String result = _toStringAsFixed(fractionDigits); + if (this == 0 && isNegative) return '-$result'; + return result; + } + + String _toStringAsFixed(int fractionDigits) => JSStringImpl.fromRefUnchecked( + embedder.f64ToFixed( + WasmF64.fromDouble(this), + WasmI32.fromInt(fractionDigits), + ), + ); + + @patch + String toStringAsExponential([int? fractionDigits]) { + // See ECMAScript-262, 15.7.4.6 for details. + + // The EcmaScript specification checks for NaN and Infinity before looking + // at the fractionDigits. In Dart we are consistent with toStringAsFixed and + // look at the fractionDigits first. + + // Step 7. + if (fractionDigits != null) { + // 0 <= fractionDigits <= 20 + RangeErrorUtils.checkValueBetweenZeroAndPositiveMax( + fractionDigits, + 20, + "fractionDigits", + ); + } + + if (isNaN) return "NaN"; + if (this == double.infinity) return "Infinity"; + if (this == -double.infinity) return "-Infinity"; + + String result = _toStringAsExponential(fractionDigits); + if (this == 0 && isNegative) return '-$result'; + return result; + } + + String _toStringAsExponential(int? fractionDigits) => + JSStringImpl.fromRefUnchecked( + fractionDigits == null + ? embedder.f64ToExponential(WasmF64.fromDouble(this)) + : embedder.f64ToExponentialWithFractionDigits( + WasmF64.fromDouble(this), + WasmI32.fromInt(fractionDigits), + ), + ); + + @patch + String toStringAsPrecision(int precision) { + // See ECMAScript-262, 15.7.4.7 for details. + + // The EcmaScript specification checks for NaN and Infinity before looking + // at the fractionDigits. In Dart we are consistent with toStringAsFixed and + // look at the fractionDigits first. + + // Step 8. + RangeErrorUtils.checkValueInInterval(precision, 1, 21, "precision"); + + if (isNaN) return "NaN"; + if (this == double.infinity) return "Infinity"; + if (this == -double.infinity) return "-Infinity"; + + String result = _toStringAsPrecision(precision); + if (this == 0 && isNegative) return '-$result'; + return result; + } + + String _toStringAsPrecision(int fractionDigits) => + JSStringImpl.fromRefUnchecked( + embedder.f64ToPrecision( + WasmF64.fromDouble(this), + WasmI32.fromInt(fractionDigits), + ), + ); +} diff --git a/sdk/lib/_internal/wasm_standalone/lib/boxed_int_patch.dart b/sdk/lib/_internal/wasm_standalone/lib/boxed_int_patch.dart new file mode 100644 index 00000000000..c41b841d5ea --- /dev/null +++ b/sdk/lib/_internal/wasm_standalone/lib/boxed_int_patch.dart @@ -0,0 +1,27 @@ +// 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' show i64ToString; +import 'dart:_error_utils'; +import 'dart:_internal'; +import 'dart:_string'; +import 'dart:_wasm'; + +@patch +class BoxedInt { + @patch + String toRadixString(int radix) { + RangeErrorUtils.checkValueInInterval(radix, 2, 36, "radix"); + return _intToString(this, radix); + } + + @patch + String toString() => _intToString(this, 10); +} + +String _intToString(int value, int radix) { + return JSStringImpl.fromRefUnchecked( + i64ToString(WasmI64.fromInt(value), WasmI32.fromInt(radix)), + ); +} diff --git a/sdk/lib/_internal/wasm_standalone/lib/date_patch_patch.dart b/sdk/lib/_internal/wasm_standalone/lib/date_patch_patch.dart new file mode 100644 index 00000000000..0612bed52bc --- /dev/null +++ b/sdk/lib/_internal/wasm_standalone/lib/date_patch_patch.dart @@ -0,0 +1,29 @@ +// 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'; +import "dart:_internal" show patch; +import 'dart:_string'; +import 'dart:_wasm'; + +@patch +class DateTime { + @patch + static int _getCurrentMicros() => currentTimeMicros().toInt(); + + @patch + static String _timeZoneNameForClampedSeconds(int secondsSinceEpoch) => + JSStringImpl.fromRefUnchecked( + timeZoneNameForClampedSeconds(WasmI64.fromInt(secondsSinceEpoch)), + ); + + // In Dart, the offset is the difference between local time and UTC, + // while in JS, the offset is the difference between UTC and local time. + // As a result, the signs are opposite, so we negate the value returned by JS. + @patch + static int _timeZoneOffsetInSecondsForClampedSeconds(int secondsSinceEpoch) => + timeZoneOffsetInSecondsForClampedSeconds( + WasmI64.fromInt(secondsSinceEpoch), + ).toIntSigned(); +} diff --git a/sdk/lib/_internal/wasm_standalone/lib/double_patch.dart b/sdk/lib/_internal/wasm_standalone/lib/double_patch.dart new file mode 100644 index 00000000000..e9d8274abc8 --- /dev/null +++ b/sdk/lib/_internal/wasm_standalone/lib/double_patch.dart @@ -0,0 +1,32 @@ +// 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'; +import 'dart:_internal' show patch; +import 'dart:_js_helper'; +import 'dart:_wasm'; + +@patch +class double { + @patch + static double parse(String source) { + double? result = tryParse(source); + if (result == null) { + throw FormatException('Invalid double $source'); + } + return result; + } + + @patch + static double? tryParse(String source) { + final parseResult = doubleTryParse( + jsStringFromDartString(source).wrappedExternRef, + ); + if (parseResult.isNull) { + return null; + } else { + return tryParseResultGetDouble(parseResult).toDouble(); + } + } +} diff --git a/sdk/lib/_internal/wasm_standalone/lib/embedder.dart b/sdk/lib/_internal/wasm_standalone/lib/embedder.dart index f756642a6e4..b355af71bd3 100644 --- a/sdk/lib/_internal/wasm_standalone/lib/embedder.dart +++ b/sdk/lib/_internal/wasm_standalone/lib/embedder.dart @@ -73,3 +73,263 @@ external WasmExternRef stringFromAsciiBytes( WasmI32 start, WasmI32 length, ); + +/// Get the frequency of ticks reported by [monotonicClockTicks] in Hz. +/// +/// Currently, the only supported values are 1kHz and 1MHz. Attempting to use +/// a stopwatch in Dart will fail if the function returns an unsupported value. +/// +/// This function must return the same value every time it is called. +@pragma("wasm:import", "dart.monotonicClockFrequency") +external WasmI32 monotonicClockFrequency(); + +/// An integer incrementing with [monotonicClockFrequency]. +@pragma("wasm:import", "dart.monotonicClockTicks") +external WasmI64 monotonicClockTicks(); + +/// Creates a weak reference wrapping [originalValue]. +/// +/// The SDK verifies that [originalValue] is a valid target (not a number, +/// boolean, string, record or FFI type) before calling this. +@pragma("wasm:import", "dart.weakRefCreate") +external WasmExternRef weakRefCreate(WasmAnyRef originalValue); + +/// Returns the value wrapped in [weakRefCreate], or null. +@pragma("wasm:import", "dart.weakRefGet") +external WasmAnyRef? weakRefGet(WasmExternRef? weakReference); + +/// Creates a new [Expando]. +@pragma("wasm:import", "dart.expandoCreate") +external WasmExternRef expandoCreate(); + +/// Lookup a value stored on [target] with [expandoSet]. +/// +/// The SDK verifies that [target] is a valid target before calling this. +@pragma("wasm:import", "dart.expandoGet") +external WasmAnyRef? expandoGet( + WasmExternRef? expando, + WasmAnyRef target, + WasmI64 targetIdentityHashCode, +); + +/// Associates the [value] with the [target] object in the expando. +/// +/// The SDK verifies that [target] is a valid target before calling this. +@pragma("wasm:import", "dart.expandoSet") +external WasmVoid expandoSet( + WasmExternRef? expando, + WasmAnyRef target, + WasmI64 targetIdentityHashCode, + WasmAnyRef? value, +); + +/// Creates a native finalizer that may invoke the [callback] with the +/// [firstParameter] and a second token when a registered object becomes +/// unreachable. +@pragma("wasm:import", "dart.finalizerCreate") +external WasmExternRef finalizerCreate( + WasmFunction callback, + WasmAnyRef firstParameter, +); + +/// Attaches an object to a finalizer. +/// +/// After [object] becomes unreachable, the `callback` passed to +/// [finalizerCreate] may be invoked with [token] as a second parameter. +/// +/// If [detachToken] is non-null, it can later be passed to [finalizerDetach] to +/// remove the [object] from the finalizer. +@pragma("wasm:import", "dart.finalizerAttach") +external WasmVoid finalizerAttach( + WasmExternRef? finalizer, + WasmAnyRef object, + WasmAnyRef? token, + WasmAnyRef? detachToken, +); + +@pragma("wasm:import", "dart.finalizerDetach") +external WasmVoid finalizerDetach( + WasmExternRef? finalizer, + WasmAnyRef detachToken, +); + +/// Returns the string value for [Uri.base], or null if no base URI exists. +@pragma("wasm:import", "dart.baseUri") +external WasmExternRef? baseUri(); + +/// Returns `1` if running on Windows, `0` otherwise. +@pragma("wasm:import", "dart.isWindows") +external WasmI32 isWindows(); + +/// Creates a stack trace object from the current call stack. +/// +/// This backs [StackTrace.current], so implementations should hide calls to +/// this extern from the created stack trace. +@pragma("wasm:import", "dart.stackTraceGetCurrent") +external WasmExternRef stackTraceGetCurrent(); + +/// Renders a stack trace returned by [stackTraceGetCurrent] as a string. +@pragma("wasm:import", "dart.stackTraceToString") +external WasmExternRef stackTraceToString(WasmExternRef? trace); + +/// Attempts to parse a string as a double, following semantics described in +/// [double.parse]. +/// +/// If the string can't be parsed as a double, return null. Otherwise, returns a +/// structure that can be used by [tryParseResultGetDouble] to extract the +/// parsed double. +@pragma("wasm:import", "dart.doubleTryParse") +external WasmExternRef? doubleTryParse(WasmExternRef? string); + +/// Extracts the double parsed from [doubleTryParse] returning a non-nullable +/// value. +@pragma("wasm:import", "dart.tryParseResultGetDouble") +external WasmF64 tryParseResultGetDouble(WasmExternRef? parseResult); + +/// The implementation of [int.toRadixString]. +/// +/// The SDK will only call this with radix values between 2 and 36 (inclusive). +@pragma("wasm:import", "dart.i64ToString") +external WasmExternRef i64ToString(WasmI64 value, WasmI32 radix); + +/// This and [f64ToExponentialWithFractionDigits] must behave exactly as +/// `Number.prototype.toExponential` in JavaScript. +@pragma("wasm:import", "dart.f64ToExponential") +external WasmExternRef f64ToExponential(WasmF64 value); + +@pragma("wasm:import", "dart.f64ToExponentialWithFractionDigits") +external WasmExternRef f64ToExponentialWithFractionDigits( + WasmF64 value, + WasmI32 fractionDigits, +); + +/// Must behave exactly as `Number.prototype.toPrecision` in JavaScript. +@pragma("wasm:import", "dart.f64ToPrecision") +external WasmExternRef f64ToPrecision(WasmF64 value, WasmI32 fractionDigits); + +/// Must behave exactly as `Number.prototype.toFixed` in JavaScript. +@pragma("wasm:import", "dart.f64ToFixed") +external WasmExternRef f64ToFixed(WasmF64 value, WasmI32 fractionDigits); + +/// Implements [double.toString]. +@pragma("wasm:import", "dart.f64ToString") +external WasmExternRef f64ToString(WasmF64 value); + +/// Creates a string buffer object. +@pragma("wasm:import", "dart.stringBufferCreate") +external WasmExternRef stringBufferCreate(); + +/// Appends a string to a string buffer. +@pragma("wasm:import", "dart.stringBufferWriteString") +external WasmVoid stringBufferWriteString( + WasmExternRef? buffer, + WasmExternRef? string, +); + +/// Appends a string containing the character with the [code] point to a string +/// buffer. +@pragma("wasm:import", "dart.stringBufferWriteCharCode") +external WasmVoid stringBufferWriteCharCode( + WasmExternRef? buffer, + WasmI32 code, +); + +/// Clears the contents of a string buffer. +@pragma("wasm:import", "dart.stringBufferClear") +external WasmVoid stringBufferClear(WasmExternRef? buffer); + +/// The current length of a string in a string buffer. +@pragma("wasm:import", "dart.stringBufferLength") +external WasmI32 stringBufferLength(WasmExternRef? buffer); + +/// Turn a string buffer into a string. +@pragma("wasm:import", "dart.stringBufferToString") +external WasmExternRef stringBufferToString(WasmExternRef? buffer); + +/// Attempts to parse the `string` as a regular expression with the given +/// options. +/// +/// Returns a regular expression object if that succeeds, or an error message as +/// a string otherwise. +@pragma("wasm:import", "dart.regexpCreateOrFailWithString") +external WasmExternRef regexpCreateOrFailWithString( + WasmExternRef? string, + WasmI32 multiLine, + WasmI32 caseSensitive, + WasmI32 unicode, + WasmI32 dotAll, +); + +/// Called with the return value of [regexpCreateOrFailWithString], returns +/// whether [ref] is a regular expression object. +/// +/// If this returns `0`, the return value is interpreted as an error message +/// string instead. +@pragma("wasm:import", "dart.regexpIsRegexp") +external WasmI32 regexpIsRegexp(WasmExternRef? ref); + +/// Implementation of [RegExp.escape]. +@pragma("wasm:import", "dart.regexpEscape") +external WasmExternRef regexpEscape(WasmExternRef? string); + +/// If [asPrefix] is `0`, return the first match of [string] for [regexp] at or +/// after [start] code units. +/// +/// If [asPrefix] is `1`, only return the match if it starts exactly at [start]. +@pragma("wasm:import", "dart.regexpMatch") +external WasmExternRef? regexpMatch( + WasmExternRef? regexp, + WasmExternRef? string, + WasmI32 start, + WasmI32 asPrefix, +); + +/// Implementation of [Match.start] for a [regexpMatch]. +@pragma("wasm:import", "dart.regexpMatchGetStart") +external WasmI32 regexpMatchGetStart(WasmExternRef? match); + +/// Implementation of [Match.end] for a [regexpMatch]. +@pragma("wasm:import", "dart.regexpMatchGetEnd") +external WasmI32 regexpMatchGetEnd(WasmExternRef? match); + +/// Implementation of [Match.groupCount] for a [regexpMatch]. +@pragma("wasm:import", "dart.regexpMatchGetGroupCount") +external WasmI32 regexpMatchGetGroupCount(WasmExternRef? match); + +/// Implementation of [Match.group] for a [regexpMatch]. +/// +/// This is only called with an index between 0 and [regexpMatchGetGroupCount] +/// (inclusive). +@pragma("wasm:import", "dart.regexpMatchGetGroup") +external WasmExternRef? regexpMatchGetGroup( + WasmExternRef? match, + WasmI32 index, +); + +/// The amount of named groups in a regexp match. +@pragma("wasm:import", "dart.regexpMatchGetNamedGroups") +external WasmI32 regexpMatchGetNamedGroups(WasmExternRef? match); + +/// For an index between 0 and [regexpMatchGetNamedGroups] (exclusive), returns +/// the name of the regexp group. +@pragma("wasm:import", "dart.regexpMatchGetGroupName") +external WasmExternRef regexpMatchGetGroupName( + WasmExternRef? match, + WasmI32 index, +); + +/// For an index of [regexpMatchGetNamedGroups], returns the match of the group +/// named `regexpMatchGetGroupName(match, nameIndex)`. +@pragma("wasm:import", "dart.regexpMatchGetGroupByName") +external WasmExternRef? regexpMatchGetGroupByName( + WasmExternRef? match, + WasmI32 nameIndex, +); + +@pragma("wasm:import", "dart.timeZoneNameForClampedSeconds") +external WasmExternRef timeZoneNameForClampedSeconds(WasmI64 secondsSinceEpoch); + +@pragma("wasm:import", "dart.timeZoneOffsetInSecondsForClampedSeconds") +external WasmI32 timeZoneOffsetInSecondsForClampedSeconds( + WasmI64 secondsSinceEpoch, +); diff --git a/sdk/lib/_internal/wasm_standalone/lib/regexp_patch.dart b/sdk/lib/_internal/wasm_standalone/lib/regexp_patch.dart new file mode 100644 index 00000000000..f4c66508067 --- /dev/null +++ b/sdk/lib/_internal/wasm_standalone/lib/regexp_patch.dart @@ -0,0 +1,238 @@ +// 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'; +import 'dart:_error_utils'; +import 'dart:_internal' show patch; +import 'dart:_js_helper'; +import 'dart:_string'; +import 'dart:_wasm'; + +@patch +class RegExp { + @patch + factory RegExp( + String source, { + bool multiLine = false, + bool caseSensitive = true, + bool unicode = false, + bool dotAll = false, + }) { + return _EmbedderRegExp(source, multiLine, caseSensitive, unicode, dotAll); + } + + @patch + static String escape(String text) { + return JSStringImpl.fromRefUnchecked( + regexpEscape(jsStringFromDartString(text).wrappedExternRef), + ); + } +} + +final class _EmbedderRegExp implements RegExp { + WasmExternRef? _regexp = WasmExternRef.nullRef; + + @override + final String pattern; + @override + final bool isMultiLine; + @override + final bool isCaseSensitive; + @override + final bool isUnicode; + @override + final bool isDotAll; + + _EmbedderRegExp( + this.pattern, + this.isMultiLine, + this.isCaseSensitive, + this.isUnicode, + this.isDotAll, + ) { + final compiled = regexpCreateOrFailWithString( + jsStringFromDartString(pattern).wrappedExternRef, + WasmI32.fromBool(isMultiLine), + WasmI32.fromBool(isCaseSensitive), + WasmI32.fromBool(isUnicode), + WasmI32.fromBool(isDotAll), + ); + if (!regexpIsRegexp(compiled).toBool()) { + // The returned value is the stringified JavaScript exception. Turn it + // into a Dart exception. + final errorMessage = JSStringImpl.fromRefUnchecked(compiled); + throw FormatException('Illegal RegExp pattern ($errorMessage)', pattern); + } + + this._regexp = compiled; + } + + @override + String toString() { + final buffer = StringBuffer('RegExp/'); + buffer.write(pattern); + buffer.write('/'); + + if (isMultiLine) buffer.write('m'); + if (!isCaseSensitive) buffer.write('i'); + if (isUnicode) buffer.write('u'); + if (isDotAll) buffer.write('s'); + return buffer.toString(); + } + + @override + Iterable allMatches(String input, [int start = 0]) { + RangeErrorUtils.checkValueBetweenZeroAndPositiveMax(start, input.length); + return Iterable.withIterator( + () => _EmbedderMatchesIterator(this, input, start), + ); + } + + @override + RegExpMatch? firstMatch(String input) { + return _search(input, 0, false); + } + + @override + bool hasMatch(String input) { + return firstMatch(input) != null; + } + + @override + Match? matchAsPrefix(String string, [int start = 0]) { + return _search(string, start, true); + } + + _EmbedderMatch? _search(String string, int start, bool exactStartIndex) { + RangeErrorUtils.checkValueBetweenZeroAndPositiveMax(start, string.length); + final match = regexpMatch( + _regexp, + jsStringFromDartString(string).wrappedExternRef, + WasmI32.fromInt(start), + WasmI32.fromBool(exactStartIndex), + ); + if (match.isNull) { + return null; + } + + return _EmbedderMatch(this, string).._match = match; + } + + @override + String? stringMatch(String input) { + var match = firstMatch(input); + if (match != null) return match[0]; + return null; + } +} + +final class _EmbedderMatch implements RegExpMatch { + @override + final _EmbedderRegExp pattern; + @override + final String input; + + WasmExternRef? _match = WasmExternRef.nullRef; + + _EmbedderMatch(this.pattern, this.input); + + @override + String? operator [](int group) { + return this.group(group); + } + + @override + int get start => regexpMatchGetStart(_match).toIntUnsigned(); + + @override + int get end => regexpMatchGetEnd(_match).toIntUnsigned(); + + @override + int get groupCount => regexpMatchGetGroupCount(_match).toIntUnsigned(); + + @override + String? group(int group) { + IndexErrorUtils.checkIndex(group, groupCount + 1); + final contents = regexpMatchGetGroup(_match, WasmI32.fromInt(group)); + return contents.isNull ? null : JSStringImpl.fromRefUnchecked(contents); + } + + @override + List groups(List groupIndices) { + return [for (final index in groupIndices) group(index)]; + } + + @override + late final List groupNames = List.generate( + regexpMatchGetNamedGroups(_match).toIntUnsigned(), + (i) { + return JSStringImpl.fromRefUnchecked( + regexpMatchGetGroupName(_match, WasmI32.fromInt(i)), + ); + }, + ); + + @override + String? namedGroup(String name) { + final groupIndex = groupNames.indexOf(name); + if (groupIndex < 0) { + throw ArgumentError.value(name, "name", "Not a capture group name"); + } + + final contents = regexpMatchGetGroupByName( + _match, + WasmI32.fromInt(groupIndex), + ); + return contents.isNull ? null : JSStringImpl.fromRefUnchecked(contents); + } +} + +class _EmbedderMatchesIterator implements Iterator { + final _EmbedderRegExp _regExp; + String? _string; + int _nextIndex; + RegExpMatch? _current; + + _EmbedderMatchesIterator(this._regExp, this._string, this._nextIndex); + + RegExpMatch get current => _current as RegExpMatch; + + static bool _isLeadSurrogate(int c) { + return c >= 0xd800 && c <= 0xdbff; + } + + static bool _isTrailSurrogate(int c) { + return c >= 0xdc00 && c <= 0xdfff; + } + + bool moveNext() { + var string = _string; + if (string == null) return false; + + if (_nextIndex <= string.length) { + final match = _regExp._search(_string!, _nextIndex, false); + if (match != null) { + _current = match; + int nextIndex = match.end; + if (match.start == nextIndex) { + // Zero-width match. Advance by one more, unless the regexp + // is in unicode mode and it would put us within a surrogate + // pair. In that case, advance past the code point as a whole. + if (_regExp.isUnicode && + _nextIndex + 1 < string.length && + _isLeadSurrogate(string.codeUnitAt(_nextIndex)) && + _isTrailSurrogate(string.codeUnitAt(_nextIndex + 1))) { + nextIndex++; + } + nextIndex++; + } + _nextIndex = nextIndex; + return true; + } + } + _current = null; + _string = null; // Marks iteration as ended. + return false; + } +} diff --git a/sdk/lib/_internal/wasm_standalone/lib/stack_trace_patch.dart b/sdk/lib/_internal/wasm_standalone/lib/stack_trace_patch.dart new file mode 100644 index 00000000000..dd36b7cf2fa --- /dev/null +++ b/sdk/lib/_internal/wasm_standalone/lib/stack_trace_patch.dart @@ -0,0 +1,34 @@ +// 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'; +import 'dart:_internal' show patch; +import 'dart:_string'; +import 'dart:_wasm'; + +@patch +class StackTrace { + @patch + @pragma("wasm:entry-point") + @pragma('wasm:never-inline') + static StackTrace get current { + final hostStackTrace = stackTraceGetCurrent(); + return _EmbedderStackTrace(hostStackTrace); + } +} + +final class _EmbedderStackTrace implements StackTrace { + WasmExternRef? _embedderStackTrace = WasmExternRef.nullRef; + + _EmbedderStackTrace(WasmExternRef? obj) { + _embedderStackTrace = obj; + } + + @override + String toString() { + return JSStringImpl.fromRefUnchecked( + stackTraceToString(_embedderStackTrace), + ); + } +} diff --git a/sdk/lib/_internal/wasm_standalone/lib/stopwatch_patch.dart b/sdk/lib/_internal/wasm_standalone/lib/stopwatch_patch.dart new file mode 100644 index 00000000000..bb1d41d3d8d --- /dev/null +++ b/sdk/lib/_internal/wasm_standalone/lib/stopwatch_patch.dart @@ -0,0 +1,39 @@ +// 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; +import 'dart:_embedder'; + +@patch +class Stopwatch { + @patch + static int _initTicker() { + final frequency = monotonicClockFrequency().toIntUnsigned(); + if (frequency != 1000 && frequency != 1000000) { + throw AssertionError( + 'dart:monotonicClockFrequency import must return either 1kHz or 1MHz.', + ); + } + return frequency; + } + + @patch + static int _now() => monotonicClockTicks().toInt(); + + @patch + int get elapsedMicroseconds { + int ticks = elapsedTicks; + if (_frequency == 1000000) return ticks; + assert(_frequency == 1000); + return ticks * 1000; + } + + @patch + int get elapsedMilliseconds { + int ticks = elapsedTicks; + if (_frequency == 1000) return ticks; + assert(_frequency == 1000000); + return ticks ~/ 1000; + } +} diff --git a/sdk/lib/_internal/wasm_standalone/lib/string_buffer_patch.dart b/sdk/lib/_internal/wasm_standalone/lib/string_buffer_patch.dart new file mode 100644 index 00000000000..c1d35106c6d --- /dev/null +++ b/sdk/lib/_internal/wasm_standalone/lib/string_buffer_patch.dart @@ -0,0 +1,81 @@ +// 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'; +import 'dart:_internal' show patch; +import 'dart:_js_helper'; +import 'dart:_string'; +import 'dart:_wasm'; + +@patch +class StringBuffer { + WasmExternRef? _hostBuffer = WasmExternRef.nullRef; + + @patch + @pragma("wasm:prefer-inline") + StringBuffer([Object content = '']) { + _hostBuffer = stringBufferCreate(); + if (content is! String || content.isNotEmpty) { + write(content); + } + } + + @patch + int get length => stringBufferLength(_hostBuffer).toIntUnsigned(); + + @patch + void write(Object? obj) { + if (obj is String) { + _writeString(obj); + } else { + _writeString(obj.toString()); + } + } + + @patch + void writeCharCode(int charCode) { + stringBufferWriteCharCode(_hostBuffer, WasmI32.fromInt(charCode)); + } + + @patch + void writeAll(Iterable objects, [String separator = ""]) { + final iterator = objects.iterator; + if (!iterator.moveNext()) return; + + if (separator.isEmpty) { + do { + write(iterator.current); + } while (iterator.moveNext()); + } else { + write(iterator.current); + while (iterator.moveNext()) { + _writeString(separator); + write(iterator.current); + } + } + } + + @patch + void writeln([Object? obj = '']) { + write(obj); + writeCharCode(10 /*\n*/); + } + + @patch + void clear() { + stringBufferClear(_hostBuffer); + } + + @patch + String toString() { + return JSStringImpl.fromRefUnchecked(stringBufferToString(_hostBuffer)); + } + + void _writeString(String str) { + stringBufferWriteString( + _hostBuffer, + jsStringFromDartString(str).wrappedExternRef, + ); + } +} diff --git a/sdk/lib/_internal/wasm_standalone/lib/uri_patch.dart b/sdk/lib/_internal/wasm_standalone/lib/uri_patch.dart new file mode 100644 index 00000000000..5d753f2fe89 --- /dev/null +++ b/sdk/lib/_internal/wasm_standalone/lib/uri_patch.dart @@ -0,0 +1,28 @@ +// 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'; +import 'dart:_internal' show patch; +import 'dart:_string'; +import 'dart:_wasm'; + +@patch +class Uri { + @patch + static Uri get base { + final currentUri = JSStringImpl.fromRefUnchecked(baseUri()); + if (currentUri != null) { + return Uri.parse(currentUri); + } + throw UnsupportedError("'Uri.base' is not supported"); + } +} + +@patch +class _Uri { + @patch + static bool get _isWindows => _isWindowsCached; + + static final bool _isWindowsCached = isWindows().toBool(); +} diff --git a/sdk/lib/_internal/wasm_standalone/lib/weak_patch.dart b/sdk/lib/_internal/wasm_standalone/lib/weak_patch.dart new file mode 100644 index 00000000000..159b098cd54 --- /dev/null +++ b/sdk/lib/_internal/wasm_standalone/lib/weak_patch.dart @@ -0,0 +1,112 @@ +// 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'; +import 'dart:_internal' show patch, unsafeCast, checkValidWeakTarget; +import 'dart:_wasm'; +import 'dart:async'; + +@patch +class Expando { + WasmExternRef? _expando = WasmExternRef.nullRef; + + @patch + Expando([String? name]) : name = name { + _expando = expandoCreate(); + } + + @patch + T? operator [](Object object) { + checkValidWeakTarget(object, 'object'); + return unsafeCast( + expandoGet( + _expando, + WasmAnyRef.fromObject(object), + WasmI64.fromInt(identityHashCode(object)), + )?.toObject(), + ); + } + + @patch + void operator []=(Object object, T? value) { + checkValidWeakTarget(object, 'object'); + expandoSet( + _expando, + WasmAnyRef.fromObject(object), + WasmI64.fromInt(identityHashCode(object)), + value == null ? null : WasmAnyRef.fromObject(value), + ); + } +} + +@patch +class WeakReference { + @patch + factory WeakReference(T target) { + checkValidWeakTarget(target, 'target'); + return _EmbedderWeakReference(target); + } +} + +final class _EmbedderWeakReference + implements WeakReference { + WasmExternRef? _ref = WasmExternRef.nullRef; + + _EmbedderWeakReference(T target) { + _ref = weakRefCreate(WasmAnyRef.fromObject(target)); + } + + @override + T? get target { + return unsafeCast(weakRefGet(_ref)?.toObject()); + } +} + +@patch +class Finalizer { + @patch + factory Finalizer(void Function(T) callback) { + return _EmbedderFinalizer(callback); + } +} + +final class _EmbedderFinalizer implements Finalizer { + final void Function(T) _callback; + WasmExternRef? _finalizer = WasmExternRef.nullRef; + + _EmbedderFinalizer(void Function(T) callback) + : _callback = Zone.current.bindUnaryCallback(callback) { + _finalizer = finalizerCreate( + WasmFunction.fromFunction(_entrypoint), + WasmAnyRef.fromObject(this), + ); + } + + @override + void attach(Object value, T finalizationToken, {Object? detach}) { + checkValidWeakTarget(value, 'value'); + if (detach != null) checkValidWeakTarget(detach, 'detach'); + + finalizerAttach( + _finalizer, + WasmAnyRef.fromObject(value), + finalizationToken == null + ? null + : WasmAnyRef.fromObject(finalizationToken), + detach == null ? null : WasmAnyRef.fromObject(detach), + ); + } + + @override + void detach(Object detach) { + checkValidWeakTarget(detach, 'detach'); + finalizerDetach(_finalizer, WasmAnyRef.fromObject(detach)); + } + + static WasmVoid _entrypoint(WasmAnyRef finalizer, WasmAnyRef? token) { + final dartFinalizer = unsafeCast<_EmbedderFinalizer>(finalizer.toObject()); + dartFinalizer._callback(token?.toObject()); + return WasmVoid(); + } +} diff --git a/sdk/lib/libraries.json b/sdk/lib/libraries.json index 912dc71ff9d..9cd2f875f97 100644 --- a/sdk/lib/libraries.json +++ b/sdk/lib/libraries.json @@ -15,7 +15,8 @@ "_internal": { "uri": "internal/internal.dart", "patches": [ - "_internal/vm/lib/internal_patch.dart" + "_internal/vm/lib/internal_patch.dart", + "_internal/vm_shared/lib/check_valid_weak_target_patch.dart" ] }, "async": { @@ -53,7 +54,8 @@ "_internal/vm_shared/lib/integers_patch.dart", "_internal/vm_shared/lib/map_patch.dart", "_internal/vm_shared/lib/null_patch.dart", - "_internal/vm_shared/lib/string_buffer_patch.dart" + "_internal/vm_shared/lib/string_buffer_patch.dart", + "_internal/vm_shared/lib/uri_encode_patch.dart" ] }, "developer": { @@ -285,18 +287,19 @@ "_internal/wasm/lib/array_patch.dart", "_internal/wasm/lib/bigint_patch_patch.dart", "_internal/wasm/lib/core_patch.dart", - "_internal/wasm/lib/double_patch.dart", - "_internal/wasm/lib/date_patch_patch.dart", + "_internal/wasm_standalone/lib/double_patch.dart", + "_internal/wasm_standalone/lib/date_patch_patch.dart", "_internal/wasm/lib/int_common_patch.dart", "_internal/wasm/lib/int_patch.dart", - "_internal/wasm/lib/regexp_patch.dart", - "_internal/wasm/lib/stack_trace_patch.dart", - "_internal/wasm/lib/string_buffer_patch.dart", + "_internal/wasm_standalone/lib/regexp_patch.dart", + "_internal/wasm_standalone/lib/stack_trace_patch.dart", + "_internal/wasm_standalone/lib/string_buffer_patch.dart", "_internal/wasm/lib/string_patch.dart", - "_internal/wasm/lib/stopwatch_patch.dart", + "_internal/wasm_standalone/lib/stopwatch_patch.dart", "_internal/wasm/lib/sync_star_patch.dart", - "_internal/wasm/lib/uri_patch.dart", - "_internal/wasm/lib/weak_patch.dart" + "_internal/wasm_standalone/lib/uri_patch.dart", + "_internal/vm_shared/lib/uri_encode_patch.dart", + "_internal/wasm_standalone/lib/weak_patch.dart" ] }, "convert": { @@ -320,12 +323,12 @@ }, "_boxed_int": { "uri": "_internal/wasm/lib/boxed_int.dart", - "patches": "_internal/wasm/lib/boxed_int_patch.dart" + "patches": "_internal/wasm_standalone/lib/boxed_int_patch.dart" }, "_boxed_double": { "uri": "_internal/wasm/lib/boxed_double.dart", "patches": [ - "_internal/wasm/lib/boxed_double_patch.dart" + "_internal/wasm_standalone/lib/boxed_double_patch.dart" ] }, "_string": { @@ -350,7 +353,8 @@ "_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/lib/print_patch.dart", + "_internal/vm_shared/lib/check_valid_weak_target_patch.dart" ] }, "_wasm": { @@ -386,7 +390,8 @@ "_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/lib/print_patch.dart", + "_internal/vm_shared/lib/check_valid_weak_target_patch.dart" ] }, "_wasm": { diff --git a/sdk/lib/libraries.yaml b/sdk/lib/libraries.yaml index 8f092959eba..175a55b8b3f 100644 --- a/sdk/lib/libraries.yaml +++ b/sdk/lib/libraries.yaml @@ -26,6 +26,7 @@ vm_common: uri: "internal/internal.dart" patches: - "_internal/vm/lib/internal_patch.dart" + - _internal/vm_shared/lib/check_valid_weak_target_patch.dart async: uri: "async/async.dart" @@ -60,6 +61,7 @@ vm_common: - "_internal/vm_shared/lib/map_patch.dart" - "_internal/vm_shared/lib/null_patch.dart" - "_internal/vm_shared/lib/string_buffer_patch.dart" + - "_internal/vm_shared/lib/uri_encode_patch.dart" developer: uri: "developer/developer.dart" @@ -243,18 +245,19 @@ wasm_standalone: - _internal/wasm/lib/array_patch.dart - _internal/wasm/lib/bigint_patch_patch.dart - _internal/wasm/lib/core_patch.dart - - _internal/wasm/lib/double_patch.dart # TODO(53884): Rewrite without JS interop in _internal/wasm_standalone - - _internal/wasm/lib/date_patch_patch.dart # TODO(53884): Rewrite without JS interop in _internal/wasm_standalone + - _internal/wasm_standalone/lib/double_patch.dart + - _internal/wasm_standalone/lib/date_patch_patch.dart - _internal/wasm/lib/int_common_patch.dart - _internal/wasm/lib/int_patch.dart - - _internal/wasm/lib/regexp_patch.dart # TODO(53884): Rewrite without JS interop in _internal/wasm_standalone - - _internal/wasm/lib/stack_trace_patch.dart # TODO(53884): Rewrite without JS interop in _internal/wasm_standalone - - _internal/wasm/lib/string_buffer_patch.dart # TODO(53884): Rewrite without JS interop in _internal/wasm_standalone + - _internal/wasm_standalone/lib/regexp_patch.dart + - _internal/wasm_standalone/lib/stack_trace_patch.dart + - _internal/wasm_standalone/lib/string_buffer_patch.dart - _internal/wasm/lib/string_patch.dart # TODO(53884): Rewrite without JS interop in _internal/wasm_standalone - - _internal/wasm/lib/stopwatch_patch.dart # TODO(53884): Rewrite without JS interop in _internal/wasm_standalone + - _internal/wasm_standalone/lib/stopwatch_patch.dart - _internal/wasm/lib/sync_star_patch.dart - - _internal/wasm/lib/uri_patch.dart # TODO(53884): Rewrite without JS interop in _internal/wasm_standalone - - _internal/wasm/lib/weak_patch.dart # TODO(53884): Rewrite without JS interop in _internal/wasm_standalone + - _internal/wasm_standalone/lib/uri_patch.dart + - _internal/vm_shared/lib/uri_encode_patch.dart + - _internal/wasm_standalone/lib/weak_patch.dart convert: uri: convert/convert.dart patches: @@ -271,11 +274,11 @@ wasm_standalone: _boxed_int: uri: _internal/wasm/lib/boxed_int.dart patches: - _internal/wasm/lib/boxed_int_patch.dart # TODO(53884): Rewrite without JS interop in _internal/wasm_standalone + _internal/wasm_standalone/lib/boxed_int_patch.dart _boxed_double: uri: _internal/wasm/lib/boxed_double.dart patches: - - _internal/wasm/lib/boxed_double_patch.dart # TODO(53884): Rewrite without JS interop in _internal/wasm_standalone + - _internal/wasm_standalone/lib/boxed_double_patch.dart _string: uri: _internal/wasm/lib/js_string.dart # TODO(53884): Rewrite without JS interop in _internal/wasm_standalone _typed_data: @@ -294,6 +297,7 @@ 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/vm_shared/lib/check_valid_weak_target_patch.dart _wasm: uri: _wasm/wasm_types.dart patches: _internal/wasm/lib/wasm_types_patch.dart # TODO(53884): Remove once other wasm_standalone patches no longer reference this @@ -319,6 +323,7 @@ wasm_js_common: - _internal/wasm/lib/internal_json_encode_patch.dart - _internal/wasm/lib/invoke_main_patch.dart - _internal/wasm/lib/print_patch.dart + - _internal/vm_shared/lib/check_valid_weak_target_patch.dart _wasm: uri: _wasm/wasm_types.dart patches: _internal/wasm/lib/wasm_types_patch.dart