[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: