[dart2wasm] Remove runtime blob to/from js string helpers, use JS<>() instead

This removes the special runtime blob for string conversions, instead we
make 2 functions for conversions use our built-in `JS<>()` support.

This will allow specializing the implementation in a future CL.

Change-Id: I06df25ed805042c0a3e2efb966eaebd1ce67dc0c
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/372660
Commit-Queue: Martin Kustermann <kustermann@google.com>
Reviewed-by: Ömer Ağacan <omersa@google.com>
diff --git a/pkg/dart2wasm/lib/js/runtime_blob.dart b/pkg/dart2wasm/lib/js/runtime_blob.dart
index 7bcd3f43..eec7dee33 100644
--- a/pkg/dart2wasm/lib/js/runtime_blob.dart
+++ b/pkg/dart2wasm/lib/js/runtime_blob.dart
@@ -16,56 +16,6 @@
     let dartInstance;
 ''';
 
-// Break to support system dependent conversion routines.
-const jsRuntimeBlobPart2Regular = r'''
-    function stringFromDartString(string) {
-        const totalLength = dartInstance.exports.$stringLength(string);
-        let result = '';
-        let index = 0;
-        while (index < totalLength) {
-          let chunkLength = Math.min(totalLength - index, 0xFFFF);
-          const array = new Array(chunkLength);
-          for (let i = 0; i < chunkLength; i++) {
-              array[i] = dartInstance.exports.$stringRead(string, index++);
-          }
-          result += String.fromCharCode(...array);
-        }
-        return result;
-    }
-
-    function stringToDartString(string) {
-        const length = string.length;
-        let range = 0;
-        for (let i = 0; i < length; i++) {
-            range |= string.codePointAt(i);
-        }
-        if (range < 256) {
-            const dartString = dartInstance.exports.$stringAllocate1(length);
-            for (let i = 0; i < length; i++) {
-                dartInstance.exports.$stringWrite1(dartString, i, string.codePointAt(i));
-            }
-            return dartString;
-        } else {
-            const dartString = dartInstance.exports.$stringAllocate2(length);
-            for (let i = 0; i < length; i++) {
-                dartInstance.exports.$stringWrite2(dartString, i, string.charCodeAt(i));
-            }
-            return dartString;
-        }
-    }
-''';
-
-// Conversion functions for JSCM.
-const jsRuntimeBlobPart2JSCM = r'''
-    function stringFromDartString(string) {
-      return dartInstance.exports.$jsStringFromJSStringImpl(string);
-    }
-
-    function stringToDartString(string) {
-      return dartInstance.exports.$jsStringToJSStringImpl(string);
-    }
-''';
-
 const jsRuntimeBlobPart3 = r'''
     // Prints to the console
     function printToConsole(value) {
diff --git a/pkg/dart2wasm/lib/js/runtime_generator.dart b/pkg/dart2wasm/lib/js/runtime_generator.dart
index 10bbebf..c0932ff 100644
--- a/pkg/dart2wasm/lib/js/runtime_generator.dart
+++ b/pkg/dart2wasm/lib/js/runtime_generator.dart
@@ -77,7 +77,6 @@
     }
     return '''
 $jsRuntimeBlobPart1
-${mode == wasm_target.Mode.jsCompatibility ? jsRuntimeBlobPart2JSCM : jsRuntimeBlobPart2Regular}
 $jsRuntimeBlobPart3
 ${usedJSMethods.join(',\n')}
 $jsRuntimeBlobPart4
diff --git a/sdk/lib/_internal/wasm/lib/boxed_double.dart b/sdk/lib/_internal/wasm/lib/boxed_double.dart
index 149d448..1fcea1e 100644
--- a/sdk/lib/_internal/wasm/lib/boxed_double.dart
+++ b/sdk/lib/_internal/wasm/lib/boxed_double.dart
@@ -3,7 +3,9 @@
 // BSD-style license that can be found in the LICENSE file.
 
 import 'dart:_internal' show doubleToIntBits, intBitsToDouble;
-import 'dart:_js_helper' show JS;
+import 'dart:_js_helper' show JS, jsStringToDartString;
+import 'dart:_string';
+import 'dart:_wasm';
 
 @pragma("wasm:entry-point")
 final class _BoxedDouble extends double {
@@ -330,7 +332,8 @@
         return "0.0";
       }
     }
-    String result = JS<String>("v => stringToDartString(v.toString())", value);
+    String result = jsStringToDartString(
+        JSStringImpl(JS<WasmExternRef>("v => v.toString()", value)));
     if (this % 1.0 == 0.0 && result.indexOf('e') == -1) {
       result = '$result.0';
     }
@@ -369,10 +372,11 @@
     return result;
   }
 
-  String _toStringAsFixed(int fractionDigits) => JS<String>(
-      "(d, digits) => stringToDartString(d.toFixed(digits))",
-      value,
-      fractionDigits.toDouble());
+  String _toStringAsFixed(int fractionDigits) =>
+      jsStringToDartString(JSStringImpl(JS<WasmExternRef>(
+          "(d, digits) => d.toFixed(digits)",
+          value,
+          fractionDigits.toDouble())));
 
   String toStringAsExponential([int? fractionDigits]) {
     // See ECMAScript-262, 15.7.4.6 for details.
@@ -398,12 +402,11 @@
   }
 
   String _toStringAsExponential(int? fractionDigits) {
-    if (fractionDigits == null) {
-      return JS<String>("d => stringToDartString(d.toExponential())", value);
-    } else {
-      return JS<String>("(d, f) => stringToDartString(d.toExponential(f))",
-          value, fractionDigits.toDouble());
-    }
+    final jsString = JSStringImpl(fractionDigits == null
+        ? JS<WasmExternRef>("d => d.toExponential()", value)
+        : JS<WasmExternRef>(
+            "(d, f) => d.toExponential(f)", value, fractionDigits.toDouble()));
+    return jsStringToDartString(jsString);
   }
 
   String toStringAsPrecision(int precision) {
@@ -427,10 +430,11 @@
     return result;
   }
 
-  String _toStringAsPrecision(int fractionDigits) => JS<String>(
-      "(d, precision) => stringToDartString(d.toPrecision(precision))",
-      value,
-      fractionDigits.toDouble());
+  String _toStringAsPrecision(int fractionDigits) =>
+      jsStringToDartString(JSStringImpl(JS<WasmExternRef>(
+          "(d, precision) => d.toPrecision(precision)",
+          value,
+          fractionDigits.toDouble())));
 
   // Order is: NaN > Infinity > ... > 0.0 > -0.0 > ... > -Infinity.
   int compareTo(num other) {
diff --git a/sdk/lib/_internal/wasm/lib/core_patch.dart b/sdk/lib/_internal/wasm/lib/core_patch.dart
index 483b8ee..6181e8e 100644
--- a/sdk/lib/_internal/wasm/lib/core_patch.dart
+++ b/sdk/lib/_internal/wasm/lib/core_patch.dart
@@ -29,11 +29,17 @@
 
 import "dart:_internal" as _internal;
 
-import 'dart:_js_helper' show JS, JSSyntaxRegExp, quoteStringForRegExp;
+import 'dart:_js_helper'
+    show
+        JS,
+        JSSyntaxRegExp,
+        quoteStringForRegExp,
+        jsStringFromDartString,
+        jsStringToDartString;
 
 import 'dart:_list';
 
-import 'dart:_string' show JSStringImpl;
+import 'dart:_string' show JSStringImpl, JSStringImplExt;
 
 import "dart:collection"
     show
diff --git a/sdk/lib/_internal/wasm/lib/date_patch_patch.dart b/sdk/lib/_internal/wasm/lib/date_patch_patch.dart
index f1e9e03..97966ed 100644
--- a/sdk/lib/_internal/wasm/lib/date_patch_patch.dart
+++ b/sdk/lib/_internal/wasm/lib/date_patch_patch.dart
@@ -4,7 +4,9 @@
 
 import "dart:_internal" show patch;
 
-import 'dart:_js_helper' show JS;
+import 'dart:_js_helper' show JS, jsStringToDartString;
+import 'dart:_string';
+import 'dart:_wasm';
 
 @patch
 class DateTime {
@@ -14,15 +16,16 @@
 
   @patch
   static String _timeZoneNameForClampedSeconds(int secondsSinceEpoch) =>
-      JS<String>(r"""secondsSinceEpoch => {
+      jsStringToDartString(
+          JSStringImpl(JS<WasmExternRef>(r"""secondsSinceEpoch => {
         const date = new Date(secondsSinceEpoch * 1000);
         const match = /\((.*)\)/.exec(date.toString());
         if (match == null) {
             // This should never happen on any recent browser.
             return '';
         }
-        return stringToDartString(match[1]);
-      }""", secondsSinceEpoch.toDouble());
+        return match[1];
+      }""", secondsSinceEpoch.toDouble())));
 
   // In Dart, the offset is the difference between local time and UTC,
   // while in JS, the offset is the difference between UTC and local time.
diff --git a/sdk/lib/_internal/wasm/lib/double_patch.dart b/sdk/lib/_internal/wasm/lib/double_patch.dart
index dd98a3b..aa74e9e 100644
--- a/sdk/lib/_internal/wasm/lib/double_patch.dart
+++ b/sdk/lib/_internal/wasm/lib/double_patch.dart
@@ -29,12 +29,11 @@
     // - a Dart double literal
     // We do allow leading or trailing whitespace.
     double result = JS<double>(r"""s => {
-      const jsSource = stringFromDartString(s);
-      if (!/^\s*[+-]?(?:Infinity|NaN|(?:\.\d+|\d+(?:\.\d*)?)(?:[eE][+-]?\d+)?)\s*$/.test(jsSource)) {
+      if (!/^\s*[+-]?(?:Infinity|NaN|(?:\.\d+|\d+(?:\.\d*)?)(?:[eE][+-]?\d+)?)\s*$/.test(s)) {
         return NaN;
       }
-      return parseFloat(jsSource);
-    }""", source);
+      return parseFloat(s);
+    }""", jsStringFromDartString(source).toExternRef);
     if (result.isNaN) {
       String trimmed = source.trim();
       if (!(trimmed == 'NaN' || trimmed == '+NaN' || trimmed == '-NaN')) {
diff --git a/sdk/lib/_internal/wasm/lib/internal_patch.dart b/sdk/lib/_internal/wasm/lib/internal_patch.dart
index 15e9559..83e3d21 100644
--- a/sdk/lib/_internal/wasm/lib/internal_patch.dart
+++ b/sdk/lib/_internal/wasm/lib/internal_patch.dart
@@ -2,7 +2,9 @@
 // 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:_js_helper" show JS;
+import "dart:_js_helper" show JS, jsStringFromDartString, jsStringToDartString;
+import "dart:_js_types" show JSStringImpl;
+import 'dart:_string';
 import 'dart:_wasm';
 
 part "class_id.dart";
@@ -144,8 +146,9 @@
 @pragma("wasm:export", "\$listAdd")
 void _listAdd(List<dynamic> list, dynamic item) => list.add(item);
 
-String jsonEncode(String object) => JS<String>(
-    "s => stringToDartString(JSON.stringify(stringFromDartString(s)))", object);
+String jsonEncode(String object) =>
+    jsStringToDartString(JSStringImpl(JS<WasmExternRef>(
+        "s => JSON.stringify(s)", jsStringFromDartString(object).toExternRef)));
 
 /// Whether to check bounds in [indexCheck] and [indexCheckWithName], which are
 /// used in list and typed data implementations.
diff --git a/sdk/lib/_internal/wasm/lib/js_helper.dart b/sdk/lib/_internal/wasm/lib/js_helper.dart
index 96f6855..4189b73 100644
--- a/sdk/lib/_internal/wasm/lib/js_helper.dart
+++ b/sdk/lib/_internal/wasm/lib/js_helper.dart
@@ -76,8 +76,9 @@
 }
 
 extension StringToExternRef on String? {
-  WasmExternRef? get toExternRef =>
-      this == null ? WasmExternRef.nullRef : jsStringFromDartString(this!);
+  WasmExternRef? get toExternRef => this == null
+      ? WasmExternRef.nullRef
+      : jsStringFromDartString(this!).toExternRef;
 }
 
 extension ListOfObjectToExternRef on List<Object?>? {
@@ -285,11 +286,8 @@
 WasmExternRef? jsArrayFromDartList(List<Object?> l) =>
     JS<WasmExternRef?>('l => arrayFromDartList(Array, l)', l);
 
-WasmExternRef? jsStringFromDartString(String s) =>
-    JS<WasmExternRef?>('stringFromDartString', s);
-
-String jsStringToDartString(WasmExternRef? s) =>
-    JS<String>('stringToDartString', s);
+external JSStringImpl jsStringFromDartString(String s);
+external String jsStringToDartString(JSStringImpl s);
 
 WasmExternRef? newObjectRaw() => JS<WasmExternRef?>('() => ({})');
 
@@ -364,7 +362,7 @@
   } else if (object is JSValue) {
     return object.toExternRef;
   } else if (object is String) {
-    return jsStringFromDartString(object);
+    return jsStringFromDartString(object).toExternRef;
   } else if (object is js_types.JSInt8ArrayImpl) {
     return object.toJSArrayExternRef();
   } else if (object is js_types.JSUint8ArrayImpl) {
diff --git a/sdk/lib/_internal/wasm/lib/js_helper_patch.dart b/sdk/lib/_internal/wasm/lib/js_helper_patch.dart
new file mode 100644
index 0000000..6042103
--- /dev/null
+++ b/sdk/lib/_internal/wasm/lib/js_helper_patch.dart
@@ -0,0 +1,54 @@
+// Copyright (c) 2024, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'dart:_internal' show patch;
+import 'dart:_js_helper' show JS;
+import 'dart:_string';
+import 'dart:_wasm';
+
+@patch
+@pragma('wasm:prefer-inline')
+JSStringImpl jsStringFromDartString(String s) {
+  if (s is JSStringImpl) return s;
+
+  return JSStringImpl(JS<WasmExternRef>(r'''
+    (s, length) => {
+        let result = '';
+        let index = 0;
+        while (index < length) {
+          let chunkLength = Math.min(length - index, 0xFFFF);
+          const array = new Array(chunkLength);
+          for (let i = 0; i < chunkLength; i++) {
+              array[i] = dartInstance.exports.$stringRead(s, index++);
+          }
+          result += String.fromCharCode(...array);
+        }
+        return result;
+    }
+    ''', jsObjectFromDartObject(s), s.length.toWasmI32()));
+}
+
+@patch
+@pragma('wasm:prefer-inline')
+String jsStringToDartString(JSStringImpl s) => JS<String>(r'''
+    (s, length) => {
+      let range = 0;
+      for (let i = 0; i < length; i++) {
+        range |= s.codePointAt(i);
+      }
+      if (range < 256) {
+        const dartString = dartInstance.exports.$stringAllocate1(length);
+        for (let i = 0; i < length; i++) {
+            dartInstance.exports.$stringWrite1(dartString, i, s.codePointAt(i));
+        }
+        return dartString;
+      } else {
+        const dartString = dartInstance.exports.$stringAllocate2(length);
+        for (let i = 0; i < length; i++) {
+            dartInstance.exports.$stringWrite2(dartString, i, s.charCodeAt(i));
+        }
+        return dartString;
+      }
+    }
+    ''', s.toExternRef, s.length.toWasmI32());
diff --git a/sdk/lib/_internal/wasm/lib/js_interop_patch.dart b/sdk/lib/_internal/wasm/lib/js_interop_patch.dart
index c80f13f..5884bf4 100644
--- a/sdk/lib/_internal/wasm/lib/js_interop_patch.dart
+++ b/sdk/lib/_internal/wasm/lib/js_interop_patch.dart
@@ -459,8 +459,8 @@
   @patch
   JSString get toJS {
     final t = this;
-    return JSString._(
-        JSValue(t is JSStringImpl ? t.toExternRef : jsStringFromDartString(t)));
+    return JSString._(JSValue(
+        (t is JSStringImpl ? t : jsStringFromDartString(t)).toExternRef));
   }
 }
 
diff --git a/sdk/lib/_internal/wasm/lib/js_string.dart b/sdk/lib/_internal/wasm/lib/js_string.dart
index f45fed3..53fbfaf 100644
--- a/sdk/lib/_internal/wasm/lib/js_string.dart
+++ b/sdk/lib/_internal/wasm/lib/js_string.dart
@@ -37,9 +37,6 @@
   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);
@@ -113,7 +110,7 @@
 
     // TODO(joshualitt): Refactor `string_patch.dart` so we can directly
     // allocate a string of the right size.
-    return js.jsStringToDartString(toExternRef) + other;
+    return js.jsStringToDartString(this) + other;
   }
 
   @override
@@ -758,6 +755,11 @@
   }
 }
 
+extension JSStringImplExt on JSStringImpl {
+  @pragma("wasm:prefer-inline")
+  WasmExternRef? get toExternRef => _ref;
+}
+
 String _matchString(Match match) => match[0]!;
 
 String _stringIdentity(String string) => string;
@@ -770,14 +772,6 @@
       r'(s) => s.replace(/\$/g, "$$$$")', replacement.toJS.toExternRef));
 }
 
-@pragma("wasm:export", "\$jsStringToJSStringImpl")
-JSStringImpl _jsStringToJSStringImpl(WasmExternRef? string) =>
-    JSStringImpl(string);
-
-@pragma("wasm:export", "\$jsStringFromJSStringImpl")
-WasmExternRef? _jsStringFromJSStringImpl(JSStringImpl string) =>
-    string.toExternRef;
-
 bool _jsIdentical(WasmExternRef? ref1, WasmExternRef? ref2) =>
     js.JS<bool>('Object.is', ref1, ref2);
 
diff --git a/sdk/lib/_internal/wasm/lib/print_patch.dart b/sdk/lib/_internal/wasm/lib/print_patch.dart
index 3dca9f4..e87d4cf 100644
--- a/sdk/lib/_internal/wasm/lib/print_patch.dart
+++ b/sdk/lib/_internal/wasm/lib/print_patch.dart
@@ -5,5 +5,5 @@
 part of "internal_patch.dart";
 
 @patch
-void printToConsole(String line) =>
-    JS<void>('s => printToConsole(stringFromDartString(s))');
+void printToConsole(String line) => JS<void>(
+    's => printToConsole(s)', jsStringFromDartString(line).toExternRef);
diff --git a/sdk/lib/_internal/wasm/lib/regexp_helper.dart b/sdk/lib/_internal/wasm/lib/regexp_helper.dart
index 8bb2781..c45bc20 100644
--- a/sdk/lib/_internal/wasm/lib/regexp_helper.dart
+++ b/sdk/lib/_internal/wasm/lib/regexp_helper.dart
@@ -13,13 +13,12 @@
     // This method is optimized to test before replacement, which should be
     // much faster. This might be worth measuring in real world use cases
     // though.
-    JS<String>(r"""s => {
-      let jsString = stringFromDartString(s);
-      if (/[[\]{}()*+?.\\^$|]/.test(jsString)) {
-          jsString = jsString.replace(/[[\]{}()*+?.\\^$|]/g, '\\$&');
+    jsStringToDartString(JSStringImpl(JS<WasmExternRef>(r"""s => {
+      if (/[[\]{}()*+?.\\^$|]/.test(s)) {
+          s = s.replace(/[[\]{}()*+?.\\^$|]/g, '\\$&');
       }
-      return stringToDartString(jsString);
-    }""", string);
+      return s;
+    }""", jsStringFromDartString(string).toExternRef)));
 
 // TODO(srujzs): Add this to `JSObject`.
 @js.JS('Object.keys')
@@ -107,7 +106,7 @@
     String modifiers = '$m$i$u$s$g';
     // The call to create the regexp is wrapped in a try catch so we can
     // reformat the exception if need be.
-    WasmExternRef? result = JS<WasmExternRef?>("""(s, m) => {
+    final result = JS<WasmExternRef?>("""(s, m) => {
           try {
             return new RegExp(s, m);
           } catch (e) {
@@ -117,7 +116,7 @@
     if (isJSRegExp(result)) return JSValue(result!) as JSNativeRegExp;
     // The returned value is the stringified JavaScript exception. Turn it into
     // a Dart exception.
-    String errorMessage = jsStringToDartString(result);
+    String errorMessage = jsStringToDartString(JSStringImpl(result!));
     throw new FormatException('Illegal RegExp pattern ($errorMessage)', source);
   }
 
diff --git a/sdk/lib/_internal/wasm/lib/string.dart b/sdk/lib/_internal/wasm/lib/string.dart
index b8beb43..145e727 100644
--- a/sdk/lib/_internal/wasm/lib/string.dart
+++ b/sdk/lib/_internal/wasm/lib/string.dart
@@ -11,8 +11,8 @@
         unsafeCast,
         WasmStringBase;
 
-import 'dart:_js_helper' show JS, jsStringToDartString;
-import 'dart:_js_types' show JSStringImpl;
+import 'dart:_js_helper' show JS, jsStringFromDartString, jsStringToDartString;
+import 'dart:_string';
 import 'dart:_object_helper';
 import 'dart:_string_helper';
 import 'dart:_typed_data';
@@ -107,11 +107,13 @@
 const int _maxLatin1 = 0xff;
 const int _maxUtf16 = 0xffff;
 
-String _toUpperCase(String string) => JS<String>(
-    "s => stringToDartString(stringFromDartString(s).toUpperCase())", string);
+String _toUpperCase(String string) =>
+    jsStringToDartString(JSStringImpl(JS<WasmExternRef>(
+        "s => s.toUpperCase()", jsStringFromDartString(string).toExternRef)));
 
-String _toLowerCase(String string) => JS<String>(
-    "s => stringToDartString(stringFromDartString(s).toLowerCase())", string);
+String _toLowerCase(String string) =>
+    jsStringToDartString(JSStringImpl(JS<WasmExternRef>(
+        "s => s.toLowerCase()", jsStringFromDartString(string).toExternRef)));
 
 /**
  * [StringBase] contains common methods used by concrete String
@@ -938,7 +940,7 @@
       final value = values[i];
       var stringValue = value is String ? value : value.toString();
       if (stringValue is JSStringImpl) {
-        stringValue = jsStringToDartString(stringValue.toExternRef);
+        stringValue = jsStringToDartString(stringValue);
       }
       values[i] = stringValue;
       isOneByteString = isOneByteString && stringValue is OneByteString;
@@ -1099,7 +1101,7 @@
     for (int i = start; i < end; i++) {
       String stringValue = strings[i];
       if (stringValue is JSStringImpl) {
-        stringValue = jsStringToDartString(stringValue.toExternRef);
+        stringValue = jsStringToDartString(stringValue);
         strings[i] = stringValue;
       }
       isOneByteString = isOneByteString && stringValue is OneByteString;
diff --git a/sdk/lib/_internal/wasm/lib/string_patch.dart b/sdk/lib/_internal/wasm/lib/string_patch.dart
index f767082..96db243 100644
--- a/sdk/lib/_internal/wasm/lib/string_patch.dart
+++ b/sdk/lib/_internal/wasm/lib/string_patch.dart
@@ -4,7 +4,6 @@
 
 import "dart:_internal" show patch;
 import "dart:_string";
-import "dart:_js_types" show JSStringImpl;
 
 @patch
 class String {
diff --git a/sdk/lib/_internal/wasm/lib/uri_patch.dart b/sdk/lib/_internal/wasm/lib/uri_patch.dart
index 002aedd..f3407d5 100644
--- a/sdk/lib/_internal/wasm/lib/uri_patch.dart
+++ b/sdk/lib/_internal/wasm/lib/uri_patch.dart
@@ -8,15 +8,15 @@
 class Uri {
   @patch
   static Uri get base {
-    String? currentUri = JS<String?>("""() => {
+    final currentUri = JSStringImpl(JS<WasmExternRef?>("""() => {
       // On browsers return `globalThis.location.href`
       if (globalThis.location != null) {
-        return stringToDartString(globalThis.location.href);
+        return globalThis.location.href;
       }
       return null;
-    }""");
+    }"""));
     if (currentUri != null) {
-      return Uri.parse(currentUri);
+      return Uri.parse(jsStringToDartString(currentUri));
     }
     throw UnsupportedError("'Uri.base' is not supported");
   }
diff --git a/sdk/lib/_internal/wasm_js_compatibility/lib/js_helper_patch.dart b/sdk/lib/_internal/wasm_js_compatibility/lib/js_helper_patch.dart
new file mode 100644
index 0000000..dbdfe15
--- /dev/null
+++ b/sdk/lib/_internal/wasm_js_compatibility/lib/js_helper_patch.dart
@@ -0,0 +1,15 @@
+// Copyright (c) 2024, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'dart:_internal' show patch, unsafeCast;
+import 'dart:_string' show JSStringImpl;
+import 'dart:_wasm';
+
+@patch
+@pragma('wasm:prefer-inline')
+JSStringImpl jsStringFromDartString(String s) => unsafeCast<JSStringImpl>(s);
+
+@patch
+@pragma('wasm:prefer-inline')
+String jsStringToDartString(JSStringImpl s) => s;
diff --git a/sdk/lib/libraries.json b/sdk/lib/libraries.json
index 8a5a95a..12ed06f 100644
--- a/sdk/lib/libraries.json
+++ b/sdk/lib/libraries.json
@@ -164,6 +164,12 @@
       },
       "_typed_data": {
         "uri": "_internal/wasm/lib/typed_data.dart"
+      },
+      "_js_helper": {
+        "uri": "_internal/wasm/lib/js_helper.dart",
+        "patches": [
+          "_internal/wasm/lib/js_helper_patch.dart"
+        ]
       }
     }
   },
@@ -212,6 +218,12 @@
       },
       "_string": {
         "uri": "_internal/wasm/lib/js_string.dart"
+      },
+      "_js_helper": {
+        "uri": "_internal/wasm/lib/js_helper.dart",
+        "patches": [
+          "_internal/wasm_js_compatibility/lib/js_helper_patch.dart"
+        ]
       }
     }
   },
@@ -240,9 +252,6 @@
       "_js_annotations": {
         "uri": "js/_js_annotations.dart"
       },
-      "_js_helper": {
-        "uri": "_internal/wasm/lib/js_helper.dart"
-      },
       "_js_string_convert": {
         "uri": "_internal/wasm/lib/js_string_convert.dart"
       },
diff --git a/sdk/lib/libraries.yaml b/sdk/lib/libraries.yaml
index 83b8416..9797df8 100644
--- a/sdk/lib/libraries.yaml
+++ b/sdk/lib/libraries.yaml
@@ -149,6 +149,10 @@
         _internal/wasm/lib/string.dart
     _typed_data:
       uri: _internal/wasm/lib/typed_data.dart
+    _js_helper:
+      uri: _internal/wasm/lib/js_helper.dart
+      patches:
+        - _internal/wasm/lib/js_helper_patch.dart
 
 wasm_js_compatibility:
   include:
@@ -186,6 +190,10 @@
         - _internal/wasm_js_compatibility/lib/typed_data_patch.dart
     _string:
       uri: _internal/wasm/lib/js_string.dart
+    _js_helper:
+      uri: _internal/wasm/lib/js_helper.dart
+      patches:
+        - _internal/wasm_js_compatibility/lib/js_helper_patch.dart
 
 wasm_common:
   libraries:
@@ -205,8 +213,6 @@
       - _internal/wasm/lib/internal_patch.dart
     _js_annotations:
       uri: js/_js_annotations.dart
-    _js_helper:
-      uri: _internal/wasm/lib/js_helper.dart
     _js_string_convert:
       uri: _internal/wasm/lib/js_string_convert.dart
     _js_types: