[dart2wasm] Start using WebAssembly.String imports
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 <kustermann@google.com>
Commit-Queue: Ömer Ağacan <omersa@google.com>
diff --git a/pkg/dart2wasm/lib/intrinsics.dart b/pkg/dart2wasm/lib/intrinsics.dart
index 3c55074..afdaa69 100644
--- a/pkg/dart2wasm/lib/intrinsics.dart
+++ b/pkg/dart2wasm/lib/intrinsics.dart
@@ -370,6 +370,11 @@
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 254cf82..dc59d21 100644
--- a/pkg/dart2wasm/lib/js/runtime_blob.dart
+++ b/pkg/dart2wasm/lib/js/runtime_blob.dart
@@ -95,6 +95,23 @@
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 b9d8ca6..1788810 100644
--- a/pkg/test_runner/lib/src/browser_controller.dart
+++ b/pkg/test_runner/lib/src/browser_controller.dart
@@ -409,6 +409,7 @@
"--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 dc93762..33e3d15 100644
--- a/pkg/test_runner/lib/src/compiler_configuration.dart
+++ b/pkg/test_runner/lib/src/compiler_configuration.dart
@@ -604,6 +604,7 @@
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 e2ec5bd..95c3725 100755
--- a/sdk/bin/run_dart2wasm_d8
+++ b/sdk/bin/run_dart2wasm_d8
@@ -36,6 +36,6 @@
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 0000000..bd7a451
--- /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 e66ba1b..f45cdc8 100644
--- a/sdk/lib/_internal/wasm/lib/js_array.dart
+++ b/sdk/lib/_internal/wasm/lib/js_array.dart
@@ -466,18 +466,30 @@
'(a, l) => a.length = l', toExternRef, newLength.toJS.toExternRef);
}
- @override
- JSAny? operator [](int index) {
- RangeError.checkValueInInterval(index, 0, length - 1);
- return js.JSValue.boxT<JSAny?>(js.JS<WasmExternRef?>(
- '(a, i) => a[i]', toExternRef, index.toJS.toExternRef));
- }
+ @pragma("wasm:prefer-inline")
+ JSAny? _getUnchecked(int index) =>
+ js.JSValue.boxT<JSAny?>(js.JS<WasmExternRef?>(
+ '(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<void>(
+ '(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<void>('(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 47e615b..fa14f8c 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<Object?> 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<JSAny?>(s.toExternRef) : s.toJS;
- array[i] = jsString;
+ array._setUnchecked(i, jsString);
}
return JSStringImpl(
js.JS<WasmExternRef?>("a => a.join('')", array.toExternRef));
}
@override
+ @pragma("wasm:prefer-inline")
int codeUnitAt(int index) {
- RangeError.checkValueInInterval(index, 0, length - 1);
- return js
- .JS<double>('(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<Match> 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 @@
@override
String operator +(String other) {
if (other is JSStringImpl) {
- return JSStringImpl(js.JS<WasmExternRef?>(
- '(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 @@
@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 @@
} 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 @@
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 @@
}
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 @@
@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 @@
@override
String replaceFirstMapped(Pattern from, String replace(Match match),
[int startIndex = 0]) {
- RangeError.checkValueInInterval(startIndex, 0, this.length, "startIndex");
+ RangeErrorUtils.checkValueBetweenZeroAndPositiveMax(startIndex, length);
Iterator<Match> matches = from.allMatches(this, startIndex).iterator;
if (!matches.moveNext()) return this;
Match match = matches.current;
@@ -292,15 +306,14 @@
@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 @@
@override
String substring(int start, [int? end]) {
- end = RangeError.checkValidRange(start, end, this.length);
- return JSStringImpl(js.JS<WasmExternRef?>('(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 @@
/// 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 @@
/// 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 @@
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 @@
// either end of the string.
final result =
JSStringImpl(js.JS<WasmExternRef?>('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 @@
int startIndex = 0;
final result =
JSStringImpl(js.JS<WasmExternRef?>('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 @@
// string.
final result =
JSStringImpl(js.JS<WasmExternRef?>('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 @@
@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 @@
@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 @@
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 @@
@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 @@
@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 @@
}
}
- @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<double>('s => s.length', toExternRef).toInt();
-
- @override
+ @pragma("wasm:prefer-inline")
String operator [](int index) {
- RangeError.checkValueInInterval(index, 0, length - 1);
- return JSStringImpl(js.JS<WasmExternRef?>(
- '(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 @@
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 @@
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 @@
}
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 @@
}
}
+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 @@
bool _jsIdentical(WasmExternRef? ref1, WasmExternRef? ref2) =>
js.JS<bool>('Object.is', ref1, ref2);
+
+@pragma("wasm:prefer-inline")
+int _jsCharCodeAt(WasmExternRef? stringRef, int index) => js
+ .JS<WasmI32>(
+ 'WebAssembly.String.charCodeAt', stringRef, WasmI32.fromInt(index))
+ .toIntUnsigned();
+
+WasmExternRef _jsConcat(WasmExternRef? s1, WasmExternRef? s2) =>
+ js.JS<WasmExternRef>('WebAssembly.String.concat', s1, s2);
+
+@pragma("wasm:prefer-inline")
+WasmExternRef _jsSubstring(
+ WasmExternRef? stringRef, int startIndex, int endIndex) =>
+ js.JS<WasmExternRef>('WebAssembly.String.substring', stringRef,
+ WasmI32.fromInt(startIndex), WasmI32.fromInt(endIndex));
+
+@pragma("wasm:prefer-inline")
+int _jsLength(WasmExternRef? stringRef) =>
+ js.JS<WasmI32>('WebAssembly.String.length', stringRef).toIntUnsigned();
+
+@pragma("wasm:prefer-inline")
+bool _jsEquals(WasmExternRef? s1, WasmExternRef? s2) =>
+ js.JS<WasmI32>('WebAssembly.String.equals', s1, s2).toBool();
+
+@pragma("wasm:prefer-inline")
+int _jsCompare(WasmExternRef? s1, WasmExternRef? s2) =>
+ js.JS<WasmI32>('WebAssembly.String.compare', s1, s2).toIntSigned();
+
+@pragma("wasm:prefer-inline")
+WasmExternRef _jsFromCharCode(int charCode) => js.JS<WasmExternRef>(
+ '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 64d66d6..c87ba9b 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 c618783..ce4c776 100644
--- a/sdk/lib/_wasm/wasm_types.dart
+++ b/sdk/lib/_wasm/wasm_types.dart
@@ -156,6 +156,9 @@
/// `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 fa8147f..0897a79 100644
--- a/sdk/lib/core/errors.dart
+++ b/sdk/lib/core/errors.dart
@@ -306,7 +306,6 @@
/// 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 8375e10..541b845 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 8bac4a0..be735a7 100644
--- a/sdk/lib/libraries.yaml
+++ b/sdk/lib/libraries.yaml
@@ -202,6 +202,8 @@
- _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: