mirror of
https://github.com/dart-lang/sdk
synced 2024-09-06 00:59:09 +00:00
[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>
This commit is contained in:
parent
d27b887fec
commit
7cceaebd49
|
@ -370,6 +370,11 @@ class Intrinsifier {
|
||||||
codeGen.wrap(node.arguments.positional[0], w.NumType.i64);
|
codeGen.wrap(node.arguments.positional[0], w.NumType.i64);
|
||||||
b.i64_le_u();
|
b.i64_le_u();
|
||||||
return boolType;
|
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:
|
default:
|
||||||
throw 'Unknown WasmI64 member $name';
|
throw 'Unknown WasmI64 member $name';
|
||||||
}
|
}
|
||||||
|
|
|
@ -95,6 +95,23 @@ const jsRuntimeBlobPart3 = r'''
|
||||||
return wrapped;
|
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
|
// Imports
|
||||||
const dart2wasm = {
|
const dart2wasm = {
|
||||||
''';
|
''';
|
||||||
|
|
|
@ -409,6 +409,7 @@ class Chrome extends Browser {
|
||||||
"--no-first-run",
|
"--no-first-run",
|
||||||
"--use-mock-keychain",
|
"--use-mock-keychain",
|
||||||
"--user-data-dir=${userDir.path}",
|
"--user-data-dir=${userDir.path}",
|
||||||
|
"--js-flags=--experimental-wasm-imported-strings",
|
||||||
url,
|
url,
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|
|
@ -604,6 +604,7 @@ class Dart2WasmCompilerConfiguration extends CompilerConfiguration {
|
||||||
final isD8 = runtimeConfiguration is D8RuntimeConfiguration;
|
final isD8 = runtimeConfiguration is D8RuntimeConfiguration;
|
||||||
return [
|
return [
|
||||||
if (isD8) '--turboshaft-wasm',
|
if (isD8) '--turboshaft-wasm',
|
||||||
|
if (isD8) '--experimental-wasm-imported-strings',
|
||||||
'pkg/dart2wasm/bin/run_wasm.js',
|
'pkg/dart2wasm/bin/run_wasm.js',
|
||||||
if (isD8) '--',
|
if (isD8) '--',
|
||||||
'${filename.substring(0, filename.lastIndexOf('.'))}.mjs',
|
'${filename.substring(0, filename.lastIndexOf('.'))}.mjs',
|
||||||
|
|
|
@ -36,6 +36,6 @@ if [[ $D8_OPTIONS ]]; then
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Find the JS runtime based on the input wasm file.
|
# 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[@]}" \
|
"${EXTRA_D8_OPTIONS[@]}" \
|
||||||
"$SDK_DIR/pkg/dart2wasm/bin/run_wasm.js" -- "$(realpath -- "${1%.*}.mjs")" "$@"
|
"$SDK_DIR/pkg/dart2wasm/bin/run_wasm.js" -- "$(realpath -- "${1%.*}.mjs")" "$@"
|
||||||
|
|
42
sdk/lib/_internal/wasm/lib/error_utils.dart
Normal file
42
sdk/lib/_internal/wasm/lib/error_utils.dart
Normal file
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -466,18 +466,30 @@ class JSArrayImpl implements List<JSAny?> {
|
||||||
'(a, l) => a.length = l', toExternRef, newLength.toJS.toExternRef);
|
'(a, l) => a.length = l', toExternRef, newLength.toJS.toExternRef);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@pragma("wasm:prefer-inline")
|
||||||
JSAny? operator [](int index) {
|
JSAny? _getUnchecked(int index) =>
|
||||||
RangeError.checkValueInInterval(index, 0, length - 1);
|
js.JSValue.boxT<JSAny?>(js.JS<WasmExternRef?>(
|
||||||
return js.JSValue.boxT<JSAny?>(js.JS<WasmExternRef?>(
|
'(a, i) => a[i]', toExternRef, index.toJS.toExternRef));
|
||||||
'(a, i) => a[i]', toExternRef, index.toJS.toExternRef));
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
@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) {
|
void operator []=(int index, JSAny? value) {
|
||||||
RangeError.checkValueInInterval(index, 0, length - 1);
|
IndexErrorUtils.checkAssumePositiveLength(index, length);
|
||||||
js.JS<void>('(a, i, v) => a[i] = v', toExternRef, index.toJS.toExternRef,
|
_setUnchecked(index, value);
|
||||||
value.toExternRef);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|
|
@ -4,60 +4,71 @@
|
||||||
|
|
||||||
part of dart._js_types;
|
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 class JSStringImpl implements String {
|
||||||
final WasmExternRef? _ref;
|
final WasmExternRef? _ref;
|
||||||
|
|
||||||
JSStringImpl(this._ref);
|
JSStringImpl(this._ref);
|
||||||
|
|
||||||
|
@pragma("wasm:prefer-inline")
|
||||||
static String? box(WasmExternRef? ref) =>
|
static String? box(WasmExternRef? ref) =>
|
||||||
js.isDartNull(ref) ? null : JSStringImpl(ref);
|
js.isDartNull(ref) ? null : JSStringImpl(ref);
|
||||||
|
|
||||||
|
@pragma("wasm:prefer-inline")
|
||||||
WasmExternRef? get toExternRef => _ref;
|
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")
|
@pragma("wasm:entry-point")
|
||||||
static String interpolate(List<Object?> values) {
|
static String interpolate(List<Object?> values) {
|
||||||
final array = JSArrayImpl.fromLength(values.length);
|
final valuesLength = values.length;
|
||||||
for (int i = 0; i < values.length; i++) {
|
final array = JSArrayImpl.fromLength(valuesLength);
|
||||||
|
for (int i = 0; i < valuesLength; i++) {
|
||||||
final o = values[i];
|
final o = values[i];
|
||||||
final s = o.toString();
|
final s = o.toString();
|
||||||
final jsString =
|
final jsString =
|
||||||
s is JSStringImpl ? js.JSValue.boxT<JSAny?>(s.toExternRef) : s.toJS;
|
s is JSStringImpl ? js.JSValue.boxT<JSAny?>(s.toExternRef) : s.toJS;
|
||||||
array[i] = jsString;
|
array._setUnchecked(i, jsString);
|
||||||
}
|
}
|
||||||
return JSStringImpl(
|
return JSStringImpl(
|
||||||
js.JS<WasmExternRef?>("a => a.join('')", array.toExternRef));
|
js.JS<WasmExternRef?>("a => a.join('')", array.toExternRef));
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@pragma("wasm:prefer-inline")
|
||||||
int codeUnitAt(int index) {
|
int codeUnitAt(int index) {
|
||||||
RangeError.checkValueInInterval(index, 0, length - 1);
|
final length = this.length;
|
||||||
return js
|
IndexErrorUtils.checkAssumePositiveLength(index, length);
|
||||||
.JS<double>('(s, i) => s.charCodeAt(i)', toExternRef,
|
return _codeUnitAtUnchecked(index);
|
||||||
index.toDouble().toExternRef)
|
}
|
||||||
.toInt();
|
|
||||||
|
@pragma("wasm:prefer-inline")
|
||||||
|
int _codeUnitAtUnchecked(int index) {
|
||||||
|
return _jsCharCodeAt(toExternRef, index);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Iterable<Match> allMatches(String string, [int start = 0]) {
|
Iterable<Match> allMatches(String string, [int start = 0]) {
|
||||||
if (0 > start || start > string.length) {
|
final stringLength = string.length;
|
||||||
throw new RangeError.range(start, 0, string.length);
|
RangeErrorUtils.checkValueBetweenZeroAndPositiveMax(start, stringLength);
|
||||||
}
|
|
||||||
return StringAllMatchesIterable(string, this, start);
|
return StringAllMatchesIterable(string, this, start);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Match? matchAsPrefix(String string, [int start = 0]) {
|
Match? matchAsPrefix(String string, [int start = 0]) {
|
||||||
if (start < 0 || start > string.length) {
|
final stringLength = string.length;
|
||||||
throw new RangeError.range(start, 0, string.length);
|
RangeErrorUtils.checkValueBetweenZeroAndPositiveMax(start, stringLength);
|
||||||
}
|
final length = this.length;
|
||||||
if (start + length > string.length) return null;
|
if (start + length > stringLength) return null;
|
||||||
// TODO(lrn): See if this can be optimized.
|
// TODO(lrn): See if this can be optimized.
|
||||||
for (int i = 0; i < length; i++) {
|
for (int i = 0; i < length; i++) {
|
||||||
if (string.codeUnitAt(start + i) != codeUnitAt(i)) {
|
if (string.codeUnitAt(start + i) != codeUnitAt(i)) {
|
||||||
|
@ -70,8 +81,7 @@ final class JSStringImpl implements String {
|
||||||
@override
|
@override
|
||||||
String operator +(String other) {
|
String operator +(String other) {
|
||||||
if (other is JSStringImpl) {
|
if (other is JSStringImpl) {
|
||||||
return JSStringImpl(js.JS<WasmExternRef?>(
|
return JSStringImpl(_jsConcat(toExternRef, other.toExternRef));
|
||||||
'(a, b) => a + b', toExternRef, other.toExternRef));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO(joshualitt): Refactor `string_patch.dart` so we can directly
|
// TODO(joshualitt): Refactor `string_patch.dart` so we can directly
|
||||||
|
@ -81,7 +91,8 @@ final class JSStringImpl implements String {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
bool endsWith(String other) {
|
bool endsWith(String other) {
|
||||||
int otherLength = other.length;
|
final otherLength = other.length;
|
||||||
|
final length = this.length;
|
||||||
if (otherLength > length) return false;
|
if (otherLength > length) return false;
|
||||||
return other == substring(length - otherLength);
|
return other == substring(length - otherLength);
|
||||||
}
|
}
|
||||||
|
@ -102,6 +113,7 @@ final class JSStringImpl implements String {
|
||||||
} else {
|
} else {
|
||||||
StringBuffer result = StringBuffer();
|
StringBuffer result = StringBuffer();
|
||||||
result.write(to);
|
result.write(to);
|
||||||
|
final length = this.length;
|
||||||
for (int i = 0; i < length; i++) {
|
for (int i = 0; i < length; i++) {
|
||||||
result.write(this[i]);
|
result.write(this[i]);
|
||||||
result.write(to);
|
result.write(to);
|
||||||
|
@ -143,12 +155,13 @@ final class JSStringImpl implements String {
|
||||||
if (onMatch == null) onMatch = _matchString;
|
if (onMatch == null) onMatch = _matchString;
|
||||||
if (onNonMatch == null) onNonMatch = _stringIdentity;
|
if (onNonMatch == null) onNonMatch = _stringIdentity;
|
||||||
if (from is String) {
|
if (from is String) {
|
||||||
int patternLength = from.length;
|
final patternLength = from.length;
|
||||||
if (patternLength == 0) {
|
if (patternLength == 0) {
|
||||||
// Pattern is the empty string.
|
// Pattern is the empty string.
|
||||||
StringBuffer buffer = StringBuffer();
|
StringBuffer buffer = StringBuffer();
|
||||||
int i = 0;
|
int i = 0;
|
||||||
buffer.write(onNonMatch(""));
|
buffer.write(onNonMatch(""));
|
||||||
|
final length = this.length;
|
||||||
while (i < length) {
|
while (i < length) {
|
||||||
buffer.write(onMatch(StringMatch(i, this, "")));
|
buffer.write(onMatch(StringMatch(i, this, "")));
|
||||||
// Special case to avoid splitting a surrogate pair.
|
// Special case to avoid splitting a surrogate pair.
|
||||||
|
@ -172,6 +185,7 @@ final class JSStringImpl implements String {
|
||||||
}
|
}
|
||||||
StringBuffer buffer = StringBuffer();
|
StringBuffer buffer = StringBuffer();
|
||||||
int startIndex = 0;
|
int startIndex = 0;
|
||||||
|
final length = this.length;
|
||||||
while (startIndex < length) {
|
while (startIndex < length) {
|
||||||
int position = indexOf(from, startIndex);
|
int position = indexOf(from, startIndex);
|
||||||
if (position == -1) {
|
if (position == -1) {
|
||||||
|
@ -212,7 +226,7 @@ final class JSStringImpl implements String {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String replaceFirst(Pattern from, String to, [int startIndex = 0]) {
|
String replaceFirst(Pattern from, String to, [int startIndex = 0]) {
|
||||||
RangeError.checkValueInInterval(startIndex, 0, length, "startIndex");
|
RangeErrorUtils.checkValueBetweenZeroAndPositiveMax(startIndex, length);
|
||||||
if (from is String) {
|
if (from is String) {
|
||||||
int index = indexOf(from, startIndex);
|
int index = indexOf(from, startIndex);
|
||||||
if (index < 0) return this;
|
if (index < 0) return this;
|
||||||
|
@ -233,7 +247,7 @@ final class JSStringImpl implements String {
|
||||||
@override
|
@override
|
||||||
String replaceFirstMapped(Pattern from, String replace(Match match),
|
String replaceFirstMapped(Pattern from, String replace(Match match),
|
||||||
[int startIndex = 0]) {
|
[int startIndex = 0]) {
|
||||||
RangeError.checkValueInInterval(startIndex, 0, this.length, "startIndex");
|
RangeErrorUtils.checkValueBetweenZeroAndPositiveMax(startIndex, length);
|
||||||
Iterator<Match> matches = from.allMatches(this, startIndex).iterator;
|
Iterator<Match> matches = from.allMatches(this, startIndex).iterator;
|
||||||
if (!matches.moveNext()) return this;
|
if (!matches.moveNext()) return this;
|
||||||
Match match = matches.current;
|
Match match = matches.current;
|
||||||
|
@ -292,15 +306,14 @@ final class JSStringImpl implements String {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String replaceRange(int start, int? end, String replacement) {
|
String replaceRange(int start, int? end, String replacement) {
|
||||||
final e = RangeError.checkValidRange(start, end, length);
|
end ??= length;
|
||||||
return _replaceRange(start, e, replacement);
|
RangeErrorUtils.checkValidRangePositiveLength(start, end, length);
|
||||||
|
return _replaceRange(start, end, replacement);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
bool startsWith(Pattern pattern, [int index = 0]) {
|
bool startsWith(Pattern pattern, [int index = 0]) {
|
||||||
if (index < 0 || index > length) {
|
RangeErrorUtils.checkValueBetweenZeroAndPositiveMax(index, length);
|
||||||
throw RangeError.range(index, 0, length);
|
|
||||||
}
|
|
||||||
if (pattern is String) {
|
if (pattern is String) {
|
||||||
final patternLength = pattern.length;
|
final patternLength = pattern.length;
|
||||||
final endIndex = index + patternLength;
|
final endIndex = index + patternLength;
|
||||||
|
@ -312,9 +325,9 @@ final class JSStringImpl implements String {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String substring(int start, [int? end]) {
|
String substring(int start, [int? end]) {
|
||||||
end = RangeError.checkValidRange(start, end, this.length);
|
end ??= length;
|
||||||
return JSStringImpl(js.JS<WasmExternRef?>('(o, s, i) => o.substring(s, i)',
|
RangeErrorUtils.checkValidRangePositiveLength(start, end, length);
|
||||||
toExternRef, start.toDouble(), end.toDouble()));
|
return JSStringImpl(_jsSubstring(toExternRef, start, end));
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -398,9 +411,10 @@ final class JSStringImpl implements String {
|
||||||
|
|
||||||
/// Finds the index of the first non-whitespace character, or the
|
/// Finds the index of the first non-whitespace character, or the
|
||||||
/// end of the string. Start looking at position [index].
|
/// end of the string. Start looking at position [index].
|
||||||
static int _skipLeadingWhitespace(String string, int index) {
|
static int _skipLeadingWhitespace(JSStringImpl string, int index) {
|
||||||
while (index < string.length) {
|
final stringLength = string.length;
|
||||||
int codeUnit = string.codeUnitAt(index);
|
while (index < stringLength) {
|
||||||
|
int codeUnit = string._codeUnitAtUnchecked(index);
|
||||||
if (codeUnit != spaceCodeUnit &&
|
if (codeUnit != spaceCodeUnit &&
|
||||||
codeUnit != carriageReturnCodeUnit &&
|
codeUnit != carriageReturnCodeUnit &&
|
||||||
!_isWhitespace(codeUnit)) {
|
!_isWhitespace(codeUnit)) {
|
||||||
|
@ -413,9 +427,9 @@ final class JSStringImpl implements String {
|
||||||
|
|
||||||
/// Finds the index after the last non-whitespace character, or 0.
|
/// Finds the index after the last non-whitespace character, or 0.
|
||||||
/// Start looking at position [index - 1].
|
/// Start looking at position [index - 1].
|
||||||
static int _skipTrailingWhitespace(String string, int index) {
|
static int _skipTrailingWhitespace(JSStringImpl string, int index) {
|
||||||
while (index > 0) {
|
while (index > 0) {
|
||||||
int codeUnit = string.codeUnitAt(index - 1);
|
int codeUnit = string._codeUnitAtUnchecked(index - 1);
|
||||||
if (codeUnit != spaceCodeUnit &&
|
if (codeUnit != spaceCodeUnit &&
|
||||||
codeUnit != carriageReturnCodeUnit &&
|
codeUnit != carriageReturnCodeUnit &&
|
||||||
!_isWhitespace(codeUnit)) {
|
!_isWhitespace(codeUnit)) {
|
||||||
|
@ -426,7 +440,7 @@ final class JSStringImpl implements String {
|
||||||
return index;
|
return index;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Dart2Wasm can't use JavaScript trim directly,
|
// dart2wasm can't use JavaScript trim directly,
|
||||||
// because JavaScript does not trim
|
// because JavaScript does not trim
|
||||||
// the NEXT LINE (NEL) character (0x85).
|
// the NEXT LINE (NEL) character (0x85).
|
||||||
@override
|
@override
|
||||||
|
@ -435,26 +449,27 @@ final class JSStringImpl implements String {
|
||||||
// either end of the string.
|
// either end of the string.
|
||||||
final result =
|
final result =
|
||||||
JSStringImpl(js.JS<WasmExternRef?>('s => s.trim()', toExternRef));
|
JSStringImpl(js.JS<WasmExternRef?>('s => s.trim()', toExternRef));
|
||||||
if (result.length == 0) return result;
|
final resultLength = result.length;
|
||||||
int firstCode = result.codeUnitAt(0);
|
if (resultLength == 0) return result;
|
||||||
|
int firstCode = result._codeUnitAtUnchecked(0);
|
||||||
int startIndex = 0;
|
int startIndex = 0;
|
||||||
if (firstCode == nelCodeUnit) {
|
if (firstCode == nelCodeUnit) {
|
||||||
startIndex = _skipLeadingWhitespace(result, 1);
|
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.
|
// We know that there is at least one character that is non-whitespace.
|
||||||
// Therefore we don't need to verify that endIndex > startIndex.
|
// Therefore we don't need to verify that endIndex > startIndex.
|
||||||
int lastCode = result.codeUnitAt(endIndex - 1);
|
int lastCode = result.codeUnitAt(endIndex - 1);
|
||||||
if (lastCode == nelCodeUnit) {
|
if (lastCode == nelCodeUnit) {
|
||||||
endIndex = _skipTrailingWhitespace(result, endIndex - 1);
|
endIndex = _skipTrailingWhitespace(result, endIndex - 1);
|
||||||
}
|
}
|
||||||
if (startIndex == 0 && endIndex == result.length) return result;
|
if (startIndex == 0 && endIndex == resultLength) return result;
|
||||||
return substring(startIndex, endIndex);
|
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).
|
// the NEXT LINE character (0x85).
|
||||||
@override
|
@override
|
||||||
String trimLeft() {
|
String trimLeft() {
|
||||||
|
@ -463,17 +478,18 @@ final class JSStringImpl implements String {
|
||||||
int startIndex = 0;
|
int startIndex = 0;
|
||||||
final result =
|
final result =
|
||||||
JSStringImpl(js.JS<WasmExternRef?>('s => s.trimLeft()', toExternRef));
|
JSStringImpl(js.JS<WasmExternRef?>('s => s.trimLeft()', toExternRef));
|
||||||
if (result.length == 0) return result;
|
final resultLength = result.length;
|
||||||
int firstCode = result.codeUnitAt(0);
|
if (resultLength == 0) return result;
|
||||||
|
int firstCode = result._codeUnitAtUnchecked(0);
|
||||||
if (firstCode == nelCodeUnit) {
|
if (firstCode == nelCodeUnit) {
|
||||||
startIndex = _skipLeadingWhitespace(result, 1);
|
startIndex = _skipLeadingWhitespace(result, 1);
|
||||||
}
|
}
|
||||||
if (startIndex == 0) return result;
|
if (startIndex == 0) return result;
|
||||||
if (startIndex == result.length) return "";
|
if (startIndex == resultLength) return "";
|
||||||
return result.substring(startIndex);
|
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).
|
// the NEXT LINE character (0x85).
|
||||||
@override
|
@override
|
||||||
String trimRight() {
|
String trimRight() {
|
||||||
|
@ -481,14 +497,15 @@ final class JSStringImpl implements String {
|
||||||
// string.
|
// string.
|
||||||
final result =
|
final result =
|
||||||
JSStringImpl(js.JS<WasmExternRef?>('s => s.trimRight()', toExternRef));
|
JSStringImpl(js.JS<WasmExternRef?>('s => s.trimRight()', toExternRef));
|
||||||
int endIndex = result.length;
|
final resultLength = result.length;
|
||||||
|
int endIndex = resultLength;
|
||||||
if (endIndex == 0) return result;
|
if (endIndex == 0) return result;
|
||||||
int lastCode = result.codeUnitAt(endIndex - 1);
|
int lastCode = result.codeUnitAt(endIndex - 1);
|
||||||
if (lastCode == nelCodeUnit) {
|
if (lastCode == nelCodeUnit) {
|
||||||
endIndex = _skipTrailingWhitespace(result, endIndex - 1);
|
endIndex = _skipTrailingWhitespace(result, endIndex - 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (endIndex == result.length) return result;
|
if (endIndex == resultLength) return result;
|
||||||
if (endIndex == 0) return "";
|
if (endIndex == 0) return "";
|
||||||
return result.substring(0, endIndex);
|
return result.substring(0, endIndex);
|
||||||
}
|
}
|
||||||
|
@ -503,16 +520,16 @@ final class JSStringImpl implements String {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String padLeft(int width, [String padding = ' ']) {
|
String padLeft(int width, [String padding = ' ']) {
|
||||||
int delta = width - this.length;
|
int delta = width - length;
|
||||||
if (delta <= 0) return this;
|
if (delta <= 0) return this;
|
||||||
return padding * delta + this;
|
return (padding * delta) + this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String padRight(int width, [String padding = ' ']) {
|
String padRight(int width, [String padding = ' ']) {
|
||||||
int delta = width - this.length;
|
int delta = width - length;
|
||||||
if (delta <= 0) return this;
|
if (delta <= 0) return this;
|
||||||
return this + padding * delta;
|
return this + (padding * delta);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -528,9 +545,8 @@ final class JSStringImpl implements String {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
int indexOf(Pattern pattern, [int start = 0]) {
|
int indexOf(Pattern pattern, [int start = 0]) {
|
||||||
if (start < 0 || start > this.length) {
|
final length = this.length;
|
||||||
throw RangeError.range(start, 0, this.length);
|
RangeErrorUtils.checkValueBetweenZeroAndPositiveMax(start, length);
|
||||||
}
|
|
||||||
if (pattern is JSStringImpl) {
|
if (pattern is JSStringImpl) {
|
||||||
return _jsIndexOf(pattern.toExternRef, start);
|
return _jsIndexOf(pattern.toExternRef, start);
|
||||||
} else if (pattern is String) {
|
} else if (pattern is String) {
|
||||||
|
@ -539,7 +555,7 @@ final class JSStringImpl implements String {
|
||||||
Match? match = js.firstMatchAfter(pattern, this, start);
|
Match? match = js.firstMatchAfter(pattern, this, start);
|
||||||
return (match == null) ? -1 : match.start;
|
return (match == null) ? -1 : match.start;
|
||||||
} else {
|
} else {
|
||||||
for (int i = start; i <= this.length; i++) {
|
for (int i = start; i <= length; i++) {
|
||||||
if (pattern.matchAsPrefix(this, i) != null) return i;
|
if (pattern.matchAsPrefix(this, i) != null) return i;
|
||||||
}
|
}
|
||||||
return -1;
|
return -1;
|
||||||
|
@ -553,10 +569,11 @@ final class JSStringImpl implements String {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
int lastIndexOf(Pattern pattern, [int? start]) {
|
int lastIndexOf(Pattern pattern, [int? start]) {
|
||||||
|
final length = this.length;
|
||||||
if (start == null) {
|
if (start == null) {
|
||||||
start = length;
|
start = length;
|
||||||
} else if (start < 0 || start > this.length) {
|
} else {
|
||||||
throw RangeError.range(start, 0, this.length);
|
RangeErrorUtils.checkValueBetweenZeroAndPositiveMax(start, length);
|
||||||
}
|
}
|
||||||
if (pattern is JSStringImpl) {
|
if (pattern is JSStringImpl) {
|
||||||
if (start + pattern.length > length) {
|
if (start + pattern.length > length) {
|
||||||
|
@ -577,9 +594,8 @@ final class JSStringImpl implements String {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
bool contains(Pattern other, [int startIndex = 0]) {
|
bool contains(Pattern other, [int startIndex = 0]) {
|
||||||
if (startIndex < 0 || startIndex > this.length) {
|
final length = this.length;
|
||||||
throw RangeError.range(startIndex, 0, this.length);
|
RangeErrorUtils.checkValueBetweenZeroAndPositiveMax(startIndex, length);
|
||||||
}
|
|
||||||
if (other is String) {
|
if (other is String) {
|
||||||
return indexOf(other, startIndex) >= 0;
|
return indexOf(other, startIndex) >= 0;
|
||||||
} else if (other is js.JSSyntaxRegExp) {
|
} 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.
|
/// This must be kept in sync with `StringBase.hashCode` in string_patch.dart.
|
||||||
/// TODO(joshualitt): Find some way to cache the hash code.
|
/// TODO(joshualitt): Find some way to cache the hash code.
|
||||||
@override
|
@override
|
||||||
int get hashCode {
|
int get hashCode {
|
||||||
int hash = 0;
|
int hash = 0;
|
||||||
|
final length = this.length;
|
||||||
for (int i = 0; i < length; i++) {
|
for (int i = 0; i < length; i++) {
|
||||||
hash = stringCombineHashes(hash, codeUnitAt(i));
|
hash = stringCombineHashes(hash, _codeUnitAtUnchecked(i));
|
||||||
}
|
}
|
||||||
return stringFinalizeHash(hash);
|
return stringFinalizeHash(hash);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
int get length => js.JS<double>('s => s.length', toExternRef).toInt();
|
@pragma("wasm:prefer-inline")
|
||||||
|
|
||||||
@override
|
|
||||||
String operator [](int index) {
|
String operator [](int index) {
|
||||||
RangeError.checkValueInInterval(index, 0, length - 1);
|
final length = this.length;
|
||||||
return JSStringImpl(js.JS<WasmExternRef?>(
|
IndexErrorUtils.checkAssumePositiveLength(index, length);
|
||||||
'(s, i) => s[i]', toExternRef, index.toDouble().toExternRef));
|
return JSStringImpl(_jsFromCharCode(_codeUnitAtUnchecked(index)));
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -621,25 +630,34 @@ final class JSStringImpl implements String {
|
||||||
if (identical(this, other)) {
|
if (identical(this, other)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
if (other is JSStringImpl && length == other.length) {
|
|
||||||
return js.areEqualInJS(toExternRef, other.toExternRef);
|
if (other is JSStringImpl) {
|
||||||
} else if (other is String && length == other.length) {
|
return _jsEquals(toExternRef, other.toExternRef);
|
||||||
|
}
|
||||||
|
|
||||||
|
final length = this.length;
|
||||||
|
if (other is String && length == other.length) {
|
||||||
for (int i = 0; i < length; i++) {
|
for (int i = 0; i < length; i++) {
|
||||||
if (codeUnitAt(i) != other.codeUnitAt(i)) {
|
if (_codeUnitAtUnchecked(i) != other.codeUnitAt(i)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
int compareTo(String other) {
|
int compareTo(String other) {
|
||||||
int otherLength = other.length;
|
if (other is JSStringImpl) {
|
||||||
int len = (length < otherLength) ? length : otherLength;
|
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++) {
|
for (int i = 0; i < len; i++) {
|
||||||
int thisCodeUnit = this.codeUnitAt(i);
|
int thisCodeUnit = _codeUnitAtUnchecked(i);
|
||||||
int otherCodeUnit = other.codeUnitAt(i);
|
int otherCodeUnit = other.codeUnitAt(i);
|
||||||
if (thisCodeUnit < otherCodeUnit) {
|
if (thisCodeUnit < otherCodeUnit) {
|
||||||
return -1;
|
return -1;
|
||||||
|
@ -657,10 +675,10 @@ final class JSStringImpl implements String {
|
||||||
String toString() => js.stringify(toExternRef);
|
String toString() => js.stringify(toExternRef);
|
||||||
|
|
||||||
int firstNonWhitespace() {
|
int firstNonWhitespace() {
|
||||||
final len = this.length;
|
final length = this.length;
|
||||||
int first = 0;
|
int first = 0;
|
||||||
for (; first < len; first++) {
|
for (; first < length; first++) {
|
||||||
if (!_isWhitespace(this.codeUnitAt(first))) {
|
if (!_isWhitespace(_codeUnitAtUnchecked(first))) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -668,9 +686,9 @@ final class JSStringImpl implements String {
|
||||||
}
|
}
|
||||||
|
|
||||||
int lastNonWhitespace() {
|
int lastNonWhitespace() {
|
||||||
int last = this.length - 1;
|
int last = length - 1;
|
||||||
for (; last >= 0; last--) {
|
for (; last >= 0; last--) {
|
||||||
if (!_isWhitespace(this.codeUnitAt(last))) {
|
if (!_isWhitespace(_codeUnitAtUnchecked(last))) {
|
||||||
break;
|
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")
|
@pragma("wasm:export", "\$jsStringToJSStringImpl")
|
||||||
JSStringImpl _jsStringToJSStringImpl(WasmExternRef? string) =>
|
JSStringImpl _jsStringToJSStringImpl(WasmExternRef? string) =>
|
||||||
JSStringImpl(string);
|
JSStringImpl(string);
|
||||||
|
@ -688,3 +710,34 @@ WasmExternRef? _jsStringFromJSStringImpl(JSStringImpl string) =>
|
||||||
|
|
||||||
bool _jsIdentical(WasmExternRef? ref1, WasmExternRef? ref2) =>
|
bool _jsIdentical(WasmExternRef? ref1, WasmExternRef? ref2) =>
|
||||||
js.JS<bool>('Object.is', ref1, 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));
|
||||||
|
|
|
@ -13,6 +13,7 @@
|
||||||
/// change in the future.
|
/// change in the future.
|
||||||
library dart._js_types;
|
library dart._js_types;
|
||||||
|
|
||||||
|
import 'dart:_error_utils';
|
||||||
import 'dart:_internal';
|
import 'dart:_internal';
|
||||||
import 'dart:_js_helper' as js;
|
import 'dart:_js_helper' as js;
|
||||||
import 'dart:_string_helper';
|
import 'dart:_string_helper';
|
||||||
|
|
|
@ -156,6 +156,9 @@ class WasmI64 extends _WasmInt {
|
||||||
|
|
||||||
/// `i64.le_u`.
|
/// `i64.le_u`.
|
||||||
external bool leU(WasmI64 other);
|
external bool leU(WasmI64 other);
|
||||||
|
|
||||||
|
/// `i64.lt_u`.
|
||||||
|
external bool ltU(WasmI64 other);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The Wasm `f32` type.
|
/// The Wasm `f32` type.
|
||||||
|
|
|
@ -306,7 +306,6 @@ class RangeError extends ArgumentError {
|
||||||
/// name and message text of the thrown error.
|
/// name and message text of the thrown error.
|
||||||
///
|
///
|
||||||
/// Returns [value] if it is in the interval.
|
/// Returns [value] if it is in the interval.
|
||||||
@pragma("wasm:entry-point")
|
|
||||||
static int checkValueInInterval(int value, int minValue, int maxValue,
|
static int checkValueInInterval(int value, int minValue, int maxValue,
|
||||||
[String? name, String? message]) {
|
[String? name, String? message]) {
|
||||||
if (value < minValue || value > maxValue) {
|
if (value < minValue || value > maxValue) {
|
||||||
|
|
|
@ -239,6 +239,9 @@
|
||||||
"_internal/wasm/lib/int_common_patch.dart"
|
"_internal/wasm/lib/int_common_patch.dart"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
"_error_utils": {
|
||||||
|
"uri": "_internal/wasm/lib/error_utils.dart"
|
||||||
|
},
|
||||||
"_http": {
|
"_http": {
|
||||||
"uri": "_http/http.dart"
|
"uri": "_http/http.dart"
|
||||||
},
|
},
|
||||||
|
|
|
@ -202,6 +202,8 @@ wasm_common:
|
||||||
- _internal/wasm/lib/boxed_double.dart
|
- _internal/wasm/lib/boxed_double.dart
|
||||||
- _internal/wasm/lib/boxed_int.dart
|
- _internal/wasm/lib/boxed_int.dart
|
||||||
- _internal/wasm/lib/int_common_patch.dart
|
- _internal/wasm/lib/int_common_patch.dart
|
||||||
|
_error_utils:
|
||||||
|
uri: _internal/wasm/lib/error_utils.dart
|
||||||
_http:
|
_http:
|
||||||
uri: _http/http.dart
|
uri: _http/http.dart
|
||||||
_internal:
|
_internal:
|
||||||
|
|
Loading…
Reference in a new issue