From 7cceaebd4932bee5785bd183b19d2ce1bb1c572f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=96mer=20Sinan=20A=C4=9Facan?= Date: Fri, 8 Dec 2023 10:04:41 +0000 Subject: [PATCH] [dart2wasm] Start using WebAssembly.String imports MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Refactor `JSStringImpl` to use the js-string-builtins API[1]. When the module `WebAssembly.String` is not available we define a "polyfill" with the previous imports, so this change is backwards compatible. Also updates some of the methods to use avoid multiple `this.length` calls (which do an FFI call), and use unchecked getters when possible. A new library `dart:_error_utils` is introduced for faster range and index checks when the length (or max value) is known to be positive (e.g. when it's the length of a string). For now only `JSStringImpl` and `operator []` and `operator []=` of `JSArrayImpl` are updated to use the new range and index checks. Rest of the libraries will be updated separately. [1]: https://github.com/WebAssembly/js-string-builtins CoreLibraryReviewExempt: dart2wasm specific library change. Change-Id: I9436def0cfe59c631f6f4e15ea06cc18a47a738e Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/335043 Reviewed-by: Martin Kustermann Commit-Queue: Ömer Ağacan --- pkg/dart2wasm/lib/intrinsics.dart | 5 + pkg/dart2wasm/lib/js/runtime_blob.dart | 17 ++ .../lib/src/browser_controller.dart | 1 + .../lib/src/compiler_configuration.dart | 1 + sdk/bin/run_dart2wasm_d8 | 2 +- sdk/lib/_internal/wasm/lib/error_utils.dart | 42 ++++ sdk/lib/_internal/wasm/lib/js_array.dart | 30 ++- sdk/lib/_internal/wasm/lib/js_string.dart | 237 +++++++++++------- sdk/lib/_internal/wasm/lib/js_types.dart | 1 + sdk/lib/_wasm/wasm_types.dart | 3 + sdk/lib/core/errors.dart | 1 - sdk/lib/libraries.json | 3 + sdk/lib/libraries.yaml | 2 + 13 files changed, 242 insertions(+), 103 deletions(-) create mode 100644 sdk/lib/_internal/wasm/lib/error_utils.dart diff --git a/pkg/dart2wasm/lib/intrinsics.dart b/pkg/dart2wasm/lib/intrinsics.dart index 3c55074a5aa..afdaa692ff4 100644 --- a/pkg/dart2wasm/lib/intrinsics.dart +++ b/pkg/dart2wasm/lib/intrinsics.dart @@ -370,6 +370,11 @@ class Intrinsifier { codeGen.wrap(node.arguments.positional[0], w.NumType.i64); b.i64_le_u(); return boolType; + case "ltU": + codeGen.wrap(receiver, w.NumType.i64); + codeGen.wrap(node.arguments.positional[0], w.NumType.i64); + b.i64_lt_u(); + return boolType; default: throw 'Unknown WasmI64 member $name'; } diff --git a/pkg/dart2wasm/lib/js/runtime_blob.dart b/pkg/dart2wasm/lib/js/runtime_blob.dart index 254cf823dc5..dc59d215703 100644 --- a/pkg/dart2wasm/lib/js/runtime_blob.dart +++ b/pkg/dart2wasm/lib/js/runtime_blob.dart @@ -95,6 +95,23 @@ const jsRuntimeBlobPart3 = r''' return wrapped; } + if (WebAssembly.String === undefined) { + console.log("WebAssembly.String is undefined, adding polyfill"); + WebAssembly.String = { + "charCodeAt": (s, i) => s.charCodeAt(i), + "compare": (s1, s2) => { + if (s1 < s2) return -1; + if (s1 > s2) return 1; + return 0; + }, + "concat": (s1, s2) => s1 + s2, + "equals": (s1, s2) => s1 === s2, + "fromCharCode": (i) => String.fromCharCode(i), + "length": (s) => s.length, + "substring": (s, a, b) => s.substring(a, b), + }; + } + // Imports const dart2wasm = { '''; diff --git a/pkg/test_runner/lib/src/browser_controller.dart b/pkg/test_runner/lib/src/browser_controller.dart index b9d8ca66643..17888101823 100644 --- a/pkg/test_runner/lib/src/browser_controller.dart +++ b/pkg/test_runner/lib/src/browser_controller.dart @@ -409,6 +409,7 @@ class Chrome extends Browser { "--no-first-run", "--use-mock-keychain", "--user-data-dir=${userDir.path}", + "--js-flags=--experimental-wasm-imported-strings", url, ]; diff --git a/pkg/test_runner/lib/src/compiler_configuration.dart b/pkg/test_runner/lib/src/compiler_configuration.dart index dc93762ef0c..33e3d15c41d 100644 --- a/pkg/test_runner/lib/src/compiler_configuration.dart +++ b/pkg/test_runner/lib/src/compiler_configuration.dart @@ -604,6 +604,7 @@ class Dart2WasmCompilerConfiguration extends CompilerConfiguration { final isD8 = runtimeConfiguration is D8RuntimeConfiguration; return [ if (isD8) '--turboshaft-wasm', + if (isD8) '--experimental-wasm-imported-strings', 'pkg/dart2wasm/bin/run_wasm.js', if (isD8) '--', '${filename.substring(0, filename.lastIndexOf('.'))}.mjs', diff --git a/sdk/bin/run_dart2wasm_d8 b/sdk/bin/run_dart2wasm_d8 index e2ec5bd6994..95c3725b221 100755 --- a/sdk/bin/run_dart2wasm_d8 +++ b/sdk/bin/run_dart2wasm_d8 @@ -36,6 +36,6 @@ if [[ $D8_OPTIONS ]]; then fi # Find the JS runtime based on the input wasm file. -exec "$D8_EXEC" --turboshaft-wasm \ +exec "$D8_EXEC" --turboshaft-wasm --experimental-wasm-imported-strings \ "${EXTRA_D8_OPTIONS[@]}" \ "$SDK_DIR/pkg/dart2wasm/bin/run_wasm.js" -- "$(realpath -- "${1%.*}.mjs")" "$@" diff --git a/sdk/lib/_internal/wasm/lib/error_utils.dart b/sdk/lib/_internal/wasm/lib/error_utils.dart new file mode 100644 index 00000000000..bd7a451adad --- /dev/null +++ b/sdk/lib/_internal/wasm/lib/error_utils.dart @@ -0,0 +1,42 @@ +// Copyright (c) 2023, 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:_wasm'; + +class IndexErrorUtils { + /// Same as [IndexError.check], but assumes that [length] is positive and + /// uses a single unsigned comparison. Always inlined. + @pragma("wasm:prefer-inline") + static void checkAssumePositiveLength(int index, int length) { + if (WasmI64.fromInt(length).leU(WasmI64.fromInt(index))) { + throw IndexError.withLength(index, length); + } + } +} + +class RangeErrorUtils { + /// Same as `RangeError.checkValueInInterval(value, 0, maxValue)`, but + /// assumes that [maxValue] is positive and uses a single unsigned + /// comparison. Always inlined. + @pragma("wasm:prefer-inline") + static void checkValueBetweenZeroAndPositiveMax(int value, int maxValue) { + if (WasmI64.fromInt(maxValue).ltU(WasmI64.fromInt(value))) { + throw RangeError.range(value, 0, maxValue); + } + } + + /// Same as [RangeError.checkValidRange], but assumes that [length] is + /// positive and does less checks. Error reporting is also slightly + /// different: when both [start] and [end] are negative, this reports [end] + /// instead of [start]. Always inlined. + @pragma("wasm:prefer-inline") + static void checkValidRangePositiveLength(int start, int end, int length) { + if (WasmI64.fromInt(length).ltU(WasmI64.fromInt(end))) { + throw RangeError.range(end, 0, length); + } + if (WasmI64.fromInt(end).ltU(WasmI64.fromInt(start))) { + throw RangeError.range(start, 0, end); + } + } +} diff --git a/sdk/lib/_internal/wasm/lib/js_array.dart b/sdk/lib/_internal/wasm/lib/js_array.dart index e66ba1bacd9..f45cdc89af2 100644 --- a/sdk/lib/_internal/wasm/lib/js_array.dart +++ b/sdk/lib/_internal/wasm/lib/js_array.dart @@ -466,18 +466,30 @@ class JSArrayImpl implements List { '(a, l) => a.length = l', toExternRef, newLength.toJS.toExternRef); } - @override - JSAny? operator [](int index) { - RangeError.checkValueInInterval(index, 0, length - 1); - return js.JSValue.boxT(js.JS( - '(a, i) => a[i]', toExternRef, index.toJS.toExternRef)); - } + @pragma("wasm:prefer-inline") + JSAny? _getUnchecked(int index) => + js.JSValue.boxT(js.JS( + '(a, i) => a[i]', toExternRef, index.toJS.toExternRef)); @override + @pragma("wasm:prefer-inline") + JSAny? operator [](int index) { + IndexErrorUtils.checkAssumePositiveLength(index, length); + return _getUnchecked(index); + } + + @pragma("wasm:prefer-inline") + void _setUnchecked(int index, JSAny? value) => js.JS( + '(a, i, v) => a[i] = v', + toExternRef, + index.toJS.toExternRef, + value.toExternRef); + + @override + @pragma("wasm:prefer-inline") void operator []=(int index, JSAny? value) { - RangeError.checkValueInInterval(index, 0, length - 1); - js.JS('(a, i, v) => a[i] = v', toExternRef, index.toJS.toExternRef, - value.toExternRef); + IndexErrorUtils.checkAssumePositiveLength(index, length); + _setUnchecked(index, value); } @override diff --git a/sdk/lib/_internal/wasm/lib/js_string.dart b/sdk/lib/_internal/wasm/lib/js_string.dart index 47e615bdceb..fa14f8cd717 100644 --- a/sdk/lib/_internal/wasm/lib/js_string.dart +++ b/sdk/lib/_internal/wasm/lib/js_string.dart @@ -4,60 +4,71 @@ part of dart._js_types; -String _matchString(Match match) => match[0]!; -String _stringIdentity(String string) => string; - -/// The box class for JS' `string` object. [JSStringImpl] is heavily based off -/// of `sdk/lib/_internal/js_runtime/lib/js_string.dart`. TODO(joshualitt): Add -/// `JSString` fastpaths for cases where `String` arguments are really -/// `JSString`. final class JSStringImpl implements String { final WasmExternRef? _ref; JSStringImpl(this._ref); + @pragma("wasm:prefer-inline") static String? box(WasmExternRef? ref) => js.isDartNull(ref) ? null : JSStringImpl(ref); + @pragma("wasm:prefer-inline") WasmExternRef? get toExternRef => _ref; + @override + @pragma("wasm:prefer-inline") + int get length => _jsLength(toExternRef); + + @override + @pragma("wasm:prefer-inline") + bool get isEmpty => length == 0; + + @override + @pragma("wasm:prefer-inline") + bool get isNotEmpty => !isEmpty; + @pragma("wasm:entry-point") static String interpolate(List values) { - final array = JSArrayImpl.fromLength(values.length); - for (int i = 0; i < values.length; i++) { + final valuesLength = values.length; + final array = JSArrayImpl.fromLength(valuesLength); + for (int i = 0; i < valuesLength; i++) { final o = values[i]; final s = o.toString(); final jsString = s is JSStringImpl ? js.JSValue.boxT(s.toExternRef) : s.toJS; - array[i] = jsString; + array._setUnchecked(i, jsString); } return JSStringImpl( js.JS("a => a.join('')", array.toExternRef)); } @override + @pragma("wasm:prefer-inline") int codeUnitAt(int index) { - RangeError.checkValueInInterval(index, 0, length - 1); - return js - .JS('(s, i) => s.charCodeAt(i)', toExternRef, - index.toDouble().toExternRef) - .toInt(); + final length = this.length; + IndexErrorUtils.checkAssumePositiveLength(index, length); + return _codeUnitAtUnchecked(index); + } + + @pragma("wasm:prefer-inline") + int _codeUnitAtUnchecked(int index) { + return _jsCharCodeAt(toExternRef, index); } @override Iterable allMatches(String string, [int start = 0]) { - if (0 > start || start > string.length) { - throw new RangeError.range(start, 0, string.length); - } + final stringLength = string.length; + RangeErrorUtils.checkValueBetweenZeroAndPositiveMax(start, stringLength); return StringAllMatchesIterable(string, this, start); } @override Match? matchAsPrefix(String string, [int start = 0]) { - if (start < 0 || start > string.length) { - throw new RangeError.range(start, 0, string.length); - } - if (start + length > string.length) return null; + final stringLength = string.length; + RangeErrorUtils.checkValueBetweenZeroAndPositiveMax(start, stringLength); + final length = this.length; + if (start + length > stringLength) return null; // TODO(lrn): See if this can be optimized. for (int i = 0; i < length; i++) { if (string.codeUnitAt(start + i) != codeUnitAt(i)) { @@ -70,8 +81,7 @@ final class JSStringImpl implements String { @override String operator +(String other) { if (other is JSStringImpl) { - return JSStringImpl(js.JS( - '(a, b) => a + b', toExternRef, other.toExternRef)); + return JSStringImpl(_jsConcat(toExternRef, other.toExternRef)); } // TODO(joshualitt): Refactor `string_patch.dart` so we can directly @@ -81,7 +91,8 @@ final class JSStringImpl implements String { @override bool endsWith(String other) { - int otherLength = other.length; + final otherLength = other.length; + final length = this.length; if (otherLength > length) return false; return other == substring(length - otherLength); } @@ -102,6 +113,7 @@ final class JSStringImpl implements String { } else { StringBuffer result = StringBuffer(); result.write(to); + final length = this.length; for (int i = 0; i < length; i++) { result.write(this[i]); result.write(to); @@ -143,12 +155,13 @@ final class JSStringImpl implements String { if (onMatch == null) onMatch = _matchString; if (onNonMatch == null) onNonMatch = _stringIdentity; if (from is String) { - int patternLength = from.length; + final patternLength = from.length; if (patternLength == 0) { // Pattern is the empty string. StringBuffer buffer = StringBuffer(); int i = 0; buffer.write(onNonMatch("")); + final length = this.length; while (i < length) { buffer.write(onMatch(StringMatch(i, this, ""))); // Special case to avoid splitting a surrogate pair. @@ -172,6 +185,7 @@ final class JSStringImpl implements String { } StringBuffer buffer = StringBuffer(); int startIndex = 0; + final length = this.length; while (startIndex < length) { int position = indexOf(from, startIndex); if (position == -1) { @@ -212,7 +226,7 @@ final class JSStringImpl implements String { @override String replaceFirst(Pattern from, String to, [int startIndex = 0]) { - RangeError.checkValueInInterval(startIndex, 0, length, "startIndex"); + RangeErrorUtils.checkValueBetweenZeroAndPositiveMax(startIndex, length); if (from is String) { int index = indexOf(from, startIndex); if (index < 0) return this; @@ -233,7 +247,7 @@ final class JSStringImpl implements String { @override String replaceFirstMapped(Pattern from, String replace(Match match), [int startIndex = 0]) { - RangeError.checkValueInInterval(startIndex, 0, this.length, "startIndex"); + RangeErrorUtils.checkValueBetweenZeroAndPositiveMax(startIndex, length); Iterator matches = from.allMatches(this, startIndex).iterator; if (!matches.moveNext()) return this; Match match = matches.current; @@ -292,15 +306,14 @@ final class JSStringImpl implements String { @override String replaceRange(int start, int? end, String replacement) { - final e = RangeError.checkValidRange(start, end, length); - return _replaceRange(start, e, replacement); + end ??= length; + RangeErrorUtils.checkValidRangePositiveLength(start, end, length); + return _replaceRange(start, end, replacement); } @override bool startsWith(Pattern pattern, [int index = 0]) { - if (index < 0 || index > length) { - throw RangeError.range(index, 0, length); - } + RangeErrorUtils.checkValueBetweenZeroAndPositiveMax(index, length); if (pattern is String) { final patternLength = pattern.length; final endIndex = index + patternLength; @@ -312,9 +325,9 @@ final class JSStringImpl implements String { @override String substring(int start, [int? end]) { - end = RangeError.checkValidRange(start, end, this.length); - return JSStringImpl(js.JS('(o, s, i) => o.substring(s, i)', - toExternRef, start.toDouble(), end.toDouble())); + end ??= length; + RangeErrorUtils.checkValidRangePositiveLength(start, end, length); + return JSStringImpl(_jsSubstring(toExternRef, start, end)); } @override @@ -398,9 +411,10 @@ final class JSStringImpl implements String { /// Finds the index of the first non-whitespace character, or the /// end of the string. Start looking at position [index]. - static int _skipLeadingWhitespace(String string, int index) { - while (index < string.length) { - int codeUnit = string.codeUnitAt(index); + static int _skipLeadingWhitespace(JSStringImpl string, int index) { + final stringLength = string.length; + while (index < stringLength) { + int codeUnit = string._codeUnitAtUnchecked(index); if (codeUnit != spaceCodeUnit && codeUnit != carriageReturnCodeUnit && !_isWhitespace(codeUnit)) { @@ -413,9 +427,9 @@ final class JSStringImpl implements String { /// Finds the index after the last non-whitespace character, or 0. /// Start looking at position [index - 1]. - static int _skipTrailingWhitespace(String string, int index) { + static int _skipTrailingWhitespace(JSStringImpl string, int index) { while (index > 0) { - int codeUnit = string.codeUnitAt(index - 1); + int codeUnit = string._codeUnitAtUnchecked(index - 1); if (codeUnit != spaceCodeUnit && codeUnit != carriageReturnCodeUnit && !_isWhitespace(codeUnit)) { @@ -426,7 +440,7 @@ final class JSStringImpl implements String { return index; } - // Dart2Wasm can't use JavaScript trim directly, + // dart2wasm can't use JavaScript trim directly, // because JavaScript does not trim // the NEXT LINE (NEL) character (0x85). @override @@ -435,26 +449,27 @@ final class JSStringImpl implements String { // either end of the string. final result = JSStringImpl(js.JS('s => s.trim()', toExternRef)); - if (result.length == 0) return result; - int firstCode = result.codeUnitAt(0); + final resultLength = result.length; + if (resultLength == 0) return result; + int firstCode = result._codeUnitAtUnchecked(0); int startIndex = 0; if (firstCode == nelCodeUnit) { startIndex = _skipLeadingWhitespace(result, 1); - if (startIndex == result.length) return ""; + if (startIndex == resultLength) return ""; } - int endIndex = result.length; + int endIndex = resultLength; // We know that there is at least one character that is non-whitespace. // Therefore we don't need to verify that endIndex > startIndex. int lastCode = result.codeUnitAt(endIndex - 1); if (lastCode == nelCodeUnit) { endIndex = _skipTrailingWhitespace(result, endIndex - 1); } - if (startIndex == 0 && endIndex == result.length) return result; + if (startIndex == 0 && endIndex == resultLength) return result; return substring(startIndex, endIndex); } - // Dart2Wasm can't use JavaScript trimLeft directly because it does not trim + // dart2wasm can't use JavaScript trimLeft directly because it does not trim // the NEXT LINE character (0x85). @override String trimLeft() { @@ -463,17 +478,18 @@ final class JSStringImpl implements String { int startIndex = 0; final result = JSStringImpl(js.JS('s => s.trimLeft()', toExternRef)); - if (result.length == 0) return result; - int firstCode = result.codeUnitAt(0); + final resultLength = result.length; + if (resultLength == 0) return result; + int firstCode = result._codeUnitAtUnchecked(0); if (firstCode == nelCodeUnit) { startIndex = _skipLeadingWhitespace(result, 1); } if (startIndex == 0) return result; - if (startIndex == result.length) return ""; + if (startIndex == resultLength) return ""; return result.substring(startIndex); } - // Dart2Wasm can't use JavaScript trimRight directly because it does not trim + // dart2wasm can't use JavaScript trimRight directly because it does not trim // the NEXT LINE character (0x85). @override String trimRight() { @@ -481,14 +497,15 @@ final class JSStringImpl implements String { // string. final result = JSStringImpl(js.JS('s => s.trimRight()', toExternRef)); - int endIndex = result.length; + final resultLength = result.length; + int endIndex = resultLength; if (endIndex == 0) return result; int lastCode = result.codeUnitAt(endIndex - 1); if (lastCode == nelCodeUnit) { endIndex = _skipTrailingWhitespace(result, endIndex - 1); } - if (endIndex == result.length) return result; + if (endIndex == resultLength) return result; if (endIndex == 0) return ""; return result.substring(0, endIndex); } @@ -503,16 +520,16 @@ final class JSStringImpl implements String { @override String padLeft(int width, [String padding = ' ']) { - int delta = width - this.length; + int delta = width - length; if (delta <= 0) return this; - return padding * delta + this; + return (padding * delta) + this; } @override String padRight(int width, [String padding = ' ']) { - int delta = width - this.length; + int delta = width - length; if (delta <= 0) return this; - return this + padding * delta; + return this + (padding * delta); } @override @@ -528,9 +545,8 @@ final class JSStringImpl implements String { @override int indexOf(Pattern pattern, [int start = 0]) { - if (start < 0 || start > this.length) { - throw RangeError.range(start, 0, this.length); - } + final length = this.length; + RangeErrorUtils.checkValueBetweenZeroAndPositiveMax(start, length); if (pattern is JSStringImpl) { return _jsIndexOf(pattern.toExternRef, start); } else if (pattern is String) { @@ -539,7 +555,7 @@ final class JSStringImpl implements String { Match? match = js.firstMatchAfter(pattern, this, start); return (match == null) ? -1 : match.start; } else { - for (int i = start; i <= this.length; i++) { + for (int i = start; i <= length; i++) { if (pattern.matchAsPrefix(this, i) != null) return i; } return -1; @@ -553,10 +569,11 @@ final class JSStringImpl implements String { @override int lastIndexOf(Pattern pattern, [int? start]) { + final length = this.length; if (start == null) { start = length; - } else if (start < 0 || start > this.length) { - throw RangeError.range(start, 0, this.length); + } else { + RangeErrorUtils.checkValueBetweenZeroAndPositiveMax(start, length); } if (pattern is JSStringImpl) { if (start + pattern.length > length) { @@ -577,9 +594,8 @@ final class JSStringImpl implements String { @override bool contains(Pattern other, [int startIndex = 0]) { - if (startIndex < 0 || startIndex > this.length) { - throw RangeError.range(startIndex, 0, this.length); - } + final length = this.length; + RangeErrorUtils.checkValueBetweenZeroAndPositiveMax(startIndex, length); if (other is String) { return indexOf(other, startIndex) >= 0; } else if (other is js.JSSyntaxRegExp) { @@ -589,31 +605,24 @@ final class JSStringImpl implements String { } } - @override - bool get isEmpty => length == 0; - - @override - bool get isNotEmpty => !isEmpty; - /// This must be kept in sync with `StringBase.hashCode` in string_patch.dart. /// TODO(joshualitt): Find some way to cache the hash code. @override int get hashCode { int hash = 0; + final length = this.length; for (int i = 0; i < length; i++) { - hash = stringCombineHashes(hash, codeUnitAt(i)); + hash = stringCombineHashes(hash, _codeUnitAtUnchecked(i)); } return stringFinalizeHash(hash); } @override - int get length => js.JS('s => s.length', toExternRef).toInt(); - - @override + @pragma("wasm:prefer-inline") String operator [](int index) { - RangeError.checkValueInInterval(index, 0, length - 1); - return JSStringImpl(js.JS( - '(s, i) => s[i]', toExternRef, index.toDouble().toExternRef)); + final length = this.length; + IndexErrorUtils.checkAssumePositiveLength(index, length); + return JSStringImpl(_jsFromCharCode(_codeUnitAtUnchecked(index))); } @override @@ -621,25 +630,34 @@ final class JSStringImpl implements String { if (identical(this, other)) { return true; } - if (other is JSStringImpl && length == other.length) { - return js.areEqualInJS(toExternRef, other.toExternRef); - } else if (other is String && length == other.length) { + + if (other is JSStringImpl) { + return _jsEquals(toExternRef, other.toExternRef); + } + + final length = this.length; + if (other is String && length == other.length) { for (int i = 0; i < length; i++) { - if (codeUnitAt(i) != other.codeUnitAt(i)) { + if (_codeUnitAtUnchecked(i) != other.codeUnitAt(i)) { return false; } } return true; } + return false; } @override int compareTo(String other) { - int otherLength = other.length; - int len = (length < otherLength) ? length : otherLength; + if (other is JSStringImpl) { + return _jsCompare(toExternRef, other.toExternRef); + } + final otherLength = other.length; + final length = this.length; + final len = (length < otherLength) ? length : otherLength; for (int i = 0; i < len; i++) { - int thisCodeUnit = this.codeUnitAt(i); + int thisCodeUnit = _codeUnitAtUnchecked(i); int otherCodeUnit = other.codeUnitAt(i); if (thisCodeUnit < otherCodeUnit) { return -1; @@ -657,10 +675,10 @@ final class JSStringImpl implements String { String toString() => js.stringify(toExternRef); int firstNonWhitespace() { - final len = this.length; + final length = this.length; int first = 0; - for (; first < len; first++) { - if (!_isWhitespace(this.codeUnitAt(first))) { + for (; first < length; first++) { + if (!_isWhitespace(_codeUnitAtUnchecked(first))) { break; } } @@ -668,9 +686,9 @@ final class JSStringImpl implements String { } int lastNonWhitespace() { - int last = this.length - 1; + int last = length - 1; for (; last >= 0; last--) { - if (!_isWhitespace(this.codeUnitAt(last))) { + if (!_isWhitespace(_codeUnitAtUnchecked(last))) { break; } } @@ -678,6 +696,10 @@ final class JSStringImpl implements String { } } +String _matchString(Match match) => match[0]!; + +String _stringIdentity(String string) => string; + @pragma("wasm:export", "\$jsStringToJSStringImpl") JSStringImpl _jsStringToJSStringImpl(WasmExternRef? string) => JSStringImpl(string); @@ -688,3 +710,34 @@ WasmExternRef? _jsStringFromJSStringImpl(JSStringImpl string) => bool _jsIdentical(WasmExternRef? ref1, WasmExternRef? ref2) => js.JS('Object.is', ref1, ref2); + +@pragma("wasm:prefer-inline") +int _jsCharCodeAt(WasmExternRef? stringRef, int index) => js + .JS( + 'WebAssembly.String.charCodeAt', stringRef, WasmI32.fromInt(index)) + .toIntUnsigned(); + +WasmExternRef _jsConcat(WasmExternRef? s1, WasmExternRef? s2) => + js.JS('WebAssembly.String.concat', s1, s2); + +@pragma("wasm:prefer-inline") +WasmExternRef _jsSubstring( + WasmExternRef? stringRef, int startIndex, int endIndex) => + js.JS('WebAssembly.String.substring', stringRef, + WasmI32.fromInt(startIndex), WasmI32.fromInt(endIndex)); + +@pragma("wasm:prefer-inline") +int _jsLength(WasmExternRef? stringRef) => + js.JS('WebAssembly.String.length', stringRef).toIntUnsigned(); + +@pragma("wasm:prefer-inline") +bool _jsEquals(WasmExternRef? s1, WasmExternRef? s2) => + js.JS('WebAssembly.String.equals', s1, s2).toBool(); + +@pragma("wasm:prefer-inline") +int _jsCompare(WasmExternRef? s1, WasmExternRef? s2) => + js.JS('WebAssembly.String.compare', s1, s2).toIntSigned(); + +@pragma("wasm:prefer-inline") +WasmExternRef _jsFromCharCode(int charCode) => js.JS( + 'WebAssembly.String.fromCharCode', WasmI32.fromInt(charCode)); diff --git a/sdk/lib/_internal/wasm/lib/js_types.dart b/sdk/lib/_internal/wasm/lib/js_types.dart index 64d66d61aae..c87ba9b7e77 100644 --- a/sdk/lib/_internal/wasm/lib/js_types.dart +++ b/sdk/lib/_internal/wasm/lib/js_types.dart @@ -13,6 +13,7 @@ /// change in the future. library dart._js_types; +import 'dart:_error_utils'; import 'dart:_internal'; import 'dart:_js_helper' as js; import 'dart:_string_helper'; diff --git a/sdk/lib/_wasm/wasm_types.dart b/sdk/lib/_wasm/wasm_types.dart index c61878301a6..ce4c7762139 100644 --- a/sdk/lib/_wasm/wasm_types.dart +++ b/sdk/lib/_wasm/wasm_types.dart @@ -156,6 +156,9 @@ class WasmI64 extends _WasmInt { /// `i64.le_u`. external bool leU(WasmI64 other); + + /// `i64.lt_u`. + external bool ltU(WasmI64 other); } /// The Wasm `f32` type. diff --git a/sdk/lib/core/errors.dart b/sdk/lib/core/errors.dart index fa8147f5ea3..0897a79ef13 100644 --- a/sdk/lib/core/errors.dart +++ b/sdk/lib/core/errors.dart @@ -306,7 +306,6 @@ class RangeError extends ArgumentError { /// name and message text of the thrown error. /// /// Returns [value] if it is in the interval. - @pragma("wasm:entry-point") static int checkValueInInterval(int value, int minValue, int maxValue, [String? name, String? message]) { if (value < minValue || value > maxValue) { diff --git a/sdk/lib/libraries.json b/sdk/lib/libraries.json index 8375e10c9cb..541b8452ca7 100644 --- a/sdk/lib/libraries.json +++ b/sdk/lib/libraries.json @@ -239,6 +239,9 @@ "_internal/wasm/lib/int_common_patch.dart" ] }, + "_error_utils": { + "uri": "_internal/wasm/lib/error_utils.dart" + }, "_http": { "uri": "_http/http.dart" }, diff --git a/sdk/lib/libraries.yaml b/sdk/lib/libraries.yaml index 8bac4a0e452..be735a7022f 100644 --- a/sdk/lib/libraries.yaml +++ b/sdk/lib/libraries.yaml @@ -202,6 +202,8 @@ wasm_common: - _internal/wasm/lib/boxed_double.dart - _internal/wasm/lib/boxed_int.dart - _internal/wasm/lib/int_common_patch.dart + _error_utils: + uri: _internal/wasm/lib/error_utils.dart _http: uri: _http/http.dart _internal: