Version 3.3.0-82.0.dev

Merge 60d1a40462e0b489a68e9f7a1fe159e9edac19be into dev
diff --git a/pkg/dart2wasm/lib/class_info.dart b/pkg/dart2wasm/lib/class_info.dart
index 85afb84..1667dc0 100644
--- a/pkg/dart2wasm/lib/class_info.dart
+++ b/pkg/dart2wasm/lib/class_info.dart
@@ -54,6 +54,7 @@
   static const syncStarIteratorCurrent = 3;
   static const syncStarIteratorYieldStarIterable = 4;
   static const recordFieldBase = 2;
+  static const jsStringImplRef = 2;
 
   static void validate(Translator translator) {
     void check(Class cls, String name, int expectedIndex) {
@@ -82,8 +83,10 @@
     check(translator.boxedBoolClass, "value", FieldIndex.boxValue);
     check(translator.boxedIntClass, "value", FieldIndex.boxValue);
     check(translator.boxedDoubleClass, "value", FieldIndex.boxValue);
-    check(translator.oneByteStringClass, "_array", FieldIndex.stringArray);
-    check(translator.twoByteStringClass, "_array", FieldIndex.stringArray);
+    if (!translator.options.jsCompatibility) {
+      check(translator.oneByteStringClass, "_array", FieldIndex.stringArray);
+      check(translator.twoByteStringClass, "_array", FieldIndex.stringArray);
+    }
     check(translator.listBaseClass, "_length", FieldIndex.listLength);
     check(translator.listBaseClass, "_data", FieldIndex.listArray);
     check(translator.hashFieldBaseClass, "_indexNullable",
@@ -259,6 +262,7 @@
   /// These types switch from properly reified non-masquerading types in regular
   /// Dart2Wasm mode to masquerading types in js compatibility mode.
   final Set<String> jsCompatibilityTypes = {
+    "JSStringImpl",
     "JSArrayBufferImpl",
     "JSArrayBufferViewImpl",
     "JSDataViewImpl",
@@ -284,7 +288,6 @@
     final jsTypesLibraryIndex =
         LibraryIndex(translator.component, ["dart:_js_types"]);
     final neverMasquerades = [
-      "JSStringImpl",
       if (!translator.options.jsCompatibility) ...jsCompatibilityTypes,
     ]
         .map((name) => jsTypesLibraryIndex.tryGetClass("dart:_js_types", name))
@@ -344,7 +347,9 @@
       ClassInfo superInfo = cls == translator.coreTypes.boolClass ||
               cls == translator.coreTypes.numClass
           ? topInfo
-          : cls == translator.stringBaseClass || cls == translator.typeClass
+          : (!translator.options.jsCompatibility &&
+                      cls == translator.stringBaseClass) ||
+                  cls == translator.typeClass
               ? translator.classInfo[cls.implementedTypes.single.classNode]!
               : translator.classInfo[superclass]!;
 
diff --git a/pkg/dart2wasm/lib/code_generator.dart b/pkg/dart2wasm/lib/code_generator.dart
index 75953b9..e813c68 100644
--- a/pkg/dart2wasm/lib/code_generator.dart
+++ b/pkg/dart2wasm/lib/code_generator.dart
@@ -2837,8 +2837,9 @@
         node.expressions,
         InterfaceType(
             translator.coreTypes.stringClass, Nullability.nonNullable));
-    return translator
-        .outputOrVoid(call(translator.stringInterpolate.reference));
+    return translator.outputOrVoid(call(translator.options.jsCompatibility
+        ? translator.jsStringInterpolate.reference
+        : translator.stringInterpolate.reference));
   }
 
   @override
@@ -3711,7 +3712,9 @@
           .classInfo[translator.coreTypes.stringClass]!.repr.nonNullableType;
       nullableType = translator
           .classInfo[translator.coreTypes.stringClass]!.repr.nullableType;
-      compare = () => codeGen.call(translator.stringEquals.reference);
+      compare = () => codeGen.call(translator.options.jsCompatibility
+          ? translator.jsStringEquals.reference
+          : translator.stringEquals.reference);
     } else {
       // Object switch
       nonNullableType = translator.topInfo.nonNullableType;
diff --git a/pkg/dart2wasm/lib/compile.dart b/pkg/dart2wasm/lib/compile.dart
index 2f2b968..e642e12 100644
--- a/pkg/dart2wasm/lib/compile.dart
+++ b/pkg/dart2wasm/lib/compile.dart
@@ -151,7 +151,9 @@
   }
 
   final wasmModule = translator.translate();
-  String jsRuntime =
-      jsRuntimeFinalizer.generate(translator.functions.translatedProcedures);
+  String jsRuntime = jsRuntimeFinalizer.generate(
+      translator.functions.translatedProcedures,
+      translator.internalizedStringsForJSRuntime,
+      mode);
   return CompilerOutput(wasmModule, jsRuntime);
 }
diff --git a/pkg/dart2wasm/lib/constants.dart b/pkg/dart2wasm/lib/constants.dart
index a17d387..a8f60b3 100644
--- a/pkg/dart2wasm/lib/constants.dart
+++ b/pkg/dart2wasm/lib/constants.dart
@@ -322,6 +322,15 @@
 
   @override
   ConstantInfo? visitStringConstant(StringConstant constant) {
+    if (translator.options.jsCompatibility) {
+      ClassInfo info = translator.classInfo[translator.jsStringClass]!;
+      return createConstant(constant, info.nonNullableType, (function, b) {
+        b.i32_const(info.classId);
+        b.i32_const(initialIdentityHash);
+        b.global_get(translator.getInternalizedStringGlobal(constant.value));
+        b.struct_new(info.struct);
+      });
+    }
     bool isOneByte = constant.value.codeUnits.every((c) => c <= 255);
     ClassInfo info = translator.classInfo[isOneByte
         ? translator.oneByteStringClass
@@ -885,8 +894,8 @@
   ConstantInfo? visitSymbolConstant(SymbolConstant constant) {
     ClassInfo info = translator.classInfo[translator.symbolClass]!;
     translator.functions.allocateClass(info.classId);
-    w.RefType stringType =
-        translator.classInfo[translator.coreTypes.stringClass]!.nonNullableType;
+    w.RefType stringType = translator
+        .classInfo[translator.coreTypes.stringClass]!.repr.nonNullableType;
     StringConstant nameConstant = StringConstant(constant.name);
     bool lazy = ensureConstant(nameConstant)?.isLazy ?? false;
     return createConstant(constant, info.nonNullableType, lazy: lazy,
diff --git a/pkg/dart2wasm/lib/js/runtime_blob.dart b/pkg/dart2wasm/lib/js/runtime_blob.dart
index fecb1fd..254cf82 100644
--- a/pkg/dart2wasm/lib/js/runtime_blob.dart
+++ b/pkg/dart2wasm/lib/js/runtime_blob.dart
@@ -14,6 +14,10 @@
 // This function returns a promise to the instantiated module.
 export const instantiate = async (modulePromise, importObjectPromise) => {
     let dartInstance;
+''';
+
+// Break to support system dependent conversion routines.
+const jsRuntimeBlobPart2Regular = r'''
     function stringFromDartString(string) {
         const totalLength = dartInstance.exports.$stringLength(string);
         let result = '';
@@ -49,7 +53,20 @@
             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'''
     // Converts a Dart List to a JS array. Any Dart objects will be converted, but
     // this will be cheap for JSValues.
     function arrayFromDartList(constructor, list) {
@@ -84,11 +101,15 @@
 
 // We break inside the 'dart2wasm' object to enable injection of methods. We
 // could use interpolation, but then we'd have to escape characters.
-const jsRuntimeBlobPart2 = r'''
+const jsRuntimeBlobPart4 = r'''
     };
 
     const baseImports = {
         dart2wasm: dart2wasm,
+''';
+
+// We break inside of `baseImports` to inject internalized strings.
+const jsRuntimeBlobPart5 = r'''
         Math: Math,
         Date: Date,
         Object: Object,
diff --git a/pkg/dart2wasm/lib/js/runtime_generator.dart b/pkg/dart2wasm/lib/js/runtime_generator.dart
index 7792cff..343d3f6 100644
--- a/pkg/dart2wasm/lib/js/runtime_generator.dart
+++ b/pkg/dart2wasm/lib/js/runtime_generator.dart
@@ -8,10 +8,13 @@
 import 'package:dart2wasm/js/interop_transformer.dart';
 import 'package:dart2wasm/js/method_collector.dart';
 import 'package:dart2wasm/js/runtime_blob.dart';
+import 'package:dart2wasm/target.dart' as wasm_target;
 import 'package:kernel/ast.dart';
 import 'package:kernel/class_hierarchy.dart';
 import 'package:kernel/core_types.dart';
 
+import 'dart:convert' show json;
+
 JSMethods _performJSInteropTransformations(
     Component component,
     CoreTypes coreTypes,
@@ -46,7 +49,10 @@
 
   RuntimeFinalizer(this.allJSMethods);
 
-  String generate(Iterable<Procedure> translatedProcedures) {
+  String generate(Iterable<Procedure> translatedProcedures,
+      List<String> constantStrings, wasm_target.Mode mode) {
+    String escape(String s) => json.encode(s);
+
     Set<Procedure> usedProcedures = {};
     List<String> usedJSMethods = [];
     for (Procedure p in translatedProcedures) {
@@ -54,10 +60,22 @@
         usedJSMethods.add(allJSMethods[p]!);
       }
     }
+
+    String internalizedStrings = '';
+    if (constantStrings.isNotEmpty) {
+      internalizedStrings = '''
+s: [
+  ${constantStrings.map(escape).join(',\n')}
+],''';
+    }
     return '''
   $jsRuntimeBlobPart1
+  ${mode == wasm_target.Mode.jsCompatibility ? jsRuntimeBlobPart2JSCM : jsRuntimeBlobPart2Regular}
+  $jsRuntimeBlobPart3
   ${usedJSMethods.join(',\n')}
-  $jsRuntimeBlobPart2
+  $jsRuntimeBlobPart4
+  $internalizedStrings
+  $jsRuntimeBlobPart5
 ''';
   }
 }
diff --git a/pkg/dart2wasm/lib/kernel_nodes.dart b/pkg/dart2wasm/lib/kernel_nodes.dart
index 5663bff3..d7452f4 100644
--- a/pkg/dart2wasm/lib/kernel_nodes.dart
+++ b/pkg/dart2wasm/lib/kernel_nodes.dart
@@ -12,18 +12,24 @@
 
   late final LibraryIndex index = LibraryIndex(component, [
     "dart:_internal",
+    "dart:_js_helper",
+    "dart:_js_types",
+    "dart:_string",
+    "dart:_wasm",
     "dart:async",
     "dart:collection",
     "dart:core",
     "dart:ffi",
     "dart:typed_data",
-    "dart:_string",
-    "dart:_wasm",
   ]);
 
   // dart:_internal classes
   late final Class symbolClass = index.getClass("dart:_internal", "Symbol");
 
+  // dart:_js_types classes
+  late final Class jsStringClass =
+      index.getClass("dart:_js_types", "JSStringImpl");
+
   // dart:collection classes
   late final Class hashFieldBaseClass =
       index.getClass("dart:collection", "_HashFieldBase");
@@ -144,6 +150,18 @@
   late final Procedure checkLibraryIsLoaded =
       index.getTopLevelProcedure("dart:_internal", "checkLibraryIsLoaded");
 
+  // dart:_js_helper procedures
+  late final Procedure getInternalizedString =
+      index.getTopLevelProcedure("dart:_js_helper", "getInternalizedString");
+  late final Procedure areEqualInJS =
+      index.getTopLevelProcedure("dart:_js_helper", "areEqualInJS");
+
+  // dart:_js_types procedures
+  late final Procedure jsStringEquals =
+      index.getProcedure("dart:_js_types", "JSStringImpl", "==");
+  late final Procedure jsStringInterpolate =
+      index.getProcedure("dart:_js_types", "JSStringImpl", "interpolate");
+
   // dart:collection procedures
   late final Procedure mapFactory =
       index.getProcedure("dart:collection", "LinkedHashMap", "_default");
diff --git a/pkg/dart2wasm/lib/target.dart b/pkg/dart2wasm/lib/target.dart
index 922a962..6c034b6 100644
--- a/pkg/dart2wasm/lib/target.dart
+++ b/pkg/dart2wasm/lib/target.dart
@@ -102,6 +102,7 @@
   Class? _wasmImmutableSet;
   Class? _oneByteString;
   Class? _twoByteString;
+  Class? _jsString;
   Class? _closure;
   Map<String, Class>? _nativeClasses;
 
@@ -141,12 +142,11 @@
   TargetFlags get flags => TargetFlags();
 
   @override
-  List<String> get extraRequiredLibraries => const <String>[
+  List<String> get extraRequiredLibraries => [
         'dart:_http',
         'dart:_internal',
         'dart:_js_helper',
         'dart:_js_types',
-        'dart:_string',
         'dart:_wasm',
         'dart:async',
         'dart:developer',
@@ -158,19 +158,20 @@
         'dart:js_util',
         'dart:nativewrappers',
         'dart:typed_data',
+        if (mode != Mode.jsCompatibility) 'dart:_string',
       ];
 
   @override
-  List<String> get extraIndexedLibraries => const <String>[
+  List<String> get extraIndexedLibraries => [
         'dart:_js_helper',
         'dart:_js_types',
-        'dart:_string',
         'dart:_wasm',
         'dart:collection',
         'dart:js_interop',
         'dart:js_interop_unsafe',
         'dart:js_util',
         'dart:typed_data',
+        if (mode != Mode.jsCompatibility) 'dart:_string',
       ];
 
   @override
@@ -462,6 +463,11 @@
 
   @override
   Class concreteStringLiteralClass(CoreTypes coreTypes, String value) {
+    // In JSCM all strings are JS strings.
+    if (mode == Mode.jsCompatibility) {
+      return _jsString ??=
+          coreTypes.index.getClass("dart:_js_types", "JSStringImpl");
+    }
     const int maxLatin1 = 0xff;
     for (int i = 0; i < value.length; ++i) {
       if (value.codeUnitAt(i) > maxLatin1) {
diff --git a/pkg/dart2wasm/lib/translator.dart b/pkg/dart2wasm/lib/translator.dart
index 58c0f25..c7ec7d4 100644
--- a/pkg/dart2wasm/lib/translator.dart
+++ b/pkg/dart2wasm/lib/translator.dart
@@ -84,6 +84,10 @@
   /// [ClassInfoCollector].
   final Map<Class, ClassInfo> classInfo = {};
 
+  /// Internalized strings to move to the JS runtime
+  final List<String> internalizedStringsForJSRuntime = [];
+  final Map<String, w.Global> _internalizedStringGlobals = {};
+
   final Map<w.HeapType, ClassInfo> classForHeapType = {};
   final Map<Field, int> fieldIndex = {};
   final Map<TypeParameter, int> typeParameterIndex = {};
@@ -161,8 +165,11 @@
     boxedIntClass: boxedIntClass,
     boxedDoubleClass: boxedDoubleClass,
     boxedBoolClass: coreTypes.boolClass,
-    oneByteStringClass: stringBaseClass,
-    twoByteStringClass: stringBaseClass,
+    if (!options.jsCompatibility) ...{
+      oneByteStringClass: stringBaseClass,
+      twoByteStringClass: stringBaseClass
+    },
+    if (options.jsCompatibility) ...{jsStringClass: jsStringClass},
   };
 
   /// Type for vtable entries for dynamic calls. These entries are used in
@@ -1026,6 +1033,19 @@
 
   ClassInfo getRecordClassInfo(RecordType recordType) =>
       classInfo[recordClasses[RecordShape.fromType(recordType)]!]!;
+
+  w.Global getInternalizedStringGlobal(String s) {
+    w.Global? internalizedString = _internalizedStringGlobals[s];
+    if (internalizedString != null) {
+      return internalizedString;
+    }
+    final i = internalizedStringsForJSRuntime.length;
+    internalizedString = m.globals.import('s', '$i',
+        w.GlobalType(w.RefType.extern(nullable: true), mutable: false));
+    _internalizedStringGlobals[s] = internalizedString;
+    internalizedStringsForJSRuntime.add(s);
+    return internalizedString;
+  }
 }
 
 abstract class _FunctionGenerator {
diff --git a/pkg/front_end/lib/src/fasta/fasta_codes_cfe_generated.dart b/pkg/front_end/lib/src/fasta/fasta_codes_cfe_generated.dart
index 38b166f..c6e06c3 100644
--- a/pkg/front_end/lib/src/fasta/fasta_codes_cfe_generated.dart
+++ b/pkg/front_end/lib/src/fasta/fasta_codes_cfe_generated.dart
@@ -2799,7 +2799,7 @@
                 DartType _type3, bool isNonNullableByDefault)>(
         "InvalidExtensionTypeSuperExtensionType",
         problemMessageTemplate:
-            r"""The representation type '#type' of extension type '#name' must be a subtype of the representation type '#type2' of the implemented extension type '#type3'.""",
+            r"""The representation type '#type' of extension type '#name' must be either a subtype of the representation type '#type2' of the implemented extension type '#type3' or a subtype of '#type3' itself.""",
         correctionMessageTemplate:
             r"""Try changing the representation type to a subtype of '#type2'.""",
         withArguments: _withArgumentsInvalidExtensionTypeSuperExtensionType);
@@ -2832,7 +2832,7 @@
   String type3 = type3Parts.join();
   return new Message(codeInvalidExtensionTypeSuperExtensionType,
       problemMessage:
-          """The representation type '${type}' of extension type '${name}' must be a subtype of the representation type '${type2}' of the implemented extension type '${type3}'.""" +
+          """The representation type '${type}' of extension type '${name}' must be either a subtype of the representation type '${type2}' of the implemented extension type '${type3}' or a subtype of '${type3}' itself.""" +
               labeler.originMessages,
       correctionMessage: """Try changing the representation type to a subtype of '${type2}'.""",
       arguments: {
diff --git a/pkg/front_end/lib/src/fasta/source/source_extension_type_declaration_builder.dart b/pkg/front_end/lib/src/fasta/source/source_extension_type_declaration_builder.dart
index f4ec217..6134cbf 100644
--- a/pkg/front_end/lib/src/fasta/source/source_extension_type_declaration_builder.dart
+++ b/pkg/front_end/lib/src/fasta/source/source_extension_type_declaration_builder.dart
@@ -458,23 +458,27 @@
                 typeBuilder.fileUri);
           }
         } else if (interface is ExtensionType) {
-          DartType instantiatedRepresentationType =
-              Substitution.fromExtensionType(interface).substituteType(interface
-                  .extensionTypeDeclaration.declaredRepresentationType);
-          if (!hierarchyBuilder.types.isSubtypeOf(
-              declaredRepresentationType,
-              instantiatedRepresentationType,
-              SubtypeCheckMode.withNullabilities)) {
-            libraryBuilder.addProblem(
-                templateInvalidExtensionTypeSuperExtensionType.withArguments(
-                    declaredRepresentationType,
-                    name,
-                    instantiatedRepresentationType,
-                    interface,
-                    true),
-                typeBuilder.charOffset!,
-                noLength,
-                typeBuilder.fileUri);
+          if (!hierarchyBuilder.types.isSubtypeOf(declaredRepresentationType,
+              interface, SubtypeCheckMode.withNullabilities)) {
+            DartType instantiatedImplementedRepresentationType =
+                Substitution.fromExtensionType(interface).substituteType(
+                    interface
+                        .extensionTypeDeclaration.declaredRepresentationType);
+            if (!hierarchyBuilder.types.isSubtypeOf(
+                declaredRepresentationType,
+                instantiatedImplementedRepresentationType,
+                SubtypeCheckMode.withNullabilities)) {
+              libraryBuilder.addProblem(
+                  templateInvalidExtensionTypeSuperExtensionType.withArguments(
+                      declaredRepresentationType,
+                      name,
+                      instantiatedImplementedRepresentationType,
+                      interface,
+                      true),
+                  typeBuilder.charOffset!,
+                  noLength,
+                  typeBuilder.fileUri);
+            }
           }
         }
       }
diff --git a/pkg/front_end/messages.yaml b/pkg/front_end/messages.yaml
index a0ec14c..1df2721 100644
--- a/pkg/front_end/messages.yaml
+++ b/pkg/front_end/messages.yaml
@@ -7309,7 +7309,7 @@
     extension type E(num n) implements int {}
 
 InvalidExtensionTypeSuperExtensionType:
-  problemMessage: "The representation type '#type' of extension type '#name' must be a subtype of the representation type '#type2' of the implemented extension type '#type3'."
+  problemMessage: "The representation type '#type' of extension type '#name' must be either a subtype of the representation type '#type2' of the implemented extension type '#type3' or a subtype of '#type3' itself."
   correctionMessage: "Try changing the representation type to a subtype of '#type2'."
   experiments: inline-class
   script: |
diff --git a/pkg/front_end/testcases/extension_types/issue53170.dart.strong.expect b/pkg/front_end/testcases/extension_types/issue53170.dart.strong.expect
index bdfefed..fd28d0a 100644
--- a/pkg/front_end/testcases/extension_types/issue53170.dart.strong.expect
+++ b/pkg/front_end/testcases/extension_types/issue53170.dart.strong.expect
@@ -7,12 +7,12 @@
 // extension type S1(num id) implements String /* Error */ {}
 //                                      ^
 //
-// pkg/front_end/testcases/extension_types/issue53170.dart:11:41: Error: The representation type 'String' of extension type 'V2' must be a subtype of the representation type 'num' of the implemented extension type 'V1'.
+// pkg/front_end/testcases/extension_types/issue53170.dart:11:41: Error: The representation type 'String' of extension type 'V2' must be either a subtype of the representation type 'num' of the implemented extension type 'V1' or a subtype of 'V1' itself.
 // Try changing the representation type to a subtype of 'num'.
 // extension type V2(String id) implements V1 /* Error */ {}
 //                                         ^
 //
-// pkg/front_end/testcases/extension_types/issue53170.dart:17:41: Error: The representation type 'String' of extension type 'W2' must be a subtype of the representation type 'num' of the implemented extension type 'W1<num>'.
+// pkg/front_end/testcases/extension_types/issue53170.dart:17:41: Error: The representation type 'String' of extension type 'W2' must be either a subtype of the representation type 'num' of the implemented extension type 'W1<num>' or a subtype of 'W1<num>' itself.
 // Try changing the representation type to a subtype of 'num'.
 // extension type W2(String id) implements W1<num> /* Error */ {}
 //                                         ^
diff --git a/pkg/front_end/testcases/extension_types/issue53170.dart.strong.transformed.expect b/pkg/front_end/testcases/extension_types/issue53170.dart.strong.transformed.expect
index bdfefed..fd28d0a 100644
--- a/pkg/front_end/testcases/extension_types/issue53170.dart.strong.transformed.expect
+++ b/pkg/front_end/testcases/extension_types/issue53170.dart.strong.transformed.expect
@@ -7,12 +7,12 @@
 // extension type S1(num id) implements String /* Error */ {}
 //                                      ^
 //
-// pkg/front_end/testcases/extension_types/issue53170.dart:11:41: Error: The representation type 'String' of extension type 'V2' must be a subtype of the representation type 'num' of the implemented extension type 'V1'.
+// pkg/front_end/testcases/extension_types/issue53170.dart:11:41: Error: The representation type 'String' of extension type 'V2' must be either a subtype of the representation type 'num' of the implemented extension type 'V1' or a subtype of 'V1' itself.
 // Try changing the representation type to a subtype of 'num'.
 // extension type V2(String id) implements V1 /* Error */ {}
 //                                         ^
 //
-// pkg/front_end/testcases/extension_types/issue53170.dart:17:41: Error: The representation type 'String' of extension type 'W2' must be a subtype of the representation type 'num' of the implemented extension type 'W1<num>'.
+// pkg/front_end/testcases/extension_types/issue53170.dart:17:41: Error: The representation type 'String' of extension type 'W2' must be either a subtype of the representation type 'num' of the implemented extension type 'W1<num>' or a subtype of 'W1<num>' itself.
 // Try changing the representation type to a subtype of 'num'.
 // extension type W2(String id) implements W1<num> /* Error */ {}
 //                                         ^
diff --git a/pkg/front_end/testcases/extension_types/issue53170.dart.weak.expect b/pkg/front_end/testcases/extension_types/issue53170.dart.weak.expect
index bdfefed..fd28d0a 100644
--- a/pkg/front_end/testcases/extension_types/issue53170.dart.weak.expect
+++ b/pkg/front_end/testcases/extension_types/issue53170.dart.weak.expect
@@ -7,12 +7,12 @@
 // extension type S1(num id) implements String /* Error */ {}
 //                                      ^
 //
-// pkg/front_end/testcases/extension_types/issue53170.dart:11:41: Error: The representation type 'String' of extension type 'V2' must be a subtype of the representation type 'num' of the implemented extension type 'V1'.
+// pkg/front_end/testcases/extension_types/issue53170.dart:11:41: Error: The representation type 'String' of extension type 'V2' must be either a subtype of the representation type 'num' of the implemented extension type 'V1' or a subtype of 'V1' itself.
 // Try changing the representation type to a subtype of 'num'.
 // extension type V2(String id) implements V1 /* Error */ {}
 //                                         ^
 //
-// pkg/front_end/testcases/extension_types/issue53170.dart:17:41: Error: The representation type 'String' of extension type 'W2' must be a subtype of the representation type 'num' of the implemented extension type 'W1<num>'.
+// pkg/front_end/testcases/extension_types/issue53170.dart:17:41: Error: The representation type 'String' of extension type 'W2' must be either a subtype of the representation type 'num' of the implemented extension type 'W1<num>' or a subtype of 'W1<num>' itself.
 // Try changing the representation type to a subtype of 'num'.
 // extension type W2(String id) implements W1<num> /* Error */ {}
 //                                         ^
diff --git a/pkg/front_end/testcases/extension_types/issue53170.dart.weak.modular.expect b/pkg/front_end/testcases/extension_types/issue53170.dart.weak.modular.expect
index bdfefed..fd28d0a 100644
--- a/pkg/front_end/testcases/extension_types/issue53170.dart.weak.modular.expect
+++ b/pkg/front_end/testcases/extension_types/issue53170.dart.weak.modular.expect
@@ -7,12 +7,12 @@
 // extension type S1(num id) implements String /* Error */ {}
 //                                      ^
 //
-// pkg/front_end/testcases/extension_types/issue53170.dart:11:41: Error: The representation type 'String' of extension type 'V2' must be a subtype of the representation type 'num' of the implemented extension type 'V1'.
+// pkg/front_end/testcases/extension_types/issue53170.dart:11:41: Error: The representation type 'String' of extension type 'V2' must be either a subtype of the representation type 'num' of the implemented extension type 'V1' or a subtype of 'V1' itself.
 // Try changing the representation type to a subtype of 'num'.
 // extension type V2(String id) implements V1 /* Error */ {}
 //                                         ^
 //
-// pkg/front_end/testcases/extension_types/issue53170.dart:17:41: Error: The representation type 'String' of extension type 'W2' must be a subtype of the representation type 'num' of the implemented extension type 'W1<num>'.
+// pkg/front_end/testcases/extension_types/issue53170.dart:17:41: Error: The representation type 'String' of extension type 'W2' must be either a subtype of the representation type 'num' of the implemented extension type 'W1<num>' or a subtype of 'W1<num>' itself.
 // Try changing the representation type to a subtype of 'num'.
 // extension type W2(String id) implements W1<num> /* Error */ {}
 //                                         ^
diff --git a/pkg/front_end/testcases/extension_types/issue53170.dart.weak.outline.expect b/pkg/front_end/testcases/extension_types/issue53170.dart.weak.outline.expect
index 68e1630..abe59f8 100644
--- a/pkg/front_end/testcases/extension_types/issue53170.dart.weak.outline.expect
+++ b/pkg/front_end/testcases/extension_types/issue53170.dart.weak.outline.expect
@@ -7,12 +7,12 @@
 // extension type S1(num id) implements String /* Error */ {}
 //                                      ^
 //
-// pkg/front_end/testcases/extension_types/issue53170.dart:11:41: Error: The representation type 'String' of extension type 'V2' must be a subtype of the representation type 'num' of the implemented extension type 'V1'.
+// pkg/front_end/testcases/extension_types/issue53170.dart:11:41: Error: The representation type 'String' of extension type 'V2' must be either a subtype of the representation type 'num' of the implemented extension type 'V1' or a subtype of 'V1' itself.
 // Try changing the representation type to a subtype of 'num'.
 // extension type V2(String id) implements V1 /* Error */ {}
 //                                         ^
 //
-// pkg/front_end/testcases/extension_types/issue53170.dart:17:41: Error: The representation type 'String' of extension type 'W2' must be a subtype of the representation type 'num' of the implemented extension type 'W1<num>'.
+// pkg/front_end/testcases/extension_types/issue53170.dart:17:41: Error: The representation type 'String' of extension type 'W2' must be either a subtype of the representation type 'num' of the implemented extension type 'W1<num>' or a subtype of 'W1<num>' itself.
 // Try changing the representation type to a subtype of 'num'.
 // extension type W2(String id) implements W1<num> /* Error */ {}
 //                                         ^
diff --git a/pkg/front_end/testcases/extension_types/issue53170.dart.weak.transformed.expect b/pkg/front_end/testcases/extension_types/issue53170.dart.weak.transformed.expect
index bdfefed..fd28d0a 100644
--- a/pkg/front_end/testcases/extension_types/issue53170.dart.weak.transformed.expect
+++ b/pkg/front_end/testcases/extension_types/issue53170.dart.weak.transformed.expect
@@ -7,12 +7,12 @@
 // extension type S1(num id) implements String /* Error */ {}
 //                                      ^
 //
-// pkg/front_end/testcases/extension_types/issue53170.dart:11:41: Error: The representation type 'String' of extension type 'V2' must be a subtype of the representation type 'num' of the implemented extension type 'V1'.
+// pkg/front_end/testcases/extension_types/issue53170.dart:11:41: Error: The representation type 'String' of extension type 'V2' must be either a subtype of the representation type 'num' of the implemented extension type 'V1' or a subtype of 'V1' itself.
 // Try changing the representation type to a subtype of 'num'.
 // extension type V2(String id) implements V1 /* Error */ {}
 //                                         ^
 //
-// pkg/front_end/testcases/extension_types/issue53170.dart:17:41: Error: The representation type 'String' of extension type 'W2' must be a subtype of the representation type 'num' of the implemented extension type 'W1<num>'.
+// pkg/front_end/testcases/extension_types/issue53170.dart:17:41: Error: The representation type 'String' of extension type 'W2' must be either a subtype of the representation type 'num' of the implemented extension type 'W1<num>' or a subtype of 'W1<num>' itself.
 // Try changing the representation type to a subtype of 'num'.
 // extension type W2(String id) implements W1<num> /* Error */ {}
 //                                         ^
diff --git a/pkg/front_end/testcases/extension_types/issue53867.dart b/pkg/front_end/testcases/extension_types/issue53867.dart
new file mode 100644
index 0000000..daeb4e8
--- /dev/null
+++ b/pkg/front_end/testcases/extension_types/issue53867.dart
@@ -0,0 +1,11 @@
+// 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.
+
+extension type E1(num it) {}
+extension type E2(E1 it) implements E1 {} // Ok.
+extension type E3(int it) implements E1 {} // Ok.
+extension type E4(E3 it) implements E1 {} // Ok.
+extension type E5(E3 it) implements E2 {} // Ok.
+extension type E6(E2 it) implements E3 {} // Error.
+extension type E7(String it) implements E1 {} // Error.
diff --git a/pkg/front_end/testcases/extension_types/issue53867.dart.strong.expect b/pkg/front_end/testcases/extension_types/issue53867.dart.strong.expect
new file mode 100644
index 0000000..f9370be
--- /dev/null
+++ b/pkg/front_end/testcases/extension_types/issue53867.dart.strong.expect
@@ -0,0 +1,94 @@
+library;
+//
+// Problems in library:
+//
+// pkg/front_end/testcases/extension_types/issue53867.dart:10:37: Error: The representation type 'E2' of extension type 'E6' must be either a subtype of the representation type 'int' of the implemented extension type 'E3' or a subtype of 'E3' itself.
+// Try changing the representation type to a subtype of 'int'.
+// extension type E6(E2 it) implements E3 {} // Error.
+//                                     ^
+//
+// pkg/front_end/testcases/extension_types/issue53867.dart:11:41: Error: The representation type 'String' of extension type 'E7' must be either a subtype of the representation type 'num' of the implemented extension type 'E1' or a subtype of 'E1' itself.
+// Try changing the representation type to a subtype of 'num'.
+// extension type E7(String it) implements E1 {} // Error.
+//                                         ^
+//
+import self as self;
+import "dart:core" as core;
+
+extension type E1(core::num it) {
+  abstract inline-class-member representation-field get it() → core::num;
+  constructor • = self::E1|constructor#;
+  constructor tearoff • = self::E1|constructor#_#new#tearOff;
+}
+extension type E2(self::E1 /* = core::num */ it) implements self::E1 /* = core::num */ {
+  abstract inline-class-member representation-field get it() → self::E1 /* = core::num */;
+  constructor • = self::E2|constructor#;
+  constructor tearoff • = self::E2|constructor#_#new#tearOff;
+}
+extension type E3(core::int it) implements self::E1 /* = core::num */ {
+  abstract inline-class-member representation-field get it() → core::int;
+  constructor • = self::E3|constructor#;
+  constructor tearoff • = self::E3|constructor#_#new#tearOff;
+}
+extension type E4(self::E3 /* = core::int */ it) implements self::E1 /* = core::num */ {
+  abstract inline-class-member representation-field get it() → self::E3 /* = core::int */;
+  constructor • = self::E4|constructor#;
+  constructor tearoff • = self::E4|constructor#_#new#tearOff;
+}
+extension type E5(self::E3 /* = core::int */ it) implements self::E2 /* = core::num */ {
+  abstract inline-class-member representation-field get it() → self::E3 /* = core::int */;
+  constructor • = self::E5|constructor#;
+  constructor tearoff • = self::E5|constructor#_#new#tearOff;
+}
+extension type E6(self::E2 /* = core::num */ it) implements self::E3 /* = core::int */ {
+  abstract inline-class-member representation-field get it() → self::E2 /* = core::num */;
+  constructor • = self::E6|constructor#;
+  constructor tearoff • = self::E6|constructor#_#new#tearOff;
+}
+extension type E7(core::String it) implements self::E1 /* = core::num */ {
+  abstract inline-class-member representation-field get it() → core::String;
+  constructor • = self::E7|constructor#;
+  constructor tearoff • = self::E7|constructor#_#new#tearOff;
+}
+static inline-class-member method E1|constructor#(core::num it) → self::E1 /* = core::num */ {
+  lowered final self::E1 /* = core::num */ #this = it;
+  return #this;
+}
+static inline-class-member method E1|constructor#_#new#tearOff(core::num it) → self::E1 /* = core::num */
+  return self::E1|constructor#(it);
+static inline-class-member method E2|constructor#(self::E1 /* = core::num */ it) → self::E2 /* = core::num */ {
+  lowered final self::E2 /* = core::num */ #this = it;
+  return #this;
+}
+static inline-class-member method E2|constructor#_#new#tearOff(self::E1 /* = core::num */ it) → self::E2 /* = core::num */
+  return self::E2|constructor#(it);
+static inline-class-member method E3|constructor#(core::int it) → self::E3 /* = core::int */ {
+  lowered final self::E3 /* = core::int */ #this = it;
+  return #this;
+}
+static inline-class-member method E3|constructor#_#new#tearOff(core::int it) → self::E3 /* = core::int */
+  return self::E3|constructor#(it);
+static inline-class-member method E4|constructor#(self::E3 /* = core::int */ it) → self::E4 /* = core::int */ {
+  lowered final self::E4 /* = core::int */ #this = it;
+  return #this;
+}
+static inline-class-member method E4|constructor#_#new#tearOff(self::E3 /* = core::int */ it) → self::E4 /* = core::int */
+  return self::E4|constructor#(it);
+static inline-class-member method E5|constructor#(self::E3 /* = core::int */ it) → self::E5 /* = core::int */ {
+  lowered final self::E5 /* = core::int */ #this = it;
+  return #this;
+}
+static inline-class-member method E5|constructor#_#new#tearOff(self::E3 /* = core::int */ it) → self::E5 /* = core::int */
+  return self::E5|constructor#(it);
+static inline-class-member method E6|constructor#(self::E2 /* = core::num */ it) → self::E6 /* = core::num */ {
+  lowered final self::E6 /* = core::num */ #this = it;
+  return #this;
+}
+static inline-class-member method E6|constructor#_#new#tearOff(self::E2 /* = core::num */ it) → self::E6 /* = core::num */
+  return self::E6|constructor#(it);
+static inline-class-member method E7|constructor#(core::String it) → self::E7 /* = core::String */ {
+  lowered final self::E7 /* = core::String */ #this = it;
+  return #this;
+}
+static inline-class-member method E7|constructor#_#new#tearOff(core::String it) → self::E7 /* = core::String */
+  return self::E7|constructor#(it);
diff --git a/pkg/front_end/testcases/extension_types/issue53867.dart.strong.transformed.expect b/pkg/front_end/testcases/extension_types/issue53867.dart.strong.transformed.expect
new file mode 100644
index 0000000..f9370be
--- /dev/null
+++ b/pkg/front_end/testcases/extension_types/issue53867.dart.strong.transformed.expect
@@ -0,0 +1,94 @@
+library;
+//
+// Problems in library:
+//
+// pkg/front_end/testcases/extension_types/issue53867.dart:10:37: Error: The representation type 'E2' of extension type 'E6' must be either a subtype of the representation type 'int' of the implemented extension type 'E3' or a subtype of 'E3' itself.
+// Try changing the representation type to a subtype of 'int'.
+// extension type E6(E2 it) implements E3 {} // Error.
+//                                     ^
+//
+// pkg/front_end/testcases/extension_types/issue53867.dart:11:41: Error: The representation type 'String' of extension type 'E7' must be either a subtype of the representation type 'num' of the implemented extension type 'E1' or a subtype of 'E1' itself.
+// Try changing the representation type to a subtype of 'num'.
+// extension type E7(String it) implements E1 {} // Error.
+//                                         ^
+//
+import self as self;
+import "dart:core" as core;
+
+extension type E1(core::num it) {
+  abstract inline-class-member representation-field get it() → core::num;
+  constructor • = self::E1|constructor#;
+  constructor tearoff • = self::E1|constructor#_#new#tearOff;
+}
+extension type E2(self::E1 /* = core::num */ it) implements self::E1 /* = core::num */ {
+  abstract inline-class-member representation-field get it() → self::E1 /* = core::num */;
+  constructor • = self::E2|constructor#;
+  constructor tearoff • = self::E2|constructor#_#new#tearOff;
+}
+extension type E3(core::int it) implements self::E1 /* = core::num */ {
+  abstract inline-class-member representation-field get it() → core::int;
+  constructor • = self::E3|constructor#;
+  constructor tearoff • = self::E3|constructor#_#new#tearOff;
+}
+extension type E4(self::E3 /* = core::int */ it) implements self::E1 /* = core::num */ {
+  abstract inline-class-member representation-field get it() → self::E3 /* = core::int */;
+  constructor • = self::E4|constructor#;
+  constructor tearoff • = self::E4|constructor#_#new#tearOff;
+}
+extension type E5(self::E3 /* = core::int */ it) implements self::E2 /* = core::num */ {
+  abstract inline-class-member representation-field get it() → self::E3 /* = core::int */;
+  constructor • = self::E5|constructor#;
+  constructor tearoff • = self::E5|constructor#_#new#tearOff;
+}
+extension type E6(self::E2 /* = core::num */ it) implements self::E3 /* = core::int */ {
+  abstract inline-class-member representation-field get it() → self::E2 /* = core::num */;
+  constructor • = self::E6|constructor#;
+  constructor tearoff • = self::E6|constructor#_#new#tearOff;
+}
+extension type E7(core::String it) implements self::E1 /* = core::num */ {
+  abstract inline-class-member representation-field get it() → core::String;
+  constructor • = self::E7|constructor#;
+  constructor tearoff • = self::E7|constructor#_#new#tearOff;
+}
+static inline-class-member method E1|constructor#(core::num it) → self::E1 /* = core::num */ {
+  lowered final self::E1 /* = core::num */ #this = it;
+  return #this;
+}
+static inline-class-member method E1|constructor#_#new#tearOff(core::num it) → self::E1 /* = core::num */
+  return self::E1|constructor#(it);
+static inline-class-member method E2|constructor#(self::E1 /* = core::num */ it) → self::E2 /* = core::num */ {
+  lowered final self::E2 /* = core::num */ #this = it;
+  return #this;
+}
+static inline-class-member method E2|constructor#_#new#tearOff(self::E1 /* = core::num */ it) → self::E2 /* = core::num */
+  return self::E2|constructor#(it);
+static inline-class-member method E3|constructor#(core::int it) → self::E3 /* = core::int */ {
+  lowered final self::E3 /* = core::int */ #this = it;
+  return #this;
+}
+static inline-class-member method E3|constructor#_#new#tearOff(core::int it) → self::E3 /* = core::int */
+  return self::E3|constructor#(it);
+static inline-class-member method E4|constructor#(self::E3 /* = core::int */ it) → self::E4 /* = core::int */ {
+  lowered final self::E4 /* = core::int */ #this = it;
+  return #this;
+}
+static inline-class-member method E4|constructor#_#new#tearOff(self::E3 /* = core::int */ it) → self::E4 /* = core::int */
+  return self::E4|constructor#(it);
+static inline-class-member method E5|constructor#(self::E3 /* = core::int */ it) → self::E5 /* = core::int */ {
+  lowered final self::E5 /* = core::int */ #this = it;
+  return #this;
+}
+static inline-class-member method E5|constructor#_#new#tearOff(self::E3 /* = core::int */ it) → self::E5 /* = core::int */
+  return self::E5|constructor#(it);
+static inline-class-member method E6|constructor#(self::E2 /* = core::num */ it) → self::E6 /* = core::num */ {
+  lowered final self::E6 /* = core::num */ #this = it;
+  return #this;
+}
+static inline-class-member method E6|constructor#_#new#tearOff(self::E2 /* = core::num */ it) → self::E6 /* = core::num */
+  return self::E6|constructor#(it);
+static inline-class-member method E7|constructor#(core::String it) → self::E7 /* = core::String */ {
+  lowered final self::E7 /* = core::String */ #this = it;
+  return #this;
+}
+static inline-class-member method E7|constructor#_#new#tearOff(core::String it) → self::E7 /* = core::String */
+  return self::E7|constructor#(it);
diff --git a/pkg/front_end/testcases/extension_types/issue53867.dart.textual_outline.expect b/pkg/front_end/testcases/extension_types/issue53867.dart.textual_outline.expect
new file mode 100644
index 0000000..d5e591f
--- /dev/null
+++ b/pkg/front_end/testcases/extension_types/issue53867.dart.textual_outline.expect
@@ -0,0 +1,7 @@
+extension type E1(num it) {}
+extension type E2(E1 it) implements E1 {}
+extension type E3(int it) implements E1 {}
+extension type E4(E3 it) implements E1 {}
+extension type E5(E3 it) implements E2 {}
+extension type E6(E2 it) implements E3 {}
+extension type E7(String it) implements E1 {}
diff --git a/pkg/front_end/testcases/extension_types/issue53867.dart.textual_outline_modelled.expect b/pkg/front_end/testcases/extension_types/issue53867.dart.textual_outline_modelled.expect
new file mode 100644
index 0000000..d5e591f
--- /dev/null
+++ b/pkg/front_end/testcases/extension_types/issue53867.dart.textual_outline_modelled.expect
@@ -0,0 +1,7 @@
+extension type E1(num it) {}
+extension type E2(E1 it) implements E1 {}
+extension type E3(int it) implements E1 {}
+extension type E4(E3 it) implements E1 {}
+extension type E5(E3 it) implements E2 {}
+extension type E6(E2 it) implements E3 {}
+extension type E7(String it) implements E1 {}
diff --git a/pkg/front_end/testcases/extension_types/issue53867.dart.weak.expect b/pkg/front_end/testcases/extension_types/issue53867.dart.weak.expect
new file mode 100644
index 0000000..f9370be
--- /dev/null
+++ b/pkg/front_end/testcases/extension_types/issue53867.dart.weak.expect
@@ -0,0 +1,94 @@
+library;
+//
+// Problems in library:
+//
+// pkg/front_end/testcases/extension_types/issue53867.dart:10:37: Error: The representation type 'E2' of extension type 'E6' must be either a subtype of the representation type 'int' of the implemented extension type 'E3' or a subtype of 'E3' itself.
+// Try changing the representation type to a subtype of 'int'.
+// extension type E6(E2 it) implements E3 {} // Error.
+//                                     ^
+//
+// pkg/front_end/testcases/extension_types/issue53867.dart:11:41: Error: The representation type 'String' of extension type 'E7' must be either a subtype of the representation type 'num' of the implemented extension type 'E1' or a subtype of 'E1' itself.
+// Try changing the representation type to a subtype of 'num'.
+// extension type E7(String it) implements E1 {} // Error.
+//                                         ^
+//
+import self as self;
+import "dart:core" as core;
+
+extension type E1(core::num it) {
+  abstract inline-class-member representation-field get it() → core::num;
+  constructor • = self::E1|constructor#;
+  constructor tearoff • = self::E1|constructor#_#new#tearOff;
+}
+extension type E2(self::E1 /* = core::num */ it) implements self::E1 /* = core::num */ {
+  abstract inline-class-member representation-field get it() → self::E1 /* = core::num */;
+  constructor • = self::E2|constructor#;
+  constructor tearoff • = self::E2|constructor#_#new#tearOff;
+}
+extension type E3(core::int it) implements self::E1 /* = core::num */ {
+  abstract inline-class-member representation-field get it() → core::int;
+  constructor • = self::E3|constructor#;
+  constructor tearoff • = self::E3|constructor#_#new#tearOff;
+}
+extension type E4(self::E3 /* = core::int */ it) implements self::E1 /* = core::num */ {
+  abstract inline-class-member representation-field get it() → self::E3 /* = core::int */;
+  constructor • = self::E4|constructor#;
+  constructor tearoff • = self::E4|constructor#_#new#tearOff;
+}
+extension type E5(self::E3 /* = core::int */ it) implements self::E2 /* = core::num */ {
+  abstract inline-class-member representation-field get it() → self::E3 /* = core::int */;
+  constructor • = self::E5|constructor#;
+  constructor tearoff • = self::E5|constructor#_#new#tearOff;
+}
+extension type E6(self::E2 /* = core::num */ it) implements self::E3 /* = core::int */ {
+  abstract inline-class-member representation-field get it() → self::E2 /* = core::num */;
+  constructor • = self::E6|constructor#;
+  constructor tearoff • = self::E6|constructor#_#new#tearOff;
+}
+extension type E7(core::String it) implements self::E1 /* = core::num */ {
+  abstract inline-class-member representation-field get it() → core::String;
+  constructor • = self::E7|constructor#;
+  constructor tearoff • = self::E7|constructor#_#new#tearOff;
+}
+static inline-class-member method E1|constructor#(core::num it) → self::E1 /* = core::num */ {
+  lowered final self::E1 /* = core::num */ #this = it;
+  return #this;
+}
+static inline-class-member method E1|constructor#_#new#tearOff(core::num it) → self::E1 /* = core::num */
+  return self::E1|constructor#(it);
+static inline-class-member method E2|constructor#(self::E1 /* = core::num */ it) → self::E2 /* = core::num */ {
+  lowered final self::E2 /* = core::num */ #this = it;
+  return #this;
+}
+static inline-class-member method E2|constructor#_#new#tearOff(self::E1 /* = core::num */ it) → self::E2 /* = core::num */
+  return self::E2|constructor#(it);
+static inline-class-member method E3|constructor#(core::int it) → self::E3 /* = core::int */ {
+  lowered final self::E3 /* = core::int */ #this = it;
+  return #this;
+}
+static inline-class-member method E3|constructor#_#new#tearOff(core::int it) → self::E3 /* = core::int */
+  return self::E3|constructor#(it);
+static inline-class-member method E4|constructor#(self::E3 /* = core::int */ it) → self::E4 /* = core::int */ {
+  lowered final self::E4 /* = core::int */ #this = it;
+  return #this;
+}
+static inline-class-member method E4|constructor#_#new#tearOff(self::E3 /* = core::int */ it) → self::E4 /* = core::int */
+  return self::E4|constructor#(it);
+static inline-class-member method E5|constructor#(self::E3 /* = core::int */ it) → self::E5 /* = core::int */ {
+  lowered final self::E5 /* = core::int */ #this = it;
+  return #this;
+}
+static inline-class-member method E5|constructor#_#new#tearOff(self::E3 /* = core::int */ it) → self::E5 /* = core::int */
+  return self::E5|constructor#(it);
+static inline-class-member method E6|constructor#(self::E2 /* = core::num */ it) → self::E6 /* = core::num */ {
+  lowered final self::E6 /* = core::num */ #this = it;
+  return #this;
+}
+static inline-class-member method E6|constructor#_#new#tearOff(self::E2 /* = core::num */ it) → self::E6 /* = core::num */
+  return self::E6|constructor#(it);
+static inline-class-member method E7|constructor#(core::String it) → self::E7 /* = core::String */ {
+  lowered final self::E7 /* = core::String */ #this = it;
+  return #this;
+}
+static inline-class-member method E7|constructor#_#new#tearOff(core::String it) → self::E7 /* = core::String */
+  return self::E7|constructor#(it);
diff --git a/pkg/front_end/testcases/extension_types/issue53867.dart.weak.modular.expect b/pkg/front_end/testcases/extension_types/issue53867.dart.weak.modular.expect
new file mode 100644
index 0000000..f9370be
--- /dev/null
+++ b/pkg/front_end/testcases/extension_types/issue53867.dart.weak.modular.expect
@@ -0,0 +1,94 @@
+library;
+//
+// Problems in library:
+//
+// pkg/front_end/testcases/extension_types/issue53867.dart:10:37: Error: The representation type 'E2' of extension type 'E6' must be either a subtype of the representation type 'int' of the implemented extension type 'E3' or a subtype of 'E3' itself.
+// Try changing the representation type to a subtype of 'int'.
+// extension type E6(E2 it) implements E3 {} // Error.
+//                                     ^
+//
+// pkg/front_end/testcases/extension_types/issue53867.dart:11:41: Error: The representation type 'String' of extension type 'E7' must be either a subtype of the representation type 'num' of the implemented extension type 'E1' or a subtype of 'E1' itself.
+// Try changing the representation type to a subtype of 'num'.
+// extension type E7(String it) implements E1 {} // Error.
+//                                         ^
+//
+import self as self;
+import "dart:core" as core;
+
+extension type E1(core::num it) {
+  abstract inline-class-member representation-field get it() → core::num;
+  constructor • = self::E1|constructor#;
+  constructor tearoff • = self::E1|constructor#_#new#tearOff;
+}
+extension type E2(self::E1 /* = core::num */ it) implements self::E1 /* = core::num */ {
+  abstract inline-class-member representation-field get it() → self::E1 /* = core::num */;
+  constructor • = self::E2|constructor#;
+  constructor tearoff • = self::E2|constructor#_#new#tearOff;
+}
+extension type E3(core::int it) implements self::E1 /* = core::num */ {
+  abstract inline-class-member representation-field get it() → core::int;
+  constructor • = self::E3|constructor#;
+  constructor tearoff • = self::E3|constructor#_#new#tearOff;
+}
+extension type E4(self::E3 /* = core::int */ it) implements self::E1 /* = core::num */ {
+  abstract inline-class-member representation-field get it() → self::E3 /* = core::int */;
+  constructor • = self::E4|constructor#;
+  constructor tearoff • = self::E4|constructor#_#new#tearOff;
+}
+extension type E5(self::E3 /* = core::int */ it) implements self::E2 /* = core::num */ {
+  abstract inline-class-member representation-field get it() → self::E3 /* = core::int */;
+  constructor • = self::E5|constructor#;
+  constructor tearoff • = self::E5|constructor#_#new#tearOff;
+}
+extension type E6(self::E2 /* = core::num */ it) implements self::E3 /* = core::int */ {
+  abstract inline-class-member representation-field get it() → self::E2 /* = core::num */;
+  constructor • = self::E6|constructor#;
+  constructor tearoff • = self::E6|constructor#_#new#tearOff;
+}
+extension type E7(core::String it) implements self::E1 /* = core::num */ {
+  abstract inline-class-member representation-field get it() → core::String;
+  constructor • = self::E7|constructor#;
+  constructor tearoff • = self::E7|constructor#_#new#tearOff;
+}
+static inline-class-member method E1|constructor#(core::num it) → self::E1 /* = core::num */ {
+  lowered final self::E1 /* = core::num */ #this = it;
+  return #this;
+}
+static inline-class-member method E1|constructor#_#new#tearOff(core::num it) → self::E1 /* = core::num */
+  return self::E1|constructor#(it);
+static inline-class-member method E2|constructor#(self::E1 /* = core::num */ it) → self::E2 /* = core::num */ {
+  lowered final self::E2 /* = core::num */ #this = it;
+  return #this;
+}
+static inline-class-member method E2|constructor#_#new#tearOff(self::E1 /* = core::num */ it) → self::E2 /* = core::num */
+  return self::E2|constructor#(it);
+static inline-class-member method E3|constructor#(core::int it) → self::E3 /* = core::int */ {
+  lowered final self::E3 /* = core::int */ #this = it;
+  return #this;
+}
+static inline-class-member method E3|constructor#_#new#tearOff(core::int it) → self::E3 /* = core::int */
+  return self::E3|constructor#(it);
+static inline-class-member method E4|constructor#(self::E3 /* = core::int */ it) → self::E4 /* = core::int */ {
+  lowered final self::E4 /* = core::int */ #this = it;
+  return #this;
+}
+static inline-class-member method E4|constructor#_#new#tearOff(self::E3 /* = core::int */ it) → self::E4 /* = core::int */
+  return self::E4|constructor#(it);
+static inline-class-member method E5|constructor#(self::E3 /* = core::int */ it) → self::E5 /* = core::int */ {
+  lowered final self::E5 /* = core::int */ #this = it;
+  return #this;
+}
+static inline-class-member method E5|constructor#_#new#tearOff(self::E3 /* = core::int */ it) → self::E5 /* = core::int */
+  return self::E5|constructor#(it);
+static inline-class-member method E6|constructor#(self::E2 /* = core::num */ it) → self::E6 /* = core::num */ {
+  lowered final self::E6 /* = core::num */ #this = it;
+  return #this;
+}
+static inline-class-member method E6|constructor#_#new#tearOff(self::E2 /* = core::num */ it) → self::E6 /* = core::num */
+  return self::E6|constructor#(it);
+static inline-class-member method E7|constructor#(core::String it) → self::E7 /* = core::String */ {
+  lowered final self::E7 /* = core::String */ #this = it;
+  return #this;
+}
+static inline-class-member method E7|constructor#_#new#tearOff(core::String it) → self::E7 /* = core::String */
+  return self::E7|constructor#(it);
diff --git a/pkg/front_end/testcases/extension_types/issue53867.dart.weak.outline.expect b/pkg/front_end/testcases/extension_types/issue53867.dart.weak.outline.expect
new file mode 100644
index 0000000..842e6e2
--- /dev/null
+++ b/pkg/front_end/testcases/extension_types/issue53867.dart.weak.outline.expect
@@ -0,0 +1,80 @@
+library;
+//
+// Problems in library:
+//
+// pkg/front_end/testcases/extension_types/issue53867.dart:10:37: Error: The representation type 'E2' of extension type 'E6' must be either a subtype of the representation type 'int' of the implemented extension type 'E3' or a subtype of 'E3' itself.
+// Try changing the representation type to a subtype of 'int'.
+// extension type E6(E2 it) implements E3 {} // Error.
+//                                     ^
+//
+// pkg/front_end/testcases/extension_types/issue53867.dart:11:41: Error: The representation type 'String' of extension type 'E7' must be either a subtype of the representation type 'num' of the implemented extension type 'E1' or a subtype of 'E1' itself.
+// Try changing the representation type to a subtype of 'num'.
+// extension type E7(String it) implements E1 {} // Error.
+//                                         ^
+//
+import self as self;
+import "dart:core" as core;
+
+extension type E1(core::num it) {
+  abstract inline-class-member representation-field get it() → core::num;
+  constructor • = self::E1|constructor#;
+  constructor tearoff • = self::E1|constructor#_#new#tearOff;
+}
+extension type E2(self::E1 /* = core::num */ it) implements self::E1 /* = core::num */ {
+  abstract inline-class-member representation-field get it() → self::E1 /* = core::num */;
+  constructor • = self::E2|constructor#;
+  constructor tearoff • = self::E2|constructor#_#new#tearOff;
+}
+extension type E3(core::int it) implements self::E1 /* = core::num */ {
+  abstract inline-class-member representation-field get it() → core::int;
+  constructor • = self::E3|constructor#;
+  constructor tearoff • = self::E3|constructor#_#new#tearOff;
+}
+extension type E4(self::E3 /* = core::int */ it) implements self::E1 /* = core::num */ {
+  abstract inline-class-member representation-field get it() → self::E3 /* = core::int */;
+  constructor • = self::E4|constructor#;
+  constructor tearoff • = self::E4|constructor#_#new#tearOff;
+}
+extension type E5(self::E3 /* = core::int */ it) implements self::E2 /* = core::num */ {
+  abstract inline-class-member representation-field get it() → self::E3 /* = core::int */;
+  constructor • = self::E5|constructor#;
+  constructor tearoff • = self::E5|constructor#_#new#tearOff;
+}
+extension type E6(self::E2 /* = core::num */ it) implements self::E3 /* = core::int */ {
+  abstract inline-class-member representation-field get it() → self::E2 /* = core::num */;
+  constructor • = self::E6|constructor#;
+  constructor tearoff • = self::E6|constructor#_#new#tearOff;
+}
+extension type E7(core::String it) implements self::E1 /* = core::num */ {
+  abstract inline-class-member representation-field get it() → core::String;
+  constructor • = self::E7|constructor#;
+  constructor tearoff • = self::E7|constructor#_#new#tearOff;
+}
+static inline-class-member method E1|constructor#(core::num it) → self::E1 /* = core::num */
+  ;
+static inline-class-member method E1|constructor#_#new#tearOff(core::num it) → self::E1 /* = core::num */
+  return self::E1|constructor#(it);
+static inline-class-member method E2|constructor#(self::E1 /* = core::num */ it) → self::E2 /* = core::num */
+  ;
+static inline-class-member method E2|constructor#_#new#tearOff(self::E1 /* = core::num */ it) → self::E2 /* = core::num */
+  return self::E2|constructor#(it);
+static inline-class-member method E3|constructor#(core::int it) → self::E3 /* = core::int */
+  ;
+static inline-class-member method E3|constructor#_#new#tearOff(core::int it) → self::E3 /* = core::int */
+  return self::E3|constructor#(it);
+static inline-class-member method E4|constructor#(self::E3 /* = core::int */ it) → self::E4 /* = core::int */
+  ;
+static inline-class-member method E4|constructor#_#new#tearOff(self::E3 /* = core::int */ it) → self::E4 /* = core::int */
+  return self::E4|constructor#(it);
+static inline-class-member method E5|constructor#(self::E3 /* = core::int */ it) → self::E5 /* = core::int */
+  ;
+static inline-class-member method E5|constructor#_#new#tearOff(self::E3 /* = core::int */ it) → self::E5 /* = core::int */
+  return self::E5|constructor#(it);
+static inline-class-member method E6|constructor#(self::E2 /* = core::num */ it) → self::E6 /* = core::num */
+  ;
+static inline-class-member method E6|constructor#_#new#tearOff(self::E2 /* = core::num */ it) → self::E6 /* = core::num */
+  return self::E6|constructor#(it);
+static inline-class-member method E7|constructor#(core::String it) → self::E7 /* = core::String */
+  ;
+static inline-class-member method E7|constructor#_#new#tearOff(core::String it) → self::E7 /* = core::String */
+  return self::E7|constructor#(it);
diff --git a/pkg/front_end/testcases/extension_types/issue53867.dart.weak.transformed.expect b/pkg/front_end/testcases/extension_types/issue53867.dart.weak.transformed.expect
new file mode 100644
index 0000000..f9370be
--- /dev/null
+++ b/pkg/front_end/testcases/extension_types/issue53867.dart.weak.transformed.expect
@@ -0,0 +1,94 @@
+library;
+//
+// Problems in library:
+//
+// pkg/front_end/testcases/extension_types/issue53867.dart:10:37: Error: The representation type 'E2' of extension type 'E6' must be either a subtype of the representation type 'int' of the implemented extension type 'E3' or a subtype of 'E3' itself.
+// Try changing the representation type to a subtype of 'int'.
+// extension type E6(E2 it) implements E3 {} // Error.
+//                                     ^
+//
+// pkg/front_end/testcases/extension_types/issue53867.dart:11:41: Error: The representation type 'String' of extension type 'E7' must be either a subtype of the representation type 'num' of the implemented extension type 'E1' or a subtype of 'E1' itself.
+// Try changing the representation type to a subtype of 'num'.
+// extension type E7(String it) implements E1 {} // Error.
+//                                         ^
+//
+import self as self;
+import "dart:core" as core;
+
+extension type E1(core::num it) {
+  abstract inline-class-member representation-field get it() → core::num;
+  constructor • = self::E1|constructor#;
+  constructor tearoff • = self::E1|constructor#_#new#tearOff;
+}
+extension type E2(self::E1 /* = core::num */ it) implements self::E1 /* = core::num */ {
+  abstract inline-class-member representation-field get it() → self::E1 /* = core::num */;
+  constructor • = self::E2|constructor#;
+  constructor tearoff • = self::E2|constructor#_#new#tearOff;
+}
+extension type E3(core::int it) implements self::E1 /* = core::num */ {
+  abstract inline-class-member representation-field get it() → core::int;
+  constructor • = self::E3|constructor#;
+  constructor tearoff • = self::E3|constructor#_#new#tearOff;
+}
+extension type E4(self::E3 /* = core::int */ it) implements self::E1 /* = core::num */ {
+  abstract inline-class-member representation-field get it() → self::E3 /* = core::int */;
+  constructor • = self::E4|constructor#;
+  constructor tearoff • = self::E4|constructor#_#new#tearOff;
+}
+extension type E5(self::E3 /* = core::int */ it) implements self::E2 /* = core::num */ {
+  abstract inline-class-member representation-field get it() → self::E3 /* = core::int */;
+  constructor • = self::E5|constructor#;
+  constructor tearoff • = self::E5|constructor#_#new#tearOff;
+}
+extension type E6(self::E2 /* = core::num */ it) implements self::E3 /* = core::int */ {
+  abstract inline-class-member representation-field get it() → self::E2 /* = core::num */;
+  constructor • = self::E6|constructor#;
+  constructor tearoff • = self::E6|constructor#_#new#tearOff;
+}
+extension type E7(core::String it) implements self::E1 /* = core::num */ {
+  abstract inline-class-member representation-field get it() → core::String;
+  constructor • = self::E7|constructor#;
+  constructor tearoff • = self::E7|constructor#_#new#tearOff;
+}
+static inline-class-member method E1|constructor#(core::num it) → self::E1 /* = core::num */ {
+  lowered final self::E1 /* = core::num */ #this = it;
+  return #this;
+}
+static inline-class-member method E1|constructor#_#new#tearOff(core::num it) → self::E1 /* = core::num */
+  return self::E1|constructor#(it);
+static inline-class-member method E2|constructor#(self::E1 /* = core::num */ it) → self::E2 /* = core::num */ {
+  lowered final self::E2 /* = core::num */ #this = it;
+  return #this;
+}
+static inline-class-member method E2|constructor#_#new#tearOff(self::E1 /* = core::num */ it) → self::E2 /* = core::num */
+  return self::E2|constructor#(it);
+static inline-class-member method E3|constructor#(core::int it) → self::E3 /* = core::int */ {
+  lowered final self::E3 /* = core::int */ #this = it;
+  return #this;
+}
+static inline-class-member method E3|constructor#_#new#tearOff(core::int it) → self::E3 /* = core::int */
+  return self::E3|constructor#(it);
+static inline-class-member method E4|constructor#(self::E3 /* = core::int */ it) → self::E4 /* = core::int */ {
+  lowered final self::E4 /* = core::int */ #this = it;
+  return #this;
+}
+static inline-class-member method E4|constructor#_#new#tearOff(self::E3 /* = core::int */ it) → self::E4 /* = core::int */
+  return self::E4|constructor#(it);
+static inline-class-member method E5|constructor#(self::E3 /* = core::int */ it) → self::E5 /* = core::int */ {
+  lowered final self::E5 /* = core::int */ #this = it;
+  return #this;
+}
+static inline-class-member method E5|constructor#_#new#tearOff(self::E3 /* = core::int */ it) → self::E5 /* = core::int */
+  return self::E5|constructor#(it);
+static inline-class-member method E6|constructor#(self::E2 /* = core::num */ it) → self::E6 /* = core::num */ {
+  lowered final self::E6 /* = core::num */ #this = it;
+  return #this;
+}
+static inline-class-member method E6|constructor#_#new#tearOff(self::E2 /* = core::num */ it) → self::E6 /* = core::num */
+  return self::E6|constructor#(it);
+static inline-class-member method E7|constructor#(core::String it) → self::E7 /* = core::String */ {
+  lowered final self::E7 /* = core::String */ #this = it;
+  return #this;
+}
+static inline-class-member method E7|constructor#_#new#tearOff(core::String it) → self::E7 /* = core::String */
+  return self::E7|constructor#(it);
diff --git a/runtime/tests/vm/dart/address_local_pointer_il_test.dart b/runtime/tests/vm/dart/address_local_pointer_il_test.dart
index 0842756..eb290f5 100644
--- a/runtime/tests/vm/dart/address_local_pointer_il_test.dart
+++ b/runtime/tests/vm/dart/address_local_pointer_il_test.dart
@@ -17,32 +17,21 @@
 int identity(int address) => Pointer<Void>.fromAddress(address).address;
 
 void matchIL$identity(FlowGraph graph) {
-  graph.dump();
-  if (is32BitConfiguration) {
-    // The Dart int address is truncated before being returned.
-    graph.match([
-      match.block('Graph'),
-      match.block('Function', [
-        'address' << match.Parameter(index: 0),
-        'int32' <<
-            match.IntConverter('address',
-                from: 'int64', to: 'int32', is_truncating: true),
+  final retval = is32BitConfiguration ? 'retval' : 'address';
+  graph.match([
+    match.block('Graph'),
+    match.block('Function', [
+      'address' << match.Parameter(index: 0),
+      if (is32BitConfiguration) ...[
+        // The Dart int address is truncated before being returned.
         'uint32' <<
-            match.IntConverter('int32',
-                from: 'int32', to: 'uint32', is_truncating: true),
+            match.IntConverter('address',
+                from: 'int64', to: 'uint32', is_truncating: true),
         'retval' << match.IntConverter('uint32', from: 'uint32', to: 'int64'),
-        match.Return('retval'),
-      ]),
-    ]);
-  } else {
-    graph.match([
-      match.block('Graph'),
-      match.block('Function', [
-        'address' << match.Parameter(index: 0),
-        match.Return('address'),
-      ]),
-    ]);
-  }
+      ],
+      match.Return(retval),
+    ]),
+  ]);
 }
 
 void main(List<String> args) {
diff --git a/runtime/tests/vm/dart/regress_306327173_il_test.dart b/runtime/tests/vm/dart/regress_306327173_il_test.dart
index a9206d3e..e3c22ff 100644
--- a/runtime/tests/vm/dart/regress_306327173_il_test.dart
+++ b/runtime/tests/vm/dart/regress_306327173_il_test.dart
@@ -30,12 +30,7 @@
       // and int64 on 64-bit arches.
       if (is32BitConfiguration) ...[
         // 'unboxed' needs to be converted to int64 before returning.
-        //
-        // Note: The first two conversions here should be fixed once all
-        // kUnboxedIntPtr uses are appropriately converted to kUnboxedFfiIntPtr.
-        'extra1' << match.IntConverter('unboxed', from: 'uint32', to: 'int32'),
-        'extra2' << match.IntConverter('extra1', from: 'int32', to: 'uint32'),
-        'address' << match.IntConverter('extra2', from: 'uint32', to: 'int64'),
+        'address' << match.IntConverter('unboxed', from: 'uint32', to: 'int64'),
       ],
       match.Return(retvalName),
     ]),
diff --git a/runtime/vm/compiler/backend/il.cc b/runtime/vm/compiler/backend/il.cc
index a99cef7..88a876a 100644
--- a/runtime/vm/compiler/backend/il.cc
+++ b/runtime/vm/compiler/backend/il.cc
@@ -1980,41 +1980,25 @@
                            RangeBoundary::kRangeBoundaryInt32);
 }
 
-bool UnboxInt32Instr::ComputeCanDeoptimize() const {
+bool UnboxIntegerInstr::ComputeCanDeoptimize() const {
   if (SpeculativeModeOfInputs() == kNotSpeculative) {
     return false;
   }
-  const intptr_t value_cid = value()->Type()->ToCid();
-  if (value_cid == kSmiCid) {
-    return (compiler::target::kSmiBits > 32) && !is_truncating() &&
-           !RangeUtils::Fits(value()->definition()->range(),
-                             RangeBoundary::kRangeBoundaryInt32);
-  } else if (value_cid == kMintCid) {
-    return !is_truncating() &&
-           !RangeUtils::Fits(value()->definition()->range(),
-                             RangeBoundary::kRangeBoundaryInt32);
-  } else if (is_truncating() && value()->definition()->IsBoxInteger()) {
-    return false;
-  } else if ((compiler::target::kSmiBits < 32) && value()->Type()->IsInt()) {
-    return !RangeUtils::Fits(value()->definition()->range(),
-                             RangeBoundary::kRangeBoundaryInt32);
-  } else {
+  if (!value()->Type()->IsInt()) {
     return true;
   }
-}
-
-bool UnboxUint32Instr::ComputeCanDeoptimize() const {
-  ASSERT(is_truncating());
-  if (SpeculativeModeOfInputs() == kNotSpeculative) {
+  if (representation() == kUnboxedInt64 || is_truncating()) {
     return false;
   }
-  if ((value()->Type()->ToCid() == kSmiCid) ||
-      (value()->Type()->ToCid() == kMintCid)) {
+  const intptr_t rep_bitsize =
+      RepresentationUtils::ValueSize(representation()) * kBitsPerByte;
+  if (value()->Type()->ToCid() == kSmiCid &&
+      compiler::target::kSmiBits <= rep_bitsize) {
     return false;
   }
-  // Check input value's range.
-  Range* value_range = value()->definition()->range();
-  return !RangeUtils::Fits(value_range, RangeBoundary::kRangeBoundaryInt64);
+  return !RangeUtils::IsWithin(value()->definition()->range(),
+                               RepresentationUtils::MinValue(representation()),
+                               RepresentationUtils::MaxValue(representation()));
 }
 
 bool BinaryInt32OpInstr::ComputeCanDeoptimize() const {
@@ -3306,40 +3290,24 @@
     set_speculative_mode(kNotSpeculative);
   }
 
-  return this;
-}
-
-Definition* UnboxInt32Instr::Canonicalize(FlowGraph* flow_graph) {
-  Definition* replacement = UnboxIntegerInstr::Canonicalize(flow_graph);
-  if (replacement != this) {
-    return replacement;
-  }
-
-  ConstantInstr* c = value()->definition()->AsConstant();
-  if ((c != nullptr) && c->value().IsInteger()) {
-    if (!is_truncating()) {
-      // Check that constant fits into 32-bit integer.
-      const int64_t value = Integer::Cast(c->value()).AsInt64Value();
-      if (!Utils::IsInt(32, value)) {
-        return this;
+  if (value()->BindsToConstant()) {
+    const auto& obj = value()->BoundConstant();
+    if (obj.IsInteger()) {
+      if (representation() == kUnboxedInt64) {
+        return flow_graph->GetConstant(obj, representation());
+      }
+      const int64_t intval = Integer::Cast(obj).AsInt64Value();
+      if (RepresentationUtils::IsRepresentable(representation(), intval)) {
+        return flow_graph->GetConstant(obj, representation());
+      }
+      if (is_truncating()) {
+        const int64_t result = Evaluator::TruncateTo(intval, representation());
+        return flow_graph->GetConstant(
+            Integer::ZoneHandle(flow_graph->zone(),
+                                Integer::NewCanonical(result)),
+            representation());
       }
     }
-
-    return flow_graph->GetConstant(c->value(), kUnboxedInt32);
-  }
-
-  return this;
-}
-
-Definition* UnboxInt64Instr::Canonicalize(FlowGraph* flow_graph) {
-  Definition* replacement = UnboxIntegerInstr::Canonicalize(flow_graph);
-  if (replacement != this) {
-    return replacement;
-  }
-
-  ConstantInstr* c = value()->definition()->AsConstant();
-  if (c != nullptr && c->value().IsInteger()) {
-    return flow_graph->GetConstant(c->value(), kUnboxedInt64);
   }
 
   return this;
diff --git a/runtime/vm/compiler/backend/il.h b/runtime/vm/compiler/backend/il.h
index a825904..3906ac3 100644
--- a/runtime/vm/compiler/backend/il.h
+++ b/runtime/vm/compiler/backend/il.h
@@ -8511,6 +8511,8 @@
 
   void mark_truncating() { is_truncating_ = true; }
 
+  virtual bool ComputeCanDeoptimize() const;
+
   virtual CompileType ComputeType() const;
 
   virtual bool AttributesEqual(const Instruction& other) const {
@@ -8521,6 +8523,8 @@
 
   virtual Definition* Canonicalize(FlowGraph* flow_graph);
 
+  virtual void InferRange(RangeAnalysis* analysis, Range* range);
+
   DECLARE_ABSTRACT_INSTRUCTION(UnboxInteger)
 
   PRINT_OPERANDS_TO_SUPPORT
@@ -8570,10 +8574,6 @@
     ASSERT(is_truncating());
   }
 
-  virtual bool ComputeCanDeoptimize() const;
-
-  virtual void InferRange(RangeAnalysis* analysis, Range* range);
-
   DECLARE_INSTRUCTION_NO_BACKEND(UnboxUint32)
 
   DECLARE_EMPTY_SERIALIZATION(UnboxUint32Instr, UnboxInteger32Instr)
@@ -8594,12 +8594,6 @@
                             deopt_id,
                             speculative_mode) {}
 
-  virtual bool ComputeCanDeoptimize() const;
-
-  virtual void InferRange(RangeAnalysis* analysis, Range* range);
-
-  virtual Definition* Canonicalize(FlowGraph* flow_graph);
-
   DECLARE_INSTRUCTION_NO_BACKEND(UnboxInt32)
 
   DECLARE_EMPTY_SERIALIZATION(UnboxInt32Instr, UnboxInteger32Instr)
@@ -8619,18 +8613,6 @@
                           deopt_id,
                           speculative_mode) {}
 
-  virtual void InferRange(RangeAnalysis* analysis, Range* range);
-
-  virtual Definition* Canonicalize(FlowGraph* flow_graph);
-
-  virtual bool ComputeCanDeoptimize() const {
-    if (SpeculativeModeOfInputs() == kNotSpeculative) {
-      return false;
-    }
-
-    return !value()->Type()->IsInt();
-  }
-
   DECLARE_INSTRUCTION_NO_BACKEND(UnboxInt64)
 
   DECLARE_EMPTY_SERIALIZATION(UnboxInt64Instr, UnboxIntegerInstr)
diff --git a/runtime/vm/compiler/backend/locations.cc b/runtime/vm/compiler/backend/locations.cc
index 77cd5d0..6c298e4 100644
--- a/runtime/vm/compiler/backend/locations.cc
+++ b/runtime/vm/compiler/backend/locations.cc
@@ -121,6 +121,12 @@
 }
 #undef REP_MAX_VALUE_CLAUSE
 
+bool RepresentationUtils::IsRepresentable(Representation rep, uint64_t value) {
+  const intptr_t bit_size = ValueSize(rep) * kBitsPerByte;
+  return IsUnsigned(rep) ? Utils::IsUint(bit_size, value)
+                         : Utils::IsInt(bit_size, value);
+}
+
 const char* Location::RepresentationToCString(Representation repr) {
   switch (repr) {
 #define REPR_CASE(Name, __, ___)                                               \
diff --git a/runtime/vm/compiler/backend/locations.h b/runtime/vm/compiler/backend/locations.h
index 2489b02..5d631fe 100644
--- a/runtime/vm/compiler/backend/locations.h
+++ b/runtime/vm/compiler/backend/locations.h
@@ -97,12 +97,16 @@
   static compiler::OperandSize OperandSize(Representation rep);
 
   // The minimum integral value that can be represented.
-  // Assumes that [rep] is a unboxed integer.
+  // Assumes that [rep] is an unboxed integer.
   static int64_t MinValue(Representation rep);
 
   // The maximum integral value that can be represented.
-  // Assumes that [rep] is a unboxed integer.
+  // Assumes that [rep] is an unboxed integer.
   static int64_t MaxValue(Representation rep);
+
+  // Whether the given value is representable in the given representation.
+  // Assumes that [rep] is an unboxed integer.
+  static bool IsRepresentable(Representation rep, uint64_t value);
 };
 
 // The representation for word-sized unboxed fields.
diff --git a/runtime/vm/compiler/backend/range_analysis.cc b/runtime/vm/compiler/backend/range_analysis.cc
index d57cc36..90c4bac 100644
--- a/runtime/vm/compiler/backend/range_analysis.cc
+++ b/runtime/vm/compiler/backend/range_analysis.cc
@@ -24,6 +24,35 @@
 // Quick access to the locally defined zone() method.
 #define Z (zone())
 
+#if defined(DEBUG)
+static void CheckRangeForRepresentation(const Assert& assert,
+                                        const Instruction* instr,
+                                        const Range* range,
+                                        Representation rep) {
+  const Range other = Range::Full(rep);
+  if (!RangeUtils::IsWithin(range, &other)) {
+    assert.Fail(
+        "During range analysis for:\n  %s\n"
+        "expected range containing only %s-representable values, but got %s",
+        instr->ToCString(), RepresentationToCString(rep),
+        Range::ToCString(range));
+  }
+}
+
+#define ASSERT_VALID_RANGE_FOR_REPRESENTATION(instr, range, representation)    \
+  do {                                                                         \
+    CheckRangeForRepresentation(dart::Assert(__FILE__, __LINE__), instr,       \
+                                range, representation);                        \
+  } while (false)
+#else
+#define ASSERT_VALID_RANGE_FOR_REPRESENTATION(instr, range, representation)    \
+  do {                                                                         \
+    USE(instr);                                                                \
+    USE(range);                                                                \
+    USE(representation);                                                       \
+  } while (false)
+#endif
+
 void RangeAnalysis::Analyze() {
   CollectValues();
   InsertConstraints();
@@ -2121,6 +2150,19 @@
   return OnlyGreaterThanOrEqualTo(min_int) && OnlyLessThanOrEqualTo(max_int);
 }
 
+bool Range::IsWithin(const Range* other) const {
+  auto const lower_bound = other->min().LowerBound();
+  auto const upper_bound = other->max().UpperBound();
+  if (lower_bound.IsNegativeInfinity()) {
+    if (upper_bound.IsPositiveInfinity()) return true;
+    return OnlyLessThanOrEqualTo(other->max().ConstantValue());
+  } else if (upper_bound.IsPositiveInfinity()) {
+    return OnlyGreaterThanOrEqualTo(other->min().ConstantValue());
+  } else {
+    return IsWithin(other->min().ConstantValue(), other->max().ConstantValue());
+  }
+}
+
 bool Range::Overlaps(int64_t min_int, int64_t max_int) const {
   RangeBoundary lower = min().LowerBound();
   RangeBoundary upper = max().UpperBound();
@@ -2601,6 +2643,12 @@
     // Only Smi and Mint supported.
     FATAL("Unsupported type in: %s", ToCString());
   }
+
+  // If the representation also gives us range information, then refine
+  // the range from the type by using the intersection of the two.
+  if (RepresentationUtils::IsUnboxedInteger(representation())) {
+    *range = Range::Full(representation()).Intersect(range);
+  }
 }
 
 static bool DependsOnSymbol(const RangeBoundary& a, Definition* symbol) {
@@ -3079,60 +3127,35 @@
 
 void BoxIntegerInstr::InferRange(RangeAnalysis* analysis, Range* range) {
   const Range* value_range = value()->definition()->range();
-  if (!Range::IsUnknown(value_range)) {
+  if (Range::IsUnknown(value_range)) {
+    *range = Range::Full(from_representation());
+  } else {
+    ASSERT_VALID_RANGE_FOR_REPRESENTATION(value()->definition(), value_range,
+                                          from_representation());
     *range = *value_range;
   }
 }
 
-void UnboxInt32Instr::InferRange(RangeAnalysis* analysis, Range* range) {
-  if (value()->Type()->ToCid() == kSmiCid) {
-    const Range* value_range = analysis->GetSmiRange(value());
-    if (!Range::IsUnknown(value_range)) {
-      *range = *value_range;
-    }
-  } else if (RangeAnalysis::IsIntegerDefinition(value()->definition())) {
-    const Range* value_range = analysis->GetIntRange(value());
-    if (!Range::IsUnknown(value_range)) {
-      *range = *value_range;
-    }
-  } else if (value()->Type()->ToCid() == kSmiCid) {
-    *range = Range::Full(RangeBoundary::kRangeBoundarySmi);
-  } else {
-    *range = Range::Full(RangeBoundary::kRangeBoundaryInt32);
-  }
-}
+void UnboxIntegerInstr::InferRange(RangeAnalysis* analysis, Range* range) {
+  auto* const value_range = value()->Type()->ToCid() == kSmiCid
+                                ? analysis->GetSmiRange(value())
+                                : value()->definition()->range();
+  const Range to_range = Range::Full(representation());
 
-void UnboxUint32Instr::InferRange(RangeAnalysis* analysis, Range* range) {
-  const Range* value_range = nullptr;
-
-  if (value()->Type()->ToCid() == kSmiCid) {
-    value_range = analysis->GetSmiRange(value());
-  } else if (RangeAnalysis::IsIntegerDefinition(value()->definition())) {
-    value_range = analysis->GetIntRange(value());
-  } else {
-    *range = Range(RangeBoundary::FromConstant(0),
-                   RangeBoundary::FromConstant(kMaxUint32));
-    return;
-  }
-
-  if (!Range::IsUnknown(value_range)) {
-    if (value_range->IsPositive()) {
-      *range = *value_range;
-    } else {
-      *range = Range(RangeBoundary::FromConstant(0),
-                     RangeBoundary::FromConstant(kMaxUint32));
-    }
-  }
-}
-
-void UnboxInt64Instr::InferRange(RangeAnalysis* analysis, Range* range) {
-  const Range* value_range = value()->definition()->range();
-  if (value_range != nullptr) {
+  if (Range::IsUnknown(value_range)) {
+    *range = to_range;
+  } else if (value_range->IsWithin(&to_range)) {
     *range = *value_range;
-  } else if (!value()->definition()->IsInt64Definition() &&
-             (value()->definition()->Type()->ToCid() != kSmiCid)) {
-    *range = Range::Full(RangeBoundary::kRangeBoundaryInt64);
+  } else if (is_truncating()) {
+    // If truncating, then in most cases any non-representable values means
+    // no assumption can be made about the truncated value.
+    *range = to_range;
+  } else {
+    // When not truncating, then unboxing deoptimizes if the value is outside
+    // the range representation.
+    *range = value_range->Intersect(&to_range);
   }
+  ASSERT_VALID_RANGE_FOR_REPRESENTATION(this, range, representation());
 }
 
 void IntConverterInstr::InferRange(RangeAnalysis* analysis, Range* range) {
@@ -3140,13 +3163,14 @@
   ASSERT(RepresentationUtils::IsUnboxedInteger(to()));
   ASSERT(from() == kUntagged || RepresentationUtils::IsUnboxedInteger(from()));
 
-  auto* const value_range = value()->definition()->range();
+  const Range* const value_range = value()->definition()->range();
+  const Range to_range = Range::Full(to());
 
   if (from() == kUntagged) {
     ASSERT(value_range == nullptr);  // Not an integer-valued definition.
-    *range = Range::Full(to());
+    *range = to_range;
   } else if (Range::IsUnknown(value_range)) {
-    *range = Range::Full(to());
+    *range = to_range;
   } else if (RepresentationUtils::ValueSize(to()) >
                  RepresentationUtils::ValueSize(from()) &&
              (!RepresentationUtils::IsUnsigned(to()) ||
@@ -3161,29 +3185,21 @@
     // are the same size) or a larger value is being truncated. That means
     // we need to determine whether or not the value range lies within the
     // range of numbers that have the same representation (modulo truncation).
-    const int64_t min_overlap =
-        Utils::Maximum(RepresentationUtils::MinValue(from()),
-                       RepresentationUtils::MinValue(to()));
-    const int64_t max_overlap =
-        Utils::Minimum(RepresentationUtils::MaxValue(from()),
-                       RepresentationUtils::MaxValue(to()));
-
-    if (value_range->IsWithin(min_overlap, max_overlap)) {
+    const Range common_range = Range::Full(from()).Intersect(&to_range);
+    if (value_range->IsWithin(&common_range)) {
       *range = *value_range;
     } else {
       // In most cases, if there are non-representable values, then no
       // assumptions can be made about the converted value.
-      *range = Range::Full(to());
+      *range = to_range;
     }
   } else {
     // The conversion deoptimizes if the value is outside the range represented
     // by to(), so we can just take the intersection.
-    const auto& to_range = Range::Full(to());
     *range = value_range->Intersect(&to_range);
   }
 
-  ASSERT(RangeUtils::IsWithin(range, RepresentationUtils::MinValue(to()),
-                              RepresentationUtils::MaxValue(to())));
+  ASSERT_VALID_RANGE_FOR_REPRESENTATION(this, range, to());
 }
 
 void AssertAssignableInstr::InferRange(RangeAnalysis* analysis, Range* range) {
diff --git a/runtime/vm/compiler/backend/range_analysis.h b/runtime/vm/compiler/backend/range_analysis.h
index e3260e8..f41997f 100644
--- a/runtime/vm/compiler/backend/range_analysis.h
+++ b/runtime/vm/compiler/backend/range_analysis.h
@@ -429,6 +429,9 @@
   bool IsWithin(int64_t min_int, int64_t max_int) const;
 
   // Inclusive.
+  bool IsWithin(const Range* other) const;
+
+  // Inclusive.
   bool Overlaps(int64_t min_int, int64_t max_int) const;
 
   bool IsUnsatisfiable() const;
@@ -560,10 +563,14 @@
     return !Range::IsUnknown(range) && range->Fits(size);
   }
 
-  static bool IsWithin(Range* range, int64_t min, int64_t max) {
+  static bool IsWithin(const Range* range, int64_t min, int64_t max) {
     return !Range::IsUnknown(range) && range->IsWithin(min, max);
   }
 
+  static bool IsWithin(const Range* range, const Range* other) {
+    return !Range::IsUnknown(range) && range->IsWithin(other);
+  }
+
   static bool IsPositive(Range* range) {
     return !Range::IsUnknown(range) && range->IsPositive();
   }
diff --git a/runtime/vm/compiler/frontend/kernel_to_il.cc b/runtime/vm/compiler/frontend/kernel_to_il.cc
index 17893dd..16c3edf 100644
--- a/runtime/vm/compiler/frontend/kernel_to_il.cc
+++ b/runtime/vm/compiler/frontend/kernel_to_il.cc
@@ -1384,8 +1384,8 @@
         LocalVariable* pointer = MakeTemporary();
         body += LoadLocal(pointer);
         body += LoadLocal(address);
-        body += UnboxTruncate(kUnboxedIntPtr);
-        body += ConvertUnboxedToUntagged(kUnboxedIntPtr);
+        body += UnboxTruncate(kUnboxedFfiIntPtr);
+        body += ConvertUnboxedToUntagged(kUnboxedFfiIntPtr);
         body += StoreNativeField(Slot::PointerBase_data(),
                                  InnerPointerAccess::kCannotBeInnerPointer,
                                  StoreFieldInstr::Kind::kInitializing);
@@ -1467,8 +1467,8 @@
       body += LoadLocal(MakeTemporary());  // Duplicate Pointer.
       body += LoadLocal(parsed_function_->RawParameterVariable(0));  // Address.
       body += CheckNullOptimized(String::ZoneHandle(Z, function.name()));
-      body += UnboxTruncate(kUnboxedIntPtr);
-      body += ConvertUnboxedToUntagged(kUnboxedIntPtr);
+      body += UnboxTruncate(kUnboxedFfiIntPtr);
+      body += ConvertUnboxedToUntagged(kUnboxedFfiIntPtr);
       body += StoreNativeField(Slot::PointerBase_data(),
                                InnerPointerAccess::kCannotBeInnerPointer,
                                StoreFieldInstr::Kind::kInitializing);
@@ -1800,11 +1800,12 @@
   body += LoadLocal(typed_data);
   body += LoadNativeField(Slot::PointerBase_data(),
                           InnerPointerAccess::kMayBeInnerPointer);
-  body += ConvertUntaggedToUnboxed(kUnboxedIntPtr);
+  body += ConvertUntaggedToUnboxed(kUnboxedFfiIntPtr);
   body += LoadLocal(offset_in_bytes);
-  body += UnboxTruncate(kUnboxedIntPtr);
-  body += BinaryIntegerOp(Token::kADD, kUnboxedIntPtr, /*is_truncating=*/true);
-  body += ConvertUnboxedToUntagged(kUnboxedIntPtr);
+  body += UnboxTruncate(kUnboxedFfiIntPtr);
+  body +=
+      BinaryIntegerOp(Token::kADD, kUnboxedFfiIntPtr, /*is_truncating=*/true);
+  body += ConvertUnboxedToUntagged(kUnboxedFfiIntPtr);
   body += StoreNativeField(Slot::PointerBase_data(),
                            InnerPointerAccess::kMayBeInnerPointer,
                            StoreFieldInstr::Kind::kInitializing);
@@ -1865,27 +1866,27 @@
   call_memmove += LoadLocal(arg_to);
   call_memmove += LoadNativeField(Slot::PointerBase_data(),
                                   InnerPointerAccess::kMayBeInnerPointer);
-  call_memmove += ConvertUntaggedToUnboxed(kUnboxedIntPtr);
+  call_memmove += ConvertUntaggedToUnboxed(kUnboxedFfiIntPtr);
   call_memmove += LoadLocal(arg_to_start);
   call_memmove += IntConstant(element_size);
   call_memmove += SmiBinaryOp(Token::kMUL, /*is_truncating=*/true);
-  call_memmove += UnboxTruncate(kUnboxedIntPtr);
+  call_memmove += UnboxTruncate(kUnboxedFfiIntPtr);
   call_memmove +=
-      BinaryIntegerOp(Token::kADD, kUnboxedIntPtr, /*is_truncating=*/true);
+      BinaryIntegerOp(Token::kADD, kUnboxedFfiIntPtr, /*is_truncating=*/true);
   call_memmove += LoadLocal(arg_from);
   call_memmove += LoadNativeField(Slot::PointerBase_data(),
                                   InnerPointerAccess::kMayBeInnerPointer);
-  call_memmove += ConvertUntaggedToUnboxed(kUnboxedIntPtr);
+  call_memmove += ConvertUntaggedToUnboxed(kUnboxedFfiIntPtr);
   call_memmove += LoadLocal(arg_from_start);
   call_memmove += IntConstant(element_size);
   call_memmove += SmiBinaryOp(Token::kMUL, /*is_truncating=*/true);
-  call_memmove += UnboxTruncate(kUnboxedIntPtr);
+  call_memmove += UnboxTruncate(kUnboxedFfiIntPtr);
   call_memmove +=
-      BinaryIntegerOp(Token::kADD, kUnboxedIntPtr, /*is_truncating=*/true);
+      BinaryIntegerOp(Token::kADD, kUnboxedFfiIntPtr, /*is_truncating=*/true);
   call_memmove += LoadLocal(arg_count);
   call_memmove += IntConstant(element_size);
   call_memmove += SmiBinaryOp(Token::kMUL, /*is_truncating=*/true);
-  call_memmove += UnboxTruncate(kUnboxedIntPtr);
+  call_memmove += UnboxTruncate(kUnboxedFfiIntPtr);
   call_memmove += LoadThread();
   call_memmove += LoadUntagged(
       compiler::target::Thread::OffsetFromThread(&kMemoryMoveRuntimeEntry));
@@ -4529,8 +4530,8 @@
   LocalVariable* pointer = MakeTemporary();
   code += LoadLocal(pointer);
   code += LoadLocal(address);
-  code += UnboxTruncate(kUnboxedIntPtr);
-  code += ConvertUnboxedToUntagged(kUnboxedIntPtr);
+  code += UnboxTruncate(kUnboxedFfiIntPtr);
+  code += ConvertUnboxedToUntagged(kUnboxedFfiIntPtr);
   code += StoreNativeField(Slot::PointerBase_data(),
                            InnerPointerAccess::kCannotBeInnerPointer,
                            StoreFieldInstr::Kind::kInitializing);
diff --git a/sdk/lib/_internal/wasm/lib/boxed_int.dart b/sdk/lib/_internal/wasm/lib/boxed_int.dart
index 60a0b9f..48448e1 100644
--- a/sdk/lib/_internal/wasm/lib/boxed_int.dart
+++ b/sdk/lib/_internal/wasm/lib/boxed_int.dart
@@ -3,7 +3,6 @@
 // BSD-style license that can be found in the LICENSE file.
 
 import 'dart:_internal';
-import 'dart:_string';
 
 @pragma("wasm:entry-point")
 final class _BoxedInt extends int {
@@ -290,7 +289,7 @@
     return this.toDouble().toStringAsPrecision(precision);
   }
 
-  String toRadixString(int radix) => _intToRadixString(value, radix);
+  external String toRadixString(int radix);
 
   // Returns pow(this, e) % m.
   int modPow(int e, int m) {
@@ -421,268 +420,7 @@
   external int get bitLength;
 
   @override
-  String toString() => _intToString(value);
-}
-
-const _digits = "0123456789abcdefghijklmnopqrstuvwxyz";
-
-String _intToRadixString(int value, int radix) {
-  if (radix < 2 || 36 < radix) {
-    throw new RangeError.range(radix, 2, 36, "radix");
-  }
-  if (radix & (radix - 1) == 0) {
-    return _toPow2String(value, radix);
-  }
-  if (radix == 10) return _intToString(value);
-  final bool isNegative = value < 0;
-  value = isNegative ? -value : value;
-  if (value < 0) {
-    // With int limited to 64 bits, the value
-    // MIN_INT64 = -0x8000000000000000 overflows at negation:
-    // -MIN_INT64 == MIN_INT64, so it requires special handling.
-    return _minInt64ToRadixString(value, radix);
-  }
-  var temp = <int>[];
-  do {
-    int digit = value % radix;
-    value ~/= radix;
-    temp.add(_digits.codeUnitAt(digit));
-  } while (value > 0);
-  if (isNegative) temp.add(0x2d); // '-'.
-
-  final string = OneByteString.withLength(temp.length);
-  for (int i = 0, j = temp.length; j > 0; i++) {
-    writeIntoOneByteString(string, i, temp[--j]);
-  }
-  return string;
-}
-
-String _toPow2String(int value, int radix) {
-  if (value == 0) return "0";
-  assert(radix & (radix - 1) == 0);
-  var negative = value < 0;
-  var bitsPerDigit = radix.bitLength - 1;
-  var length = 0;
-  if (negative) {
-    value = -value;
-    length = 1;
-    if (value < 0) {
-      // With int limited to 64 bits, the value
-      // MIN_INT64 = -0x8000000000000000 overflows at negation:
-      // -MIN_INT64 == MIN_INT64, so it requires special handling.
-      return _minInt64ToRadixString(value, radix);
-    }
-  }
-  // Integer division, rounding up, to find number of _digits.
-  length += (value.bitLength + bitsPerDigit - 1) ~/ bitsPerDigit;
-  final string = OneByteString.withLength(length);
-  writeIntoOneByteString(
-      string, 0, 0x2d); // '-'. Is overwritten if not negative.
-  var mask = radix - 1;
-  do {
-    writeIntoOneByteString(string, --length, _digits.codeUnitAt(value & mask));
-    value >>= bitsPerDigit;
-  } while (value > 0);
-  return string;
-}
-
-/// Converts negative value to radix string.
-/// This method is only used to handle corner case of
-/// MIN_INT64 = -0x8000000000000000.
-String _minInt64ToRadixString(int value, int radix) {
-  var temp = <int>[];
-  assert(value < 0);
-  do {
-    int digit = -unsafeCast<int>(value.remainder(radix));
-    value ~/= radix;
-    temp.add(_digits.codeUnitAt(digit));
-  } while (value != 0);
-  temp.add(0x2d); // '-'.
-
-  final string = OneByteString.withLength(temp.length);
-  for (int i = 0, j = temp.length; j > 0; i++) {
-    writeIntoOneByteString(string, i, temp[--j]);
-  }
-  return string;
-}
-
-/**
- * The digits of '00', '01', ... '99' as a single array.
- *
- * Get the digits of `n`, with `0 <= n < 100`, as
- * `_digitTable[n * 2]` and `_digitTable[n * 2 + 1]`.
- */
-const _digitTable = const [
-  0x30, 0x30, 0x30, 0x31, 0x30, 0x32, 0x30, 0x33, //
-  0x30, 0x34, 0x30, 0x35, 0x30, 0x36, 0x30, 0x37, //
-  0x30, 0x38, 0x30, 0x39, 0x31, 0x30, 0x31, 0x31, //
-  0x31, 0x32, 0x31, 0x33, 0x31, 0x34, 0x31, 0x35, //
-  0x31, 0x36, 0x31, 0x37, 0x31, 0x38, 0x31, 0x39, //
-  0x32, 0x30, 0x32, 0x31, 0x32, 0x32, 0x32, 0x33, //
-  0x32, 0x34, 0x32, 0x35, 0x32, 0x36, 0x32, 0x37, //
-  0x32, 0x38, 0x32, 0x39, 0x33, 0x30, 0x33, 0x31, //
-  0x33, 0x32, 0x33, 0x33, 0x33, 0x34, 0x33, 0x35, //
-  0x33, 0x36, 0x33, 0x37, 0x33, 0x38, 0x33, 0x39, //
-  0x34, 0x30, 0x34, 0x31, 0x34, 0x32, 0x34, 0x33, //
-  0x34, 0x34, 0x34, 0x35, 0x34, 0x36, 0x34, 0x37, //
-  0x34, 0x38, 0x34, 0x39, 0x35, 0x30, 0x35, 0x31, //
-  0x35, 0x32, 0x35, 0x33, 0x35, 0x34, 0x35, 0x35, //
-  0x35, 0x36, 0x35, 0x37, 0x35, 0x38, 0x35, 0x39, //
-  0x36, 0x30, 0x36, 0x31, 0x36, 0x32, 0x36, 0x33, //
-  0x36, 0x34, 0x36, 0x35, 0x36, 0x36, 0x36, 0x37, //
-  0x36, 0x38, 0x36, 0x39, 0x37, 0x30, 0x37, 0x31, //
-  0x37, 0x32, 0x37, 0x33, 0x37, 0x34, 0x37, 0x35, //
-  0x37, 0x36, 0x37, 0x37, 0x37, 0x38, 0x37, 0x39, //
-  0x38, 0x30, 0x38, 0x31, 0x38, 0x32, 0x38, 0x33, //
-  0x38, 0x34, 0x38, 0x35, 0x38, 0x36, 0x38, 0x37, //
-  0x38, 0x38, 0x38, 0x39, 0x39, 0x30, 0x39, 0x31, //
-  0x39, 0x32, 0x39, 0x33, 0x39, 0x34, 0x39, 0x35, //
-  0x39, 0x36, 0x39, 0x37, 0x39, 0x38, 0x39, 0x39, //
-];
-
-/**
- * Result of int.toString for -99, -98, ..., 98, 99.
- */
-const _smallLookupTable = const [
-  "-99", "-98", "-97", "-96", "-95", "-94", "-93", "-92", "-91", "-90", //
-  "-89", "-88", "-87", "-86", "-85", "-84", "-83", "-82", "-81", "-80", //
-  "-79", "-78", "-77", "-76", "-75", "-74", "-73", "-72", "-71", "-70", //
-  "-69", "-68", "-67", "-66", "-65", "-64", "-63", "-62", "-61", "-60", //
-  "-59", "-58", "-57", "-56", "-55", "-54", "-53", "-52", "-51", "-50", //
-  "-49", "-48", "-47", "-46", "-45", "-44", "-43", "-42", "-41", "-40", //
-  "-39", "-38", "-37", "-36", "-35", "-34", "-33", "-32", "-31", "-30", //
-  "-29", "-28", "-27", "-26", "-25", "-24", "-23", "-22", "-21", "-20", //
-  "-19", "-18", "-17", "-16", "-15", "-14", "-13", "-12", "-11", "-10", //
-  "-9", "-8", "-7", "-6", "-5", "-4", "-3", "-2", "-1", "0", //
-  "1", "2", "3", "4", "5", "6", "7", "8", "9", "10", //
-  "11", "12", "13", "14", "15", "16", "17", "18", "19", "20", //
-  "21", "22", "23", "24", "25", "26", "27", "28", "29", "30", //
-  "31", "32", "33", "34", "35", "36", "37", "38", "39", "40", //
-  "41", "42", "43", "44", "45", "46", "47", "48", "49", "50", //
-  "51", "52", "53", "54", "55", "56", "57", "58", "59", "60", //
-  "61", "62", "63", "64", "65", "66", "67", "68", "69", "70", //
-  "71", "72", "73", "74", "75", "76", "77", "78", "79", "80", //
-  "81", "82", "83", "84", "85", "86", "87", "88", "89", "90", //
-  "91", "92", "93", "94", "95", "96", "97", "98", "99" //
-];
-
-// Powers of 10 above 1000000 are indistinguishable by eye.
-const int _POW_10_7 = 10000000;
-const int _POW_10_8 = 100000000;
-const int _POW_10_9 = 1000000000;
-
-// Find the number of decimal digits in a positive smi.
-// Never called with numbers < 100. These are handled before calling.
-int _positiveBase10Length(int smi) {
-  // A positive smi has length <= 19 if 63-bit,  <=10 if 31-bit.
-  // Avoid comparing a 31-bit smi to a non-smi.
-  if (smi < 1000) return 3;
-  if (smi < 10000) return 4;
-  if (smi < _POW_10_7) {
-    if (smi < 100000) return 5;
-    if (smi < 1000000) return 6;
-    return 7;
-  }
-  if (smi < _POW_10_8) return 8;
-  if (smi < _POW_10_9) return 9;
-  smi = smi ~/ _POW_10_9;
-  // Handle numbers < 100 before calling recursively.
-  if (smi < 10) return 10;
-  if (smi < 100) return 11;
-  return 9 + _positiveBase10Length(smi);
-}
-
-String _intToString(int value) {
-  if (value < 100 && value > -100) {
-    // Issue(https://dartbug.com/39639): The analyzer incorrectly reports the
-    // result type as `num`.
-    return _smallLookupTable[value + 99];
-  }
-  if (value < 0) return _negativeToString(value);
-  // Inspired by Andrei Alexandrescu: "Three Optimization Tips for C++"
-  // Avoid expensive remainder operation by doing it on more than
-  // one digit at a time.
-  const int DIGIT_ZERO = 0x30;
-  int length = _positiveBase10Length(value);
-  final result = OneByteString.withLength(length);
-  int index = length - 1;
-  int smi = value;
-  do {
-    // Two digits at a time.
-    final int twoDigits = smi.remainder(100);
-    smi = smi ~/ 100;
-    int digitIndex = twoDigits * 2;
-    writeIntoOneByteString(result, index, _digitTable[digitIndex + 1]);
-    writeIntoOneByteString(result, index - 1, _digitTable[digitIndex]);
-    index -= 2;
-  } while (smi >= 100);
-  if (smi < 10) {
-    // Character code for '0'.
-    // Issue(https://dartbug.com/39639): The analyzer incorrectly reports the
-    // result type as `num`.
-    writeIntoOneByteString(result, index, DIGIT_ZERO + smi);
-  } else {
-    // No remainder for this case.
-    // Issue(https://dartbug.com/39639): The analyzer incorrectly reports the
-    // result type as `num`.
-    int digitIndex = smi * 2;
-    writeIntoOneByteString(result, index, _digitTable[digitIndex + 1]);
-    writeIntoOneByteString(result, index - 1, _digitTable[digitIndex]);
-  }
-  return result;
-}
-
-// Find the number of decimal digits in a negative smi.
-// Never called with numbers > -100. These are handled before calling.
-int _negativeBase10Length(int negSmi) {
-  // A negative smi has length <= 19 if 63-bit, <=10 if 31-bit.
-  // Avoid comparing a 31-bit smi to a non-smi.
-  if (negSmi > -1000) return 3;
-  if (negSmi > -10000) return 4;
-  if (negSmi > -_POW_10_7) {
-    if (negSmi > -100000) return 5;
-    if (negSmi > -1000000) return 6;
-    return 7;
-  }
-  if (negSmi > -_POW_10_8) return 8;
-  if (negSmi > -_POW_10_9) return 9;
-  negSmi = negSmi ~/ _POW_10_9;
-  // Handle numbers > -100 before calling recursively.
-  if (negSmi > -10) return 10;
-  if (negSmi > -100) return 11;
-  return 9 + _negativeBase10Length(negSmi);
-}
-
-// Convert a negative smi to a string.
-// Doesn't negate the smi to avoid negating the most negative smi, which
-// would become a non-smi.
-String _negativeToString(int negSmi) {
-  // Character code for '-'
-  const int MINUS_SIGN = 0x2d;
-  // Character code for '0'.
-  const int DIGIT_ZERO = 0x30;
-  // Number of digits, not including minus.
-  int digitCount = _negativeBase10Length(negSmi);
-  final result = OneByteString.withLength(digitCount + 1);
-  writeIntoOneByteString(result, 0, MINUS_SIGN); // '-'.
-  int index = digitCount;
-  do {
-    int twoDigits = unsafeCast<int>(negSmi.remainder(100));
-    negSmi = negSmi ~/ 100;
-    int digitIndex = -twoDigits * 2;
-    writeIntoOneByteString(result, index, _digitTable[digitIndex + 1]);
-    writeIntoOneByteString(result, index - 1, _digitTable[digitIndex]);
-    index -= 2;
-  } while (negSmi <= -100);
-  if (negSmi > -10) {
-    writeIntoOneByteString(result, index, DIGIT_ZERO - negSmi);
-  } else {
-    // No remainder necessary for this case.
-    int digitIndex = -negSmi * 2;
-    writeIntoOneByteString(result, index, _digitTable[digitIndex + 1]);
-    writeIntoOneByteString(result, index - 1, _digitTable[digitIndex]);
-  }
-  return result;
+  external String toString();
 }
 
 int _intHashCode(int value) {
diff --git a/sdk/lib/_internal/wasm/lib/boxed_int_to_string.dart b/sdk/lib/_internal/wasm/lib/boxed_int_to_string.dart
new file mode 100644
index 0000000..c7803ad
--- /dev/null
+++ b/sdk/lib/_internal/wasm/lib/boxed_int_to_string.dart
@@ -0,0 +1,276 @@
+// 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:_internal';
+import 'dart:_string';
+
+@patch
+class _BoxedInt {
+  @patch
+  String toRadixString(int radix) => _intToRadixString(value, radix);
+
+  @patch
+  String toString() => _intToString(value);
+}
+
+const _digits = "0123456789abcdefghijklmnopqrstuvwxyz";
+
+String _intToRadixString(int value, int radix) {
+  if (radix < 2 || 36 < radix) {
+    throw new RangeError.range(radix, 2, 36, "radix");
+  }
+  if (radix & (radix - 1) == 0) {
+    return _toPow2String(value, radix);
+  }
+  if (radix == 10) return _intToString(value);
+  final bool isNegative = value < 0;
+  value = isNegative ? -value : value;
+  if (value < 0) {
+    // With int limited to 64 bits, the value
+    // MIN_INT64 = -0x8000000000000000 overflows at negation:
+    // -MIN_INT64 == MIN_INT64, so it requires special handling.
+    return _minInt64ToRadixString(value, radix);
+  }
+  var temp = <int>[];
+  do {
+    int digit = value % radix;
+    value ~/= radix;
+    temp.add(_digits.codeUnitAt(digit));
+  } while (value > 0);
+  if (isNegative) temp.add(0x2d); // '-'.
+
+  final string = OneByteString.withLength(temp.length);
+  for (int i = 0, j = temp.length; j > 0; i++) {
+    writeIntoOneByteString(string, i, temp[--j]);
+  }
+  return string;
+}
+
+String _toPow2String(int value, int radix) {
+  if (value == 0) return "0";
+  assert(radix & (radix - 1) == 0);
+  var negative = value < 0;
+  var bitsPerDigit = radix.bitLength - 1;
+  var length = 0;
+  if (negative) {
+    value = -value;
+    length = 1;
+    if (value < 0) {
+      // With int limited to 64 bits, the value
+      // MIN_INT64 = -0x8000000000000000 overflows at negation:
+      // -MIN_INT64 == MIN_INT64, so it requires special handling.
+      return _minInt64ToRadixString(value, radix);
+    }
+  }
+  // Integer division, rounding up, to find number of _digits.
+  length += (value.bitLength + bitsPerDigit - 1) ~/ bitsPerDigit;
+  final string = OneByteString.withLength(length);
+  writeIntoOneByteString(
+      string, 0, 0x2d); // '-'. Is overwritten if not negative.
+  var mask = radix - 1;
+  do {
+    writeIntoOneByteString(string, --length, _digits.codeUnitAt(value & mask));
+    value >>= bitsPerDigit;
+  } while (value > 0);
+  return string;
+}
+
+/// Converts negative value to radix string.
+/// This method is only used to handle corner case of
+/// MIN_INT64 = -0x8000000000000000.
+String _minInt64ToRadixString(int value, int radix) {
+  var temp = <int>[];
+  assert(value < 0);
+  do {
+    int digit = -unsafeCast<int>(value.remainder(radix));
+    value ~/= radix;
+    temp.add(_digits.codeUnitAt(digit));
+  } while (value != 0);
+  temp.add(0x2d); // '-'.
+
+  final string = OneByteString.withLength(temp.length);
+  for (int i = 0, j = temp.length; j > 0; i++) {
+    writeIntoOneByteString(string, i, temp[--j]);
+  }
+  return string;
+}
+
+/**
+ * The digits of '00', '01', ... '99' as a single array.
+ *
+ * Get the digits of `n`, with `0 <= n < 100`, as
+ * `_digitTable[n * 2]` and `_digitTable[n * 2 + 1]`.
+ */
+const _digitTable = const [
+  0x30, 0x30, 0x30, 0x31, 0x30, 0x32, 0x30, 0x33, //
+  0x30, 0x34, 0x30, 0x35, 0x30, 0x36, 0x30, 0x37, //
+  0x30, 0x38, 0x30, 0x39, 0x31, 0x30, 0x31, 0x31, //
+  0x31, 0x32, 0x31, 0x33, 0x31, 0x34, 0x31, 0x35, //
+  0x31, 0x36, 0x31, 0x37, 0x31, 0x38, 0x31, 0x39, //
+  0x32, 0x30, 0x32, 0x31, 0x32, 0x32, 0x32, 0x33, //
+  0x32, 0x34, 0x32, 0x35, 0x32, 0x36, 0x32, 0x37, //
+  0x32, 0x38, 0x32, 0x39, 0x33, 0x30, 0x33, 0x31, //
+  0x33, 0x32, 0x33, 0x33, 0x33, 0x34, 0x33, 0x35, //
+  0x33, 0x36, 0x33, 0x37, 0x33, 0x38, 0x33, 0x39, //
+  0x34, 0x30, 0x34, 0x31, 0x34, 0x32, 0x34, 0x33, //
+  0x34, 0x34, 0x34, 0x35, 0x34, 0x36, 0x34, 0x37, //
+  0x34, 0x38, 0x34, 0x39, 0x35, 0x30, 0x35, 0x31, //
+  0x35, 0x32, 0x35, 0x33, 0x35, 0x34, 0x35, 0x35, //
+  0x35, 0x36, 0x35, 0x37, 0x35, 0x38, 0x35, 0x39, //
+  0x36, 0x30, 0x36, 0x31, 0x36, 0x32, 0x36, 0x33, //
+  0x36, 0x34, 0x36, 0x35, 0x36, 0x36, 0x36, 0x37, //
+  0x36, 0x38, 0x36, 0x39, 0x37, 0x30, 0x37, 0x31, //
+  0x37, 0x32, 0x37, 0x33, 0x37, 0x34, 0x37, 0x35, //
+  0x37, 0x36, 0x37, 0x37, 0x37, 0x38, 0x37, 0x39, //
+  0x38, 0x30, 0x38, 0x31, 0x38, 0x32, 0x38, 0x33, //
+  0x38, 0x34, 0x38, 0x35, 0x38, 0x36, 0x38, 0x37, //
+  0x38, 0x38, 0x38, 0x39, 0x39, 0x30, 0x39, 0x31, //
+  0x39, 0x32, 0x39, 0x33, 0x39, 0x34, 0x39, 0x35, //
+  0x39, 0x36, 0x39, 0x37, 0x39, 0x38, 0x39, 0x39, //
+];
+
+/**
+ * Result of int.toString for -99, -98, ..., 98, 99.
+ */
+const _smallLookupTable = const [
+  "-99", "-98", "-97", "-96", "-95", "-94", "-93", "-92", "-91", "-90", //
+  "-89", "-88", "-87", "-86", "-85", "-84", "-83", "-82", "-81", "-80", //
+  "-79", "-78", "-77", "-76", "-75", "-74", "-73", "-72", "-71", "-70", //
+  "-69", "-68", "-67", "-66", "-65", "-64", "-63", "-62", "-61", "-60", //
+  "-59", "-58", "-57", "-56", "-55", "-54", "-53", "-52", "-51", "-50", //
+  "-49", "-48", "-47", "-46", "-45", "-44", "-43", "-42", "-41", "-40", //
+  "-39", "-38", "-37", "-36", "-35", "-34", "-33", "-32", "-31", "-30", //
+  "-29", "-28", "-27", "-26", "-25", "-24", "-23", "-22", "-21", "-20", //
+  "-19", "-18", "-17", "-16", "-15", "-14", "-13", "-12", "-11", "-10", //
+  "-9", "-8", "-7", "-6", "-5", "-4", "-3", "-2", "-1", "0", //
+  "1", "2", "3", "4", "5", "6", "7", "8", "9", "10", //
+  "11", "12", "13", "14", "15", "16", "17", "18", "19", "20", //
+  "21", "22", "23", "24", "25", "26", "27", "28", "29", "30", //
+  "31", "32", "33", "34", "35", "36", "37", "38", "39", "40", //
+  "41", "42", "43", "44", "45", "46", "47", "48", "49", "50", //
+  "51", "52", "53", "54", "55", "56", "57", "58", "59", "60", //
+  "61", "62", "63", "64", "65", "66", "67", "68", "69", "70", //
+  "71", "72", "73", "74", "75", "76", "77", "78", "79", "80", //
+  "81", "82", "83", "84", "85", "86", "87", "88", "89", "90", //
+  "91", "92", "93", "94", "95", "96", "97", "98", "99" //
+];
+
+// Powers of 10 above 1000000 are indistinguishable by eye.
+const int _POW_10_7 = 10000000;
+const int _POW_10_8 = 100000000;
+const int _POW_10_9 = 1000000000;
+
+// Find the number of decimal digits in a positive smi.
+// Never called with numbers < 100. These are handled before calling.
+int _positiveBase10Length(int smi) {
+  // A positive smi has length <= 19 if 63-bit,  <=10 if 31-bit.
+  // Avoid comparing a 31-bit smi to a non-smi.
+  if (smi < 1000) return 3;
+  if (smi < 10000) return 4;
+  if (smi < _POW_10_7) {
+    if (smi < 100000) return 5;
+    if (smi < 1000000) return 6;
+    return 7;
+  }
+  if (smi < _POW_10_8) return 8;
+  if (smi < _POW_10_9) return 9;
+  smi = smi ~/ _POW_10_9;
+  // Handle numbers < 100 before calling recursively.
+  if (smi < 10) return 10;
+  if (smi < 100) return 11;
+  return 9 + _positiveBase10Length(smi);
+}
+
+String _intToString(int value) {
+  if (value < 100 && value > -100) {
+    // Issue(https://dartbug.com/39639): The analyzer incorrectly reports the
+    // result type as `num`.
+    return _smallLookupTable[value + 99];
+  }
+  if (value < 0) return _negativeToString(value);
+  // Inspired by Andrei Alexandrescu: "Three Optimization Tips for C++"
+  // Avoid expensive remainder operation by doing it on more than
+  // one digit at a time.
+  const int DIGIT_ZERO = 0x30;
+  int length = _positiveBase10Length(value);
+  final result = OneByteString.withLength(length);
+  int index = length - 1;
+  int smi = value;
+  do {
+    // Two digits at a time.
+    final int twoDigits = smi.remainder(100);
+    smi = smi ~/ 100;
+    int digitIndex = twoDigits * 2;
+    writeIntoOneByteString(result, index, _digitTable[digitIndex + 1]);
+    writeIntoOneByteString(result, index - 1, _digitTable[digitIndex]);
+    index -= 2;
+  } while (smi >= 100);
+  if (smi < 10) {
+    // Character code for '0'.
+    // Issue(https://dartbug.com/39639): The analyzer incorrectly reports the
+    // result type as `num`.
+    writeIntoOneByteString(result, index, DIGIT_ZERO + smi);
+  } else {
+    // No remainder for this case.
+    // Issue(https://dartbug.com/39639): The analyzer incorrectly reports the
+    // result type as `num`.
+    int digitIndex = smi * 2;
+    writeIntoOneByteString(result, index, _digitTable[digitIndex + 1]);
+    writeIntoOneByteString(result, index - 1, _digitTable[digitIndex]);
+  }
+  return result;
+}
+
+// Find the number of decimal digits in a negative smi.
+// Never called with numbers > -100. These are handled before calling.
+int _negativeBase10Length(int negSmi) {
+  // A negative smi has length <= 19 if 63-bit, <=10 if 31-bit.
+  // Avoid comparing a 31-bit smi to a non-smi.
+  if (negSmi > -1000) return 3;
+  if (negSmi > -10000) return 4;
+  if (negSmi > -_POW_10_7) {
+    if (negSmi > -100000) return 5;
+    if (negSmi > -1000000) return 6;
+    return 7;
+  }
+  if (negSmi > -_POW_10_8) return 8;
+  if (negSmi > -_POW_10_9) return 9;
+  negSmi = negSmi ~/ _POW_10_9;
+  // Handle numbers > -100 before calling recursively.
+  if (negSmi > -10) return 10;
+  if (negSmi > -100) return 11;
+  return 9 + _negativeBase10Length(negSmi);
+}
+
+// Convert a negative smi to a string.
+// Doesn't negate the smi to avoid negating the most negative smi, which
+// would become a non-smi.
+String _negativeToString(int negSmi) {
+  // Character code for '-'
+  const int MINUS_SIGN = 0x2d;
+  // Character code for '0'.
+  const int DIGIT_ZERO = 0x30;
+  // Number of digits, not including minus.
+  int digitCount = _negativeBase10Length(negSmi);
+  final result = OneByteString.withLength(digitCount + 1);
+  writeIntoOneByteString(result, 0, MINUS_SIGN); // '-'.
+  int index = digitCount;
+  do {
+    int twoDigits = unsafeCast<int>(negSmi.remainder(100));
+    negSmi = negSmi ~/ 100;
+    int digitIndex = -twoDigits * 2;
+    writeIntoOneByteString(result, index, _digitTable[digitIndex + 1]);
+    writeIntoOneByteString(result, index - 1, _digitTable[digitIndex]);
+    index -= 2;
+  } while (negSmi <= -100);
+  if (negSmi > -10) {
+    writeIntoOneByteString(result, index, DIGIT_ZERO - negSmi);
+  } else {
+    // No remainder necessary for this case.
+    int digitIndex = -negSmi * 2;
+    writeIntoOneByteString(result, index, _digitTable[digitIndex + 1]);
+    writeIntoOneByteString(result, index - 1, _digitTable[digitIndex]);
+  }
+  return result;
+}
diff --git a/sdk/lib/_internal/wasm/lib/int_common_patch.dart b/sdk/lib/_internal/wasm/lib/int_common_patch.dart
new file mode 100644
index 0000000..506335d
--- /dev/null
+++ b/sdk/lib/_internal/wasm/lib/int_common_patch.dart
@@ -0,0 +1,30 @@
+// 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:_internal' show patch;
+
+@patch
+class int {
+  @patch
+  external const factory int.fromEnvironment(String name,
+      {int defaultValue = 0});
+
+  /// Wasm i64.div_s instruction.
+  external int _div_s(int divisor);
+
+  /// Wasm i64.le_u instruction.
+  external bool _le_u(int other);
+
+  /// Wasm i64.lt_u instruction.
+  external bool _lt_u(int other);
+
+  /// Wasm i64.shr_s instruction.
+  external int _shr_s(int shift);
+
+  /// Wasm i64.shr_u instruction.
+  external int _shr_u(int shift);
+
+  /// Wasm i64.shl instruction.
+  external int _shl(int shift);
+}
diff --git a/sdk/lib/_internal/wasm/lib/int_patch.dart b/sdk/lib/_internal/wasm/lib/int_patch.dart
index 3d669b7..e84d8f5 100644
--- a/sdk/lib/_internal/wasm/lib/int_patch.dart
+++ b/sdk/lib/_internal/wasm/lib/int_patch.dart
@@ -9,10 +9,6 @@
 
 @patch
 class int {
-  @patch
-  external const factory int.fromEnvironment(String name,
-      {int defaultValue = 0});
-
   static int? _tryParseSmi(String str, int first, int last) {
     assert(first <= last);
     var ix = first;
@@ -298,22 +294,4 @@
         -unsafeCast<int>(_minInt64.remainder(multiplier));
     return _int64OverflowLimits[tableIndex];
   }
-
-  /// Wasm i64.div_s instruction.
-  external int _div_s(int divisor);
-
-  /// Wasm i64.le_u instruction.
-  external bool _le_u(int other);
-
-  /// Wasm i64.lt_u instruction.
-  external bool _lt_u(int other);
-
-  /// Wasm i64.shr_s instruction.
-  external int _shr_s(int shift);
-
-  /// Wasm i64.shr_u instruction.
-  external int _shr_u(int shift);
-
-  /// Wasm i64.shl instruction.
-  external int _shl(int shift);
 }
diff --git a/sdk/lib/_internal/wasm/lib/internal_patch.dart b/sdk/lib/_internal/wasm/lib/internal_patch.dart
index 00db9f5..3ea31d8 100644
--- a/sdk/lib/_internal/wasm/lib/internal_patch.dart
+++ b/sdk/lib/_internal/wasm/lib/internal_patch.dart
@@ -3,7 +3,6 @@
 // BSD-style license that can be found in the LICENSE file.
 
 import "dart:_js_helper" show JS;
-import "dart:_string";
 
 part "class_id.dart";
 part "deferred.dart";
@@ -14,38 +13,6 @@
 @patch
 bool typeAcceptsNull<T>() => null is T;
 
-// String accessors used to perform Dart<->JS string conversion
-
-@pragma("wasm:export", "\$stringLength")
-double _stringLength(String string) {
-  return string.length.toDouble();
-}
-
-@pragma("wasm:export", "\$stringRead")
-double _stringRead(String string, double index) {
-  return string.codeUnitAt(index.toInt()).toDouble();
-}
-
-@pragma("wasm:export", "\$stringAllocate1")
-OneByteString _stringAllocate1(double length) {
-  return OneByteString.withLength(length.toInt());
-}
-
-@pragma("wasm:export", "\$stringWrite1")
-void _stringWrite1(OneByteString string, double index, double codePoint) {
-  writeIntoOneByteString(string, index.toInt(), codePoint.toInt());
-}
-
-@pragma("wasm:export", "\$stringAllocate2")
-TwoByteString _stringAllocate2(double length) {
-  return TwoByteString.withLength(length.toInt());
-}
-
-@pragma("wasm:export", "\$stringWrite2")
-void _stringWrite2(TwoByteString string, double index, double codePoint) {
-  writeIntoTwoByteString(string, index.toInt(), codePoint.toInt());
-}
-
 const bool has63BitSmis = false;
 
 class Lists {
diff --git a/sdk/lib/_internal/wasm/lib/js_array.dart b/sdk/lib/_internal/wasm/lib/js_array.dart
index f8865eb..382e59d 100644
--- a/sdk/lib/_internal/wasm/lib/js_array.dart
+++ b/sdk/lib/_internal/wasm/lib/js_array.dart
@@ -11,6 +11,9 @@
 
   JSArrayImpl(this._ref);
 
+  factory JSArrayImpl.fromLength(int length) =>
+      JSArrayImpl(js.newArrayFromLengthRaw(length));
+
   static JSArrayImpl? box(WasmExternRef? ref) =>
       js.isDartNull(ref) ? null : JSArrayImpl(ref);
 
diff --git a/sdk/lib/_internal/wasm/lib/js_helper.dart b/sdk/lib/_internal/wasm/lib/js_helper.dart
index 5cfddbb..116efcd 100644
--- a/sdk/lib/_internal/wasm/lib/js_helper.dart
+++ b/sdk/lib/_internal/wasm/lib/js_helper.dart
@@ -287,6 +287,9 @@
 
 WasmExternRef? newArrayRaw() => JS<WasmExternRef?>('() => []');
 
+WasmExternRef? newArrayFromLengthRaw(int length) =>
+    JS<WasmExternRef?>('l => new Array(l)', length.toDouble());
+
 WasmExternRef? globalThisRaw() => JS<WasmExternRef?>('() => globalThis');
 
 WasmExternRef? callConstructorVarArgsRaw(
diff --git a/sdk/lib/_internal/wasm/lib/js_string.dart b/sdk/lib/_internal/wasm/lib/js_string.dart
index 3d0c4f7..59834a8 100644
--- a/sdk/lib/_internal/wasm/lib/js_string.dart
+++ b/sdk/lib/_internal/wasm/lib/js_string.dart
@@ -21,6 +21,20 @@
 
   WasmExternRef? get toExternRef => _ref;
 
+  @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 o = values[i];
+      final s = o.toString();
+      final jsString =
+          s is JSStringImpl ? js.JSValue.boxT<JSAny?>(s.toExternRef) : s.toJS;
+      array[i] = jsString;
+    }
+    return JSStringImpl(
+        js.JS<WasmExternRef?>("a => a.join('')", array.toExternRef));
+  }
+
   @override
   int codeUnitAt(int index) {
     RangeError.checkValueInInterval(index, 0, length - 1);
@@ -635,4 +649,33 @@
 
   @override
   String toString() => js.stringify(toExternRef);
+
+  int firstNonWhitespace() {
+    final len = this.length;
+    int first = 0;
+    for (; first < len; first++) {
+      if (!_isWhitespace(this.codeUnitAt(first))) {
+        break;
+      }
+    }
+    return first;
+  }
+
+  int lastNonWhitespace() {
+    int last = this.length - 1;
+    for (; last >= 0; last--) {
+      if (!_isWhitespace(this.codeUnitAt(last))) {
+        break;
+      }
+    }
+    return last;
+  }
 }
+
+@pragma("wasm:export", "\$jsStringToJSStringImpl")
+JSStringImpl _jsStringToJSStringImpl(WasmExternRef? string) =>
+    JSStringImpl(string);
+
+@pragma("wasm:export", "\$jsStringFromJSStringImpl")
+WasmExternRef? _jsStringFromJSStringImpl(JSStringImpl string) =>
+    string.toExternRef;
diff --git a/sdk/lib/_internal/wasm/lib/string.dart b/sdk/lib/_internal/wasm/lib/string.dart
index b67f7b5..7d3aa1c 100644
--- a/sdk/lib/_internal/wasm/lib/string.dart
+++ b/sdk/lib/_internal/wasm/lib/string.dart
@@ -1381,3 +1381,35 @@
     return offset + length;
   }
 }
+
+// String accessors used to perform Dart<->JS string conversion
+
+@pragma("wasm:export", "\$stringLength")
+double _stringLength(String string) {
+  return string.length.toDouble();
+}
+
+@pragma("wasm:export", "\$stringRead")
+double _stringRead(String string, double index) {
+  return string.codeUnitAt(index.toInt()).toDouble();
+}
+
+@pragma("wasm:export", "\$stringAllocate1")
+OneByteString _stringAllocate1(double length) {
+  return OneByteString.withLength(length.toInt());
+}
+
+@pragma("wasm:export", "\$stringWrite1")
+void _stringWrite1(OneByteString string, double index, double codePoint) {
+  writeIntoOneByteString(string, index.toInt(), codePoint.toInt());
+}
+
+@pragma("wasm:export", "\$stringAllocate2")
+TwoByteString _stringAllocate2(double length) {
+  return TwoByteString.withLength(length.toInt());
+}
+
+@pragma("wasm:export", "\$stringWrite2")
+void _stringWrite2(TwoByteString string, double index, double codePoint) {
+  writeIntoTwoByteString(string, index.toInt(), codePoint.toInt());
+}
diff --git a/sdk/lib/_internal/wasm_js_compatibility/lib/boxed_int_to_string.dart b/sdk/lib/_internal/wasm_js_compatibility/lib/boxed_int_to_string.dart
new file mode 100644
index 0000000..288ee32
--- /dev/null
+++ b/sdk/lib/_internal/wasm_js_compatibility/lib/boxed_int_to_string.dart
@@ -0,0 +1,26 @@
+// 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:_internal';
+import 'dart:_js_helper';
+import 'dart:_js_types';
+import 'dart:_wasm';
+
+@patch
+class _BoxedInt {
+  @patch
+  String toRadixString(int radix) {
+    // We could also catch the `_JavaScriptError` here and convert it to
+    // `RangeError`, but I'm not sure if that would be faster.
+    if (radix < 2 || 36 < radix) {
+      throw RangeError.range(radix, 2, 36, "radix");
+    }
+    return JSStringImpl(JS<WasmExternRef?>(
+        '(n, r) => n.toString(r)', toDouble().toExternRef, radix.toDouble()));
+  }
+
+  @patch
+  String toString() => JSStringImpl(
+      JS<WasmExternRef?>('(n) => n.toString()', toDouble().toExternRef));
+}
diff --git a/sdk/lib/_internal/wasm_js_compatibility/lib/convert_patch.dart b/sdk/lib/_internal/wasm_js_compatibility/lib/convert_patch.dart
index 3c62389..cef1d72 100644
--- a/sdk/lib/_internal/wasm_js_compatibility/lib/convert_patch.dart
+++ b/sdk/lib/_internal/wasm_js_compatibility/lib/convert_patch.dart
@@ -1,20 +1,14 @@
-// Copyright (c) 2023, the Dart project authors.  Please see the AUTHORS file
+// Copyright (c) 2022, 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.
 
-/// Note: the VM concatenates all patch files into a single patch file. This
-/// file is the first patch in "dart:convert" which contains all the imports
-/// used by patches of that library. We plan to change this when we have a
-/// shared front end and simply use parts.
+import "dart:_internal" show ClassID, patch, POWERS_OF_TEN, unsafeCast;
+import "dart:_js_types";
+import 'dart:_wasm';
+import 'dart:_js_helper' as js;
 
-import "dart:_internal" show patch, POWERS_OF_TEN, unsafeCast;
-import "dart:_string";
 import "dart:typed_data" show Uint8List, Uint16List;
 
-/// This patch library has no additional parts.
-
-// JSON conversion.
-
 @patch
 dynamic _parseJson(
     String source, Object? Function(Object? key, Object? value)? reviver) {
@@ -29,43 +23,100 @@
 
 @patch
 class Utf8Decoder {
+  // Always fall back to the Dart implementation for strings shorter than this
+  // threshold, as there is a large, constant overhead for using TextDecoder.
+  // TODO(omersa): This is copied from dart2js runtime, make sure the value is
+  // right for dart2wasm.
+  static const int _shortInputThreshold = 15;
+
   @patch
   Converter<List<int>, T> fuse<T>(Converter<String, T> next) {
-    if (next is JsonDecoder) {
-      return new _JsonUtf8Decoder(
-              (next as JsonDecoder)._reviver, this._allowMalformed)
-          as dynamic/*=Converter<List<int>, T>*/;
-    }
-    // TODO(lrn): Recognize a fused decoder where the next step is JsonDecoder.
-    return super.fuse<T>(next);
+    return super.fuse(next);
   }
 
   // Allow intercepting of UTF-8 decoding when built-in lists are passed.
   @patch
   static String? _convertIntercepted(
       bool allowMalformed, List<int> codeUnits, int start, int? end) {
+    if (codeUnits is JSUint8ArrayImpl) {
+      final JSUint8ArrayImpl jsCodeUnits = codeUnits;
+      end ??= jsCodeUnits.length;
+      if (end - start < _shortInputThreshold) {
+        return null;
+      }
+      return _convertInterceptedUint8List(
+          allowMalformed, jsCodeUnits, start, end);
+    }
     return null; // This call was not intercepted.
   }
-}
 
-class _JsonUtf8Decoder extends Converter<List<int>, Object?> {
-  final Object? Function(Object? key, Object? value)? _reviver;
-  final bool _allowMalformed;
+  static String? _convertInterceptedUint8List(
+      bool allowMalformed, JSUint8ArrayImpl codeUnits, int start, int end) {
+    // TODO(omersa): There's a bug somewhere when compiling lazy statics that
+    // return `WasmExternRef`?
+    // final WasmExternRef? decoder = allowMalformed ? _decoderNonfatal : _decoder;
+    // if (decoder == WasmExternRef.nullRef) {
+    //   return null;
+    // }
+    final WasmExternRef? decoder;
+    try {
+      decoder = allowMalformed
+          ? js.JS<WasmExternRef?>(
+              '() => new TextDecoder("utf-8", {fatal: false})')
+          : js.JS<WasmExternRef?>(
+              '() => new TextDecoder("utf-8", {fatal: true})');
+    } catch (e) {
+      return null;
+    }
 
-  _JsonUtf8Decoder(this._reviver, this._allowMalformed);
-
-  Object? convert(List<int> input) {
-    var parser = _JsonUtf8DecoderSink._createParser(_reviver, _allowMalformed);
-    parser.chunk = input;
-    parser.chunkEnd = input.length;
-    parser.parse(0);
-    parser.close();
-    return parser.result;
+    if (0 == start && end == codeUnits.length) {
+      return _useTextDecoder(decoder, codeUnits.toExternRef);
+    }
+    final length = codeUnits.length;
+    end = RangeError.checkValidRange(start, end, length);
+    return _useTextDecoder(
+        decoder,
+        js.JS<WasmExternRef?>(
+            '(codeUnits, start, end) => codeUnits.subarray(start, end)',
+            codeUnits.toExternRef,
+            start.toDouble,
+            end.toDouble));
   }
 
-  ByteConversionSink startChunkedConversion(Sink<Object?> sink) {
-    return new _JsonUtf8DecoderSink(_reviver, sink, _allowMalformed);
+  static String? _useTextDecoder(
+      WasmExternRef? decoder, WasmExternRef? codeUnits) {
+    // If the input is malformed, catch the exception and return `null` to fall
+    // back on unintercepted decoder. The fallback will either succeed in
+    // decoding, or report the problem better than TextDecoder.
+    try {
+      return JSStringImpl(js.JS<WasmExternRef?>(
+          '(decoder, codeUnits) => decoder.decode(codeUnits)',
+          decoder,
+          codeUnits));
+    } catch (e) {}
+    return null;
   }
+
+  // TODO(omersa): These values seem to be miscompiled at the use sites, see
+  // above.
+  //
+  // // TextDecoder is not defined on some browsers and on the stand-alone d8 and
+  // // jsshell engines. Use a lazy initializer to do feature detection once.
+  // static final WasmExternRef? _decoder = () {
+  //   try {
+  //     return js
+  //         .JS<WasmExternRef?>('() => new TextDecoder("utf-8", {fatal: true})');
+  //   } catch (e) {}
+  //   return null;
+  // }();
+
+  // static final WasmExternRef? _decoderNonfatal = () {
+  //   try {
+  //     return js
+  //         .JS<WasmExternRef?>('() => new TextDecoder("utf-8", {fatal: false})');
+  //   } catch (e) {}
+  //   return null;
+  // }();
 }
 
 //// Implementation ///////////////////////////////////////////////////////////
@@ -1425,50 +1476,30 @@
 class JsonDecoder {
   @patch
   StringConversionSink startChunkedConversion(Sink<Object?> sink) {
-    return new _JsonStringDecoderSink(this._reviver, sink);
+    // return _JsonStringDecoderSink(this._reviver, sink);
+    return _JsonDecoderSink(_reviver, sink);
   }
 }
 
-/**
- * Implements the chunked conversion from a JSON string to its corresponding
- * object.
- *
- * The sink only creates one object, but its input can be chunked.
- */
-class _JsonStringDecoderSink extends StringConversionSinkBase {
-  _JsonStringParser _parser;
+/// Implements the chunked conversion from a JSON string to its corresponding
+/// object.
+///
+/// The sink only creates one object, but its input can be chunked.
+// TODO(floitsch): don't accumulate everything before starting to decode.
+class _JsonDecoderSink extends _StringSinkConversionSink<StringBuffer> {
   final Object? Function(Object? key, Object? value)? _reviver;
   final Sink<Object?> _sink;
 
-  _JsonStringDecoderSink(this._reviver, this._sink)
-      : _parser = _createParser(_reviver);
-
-  static _JsonStringParser _createParser(
-      Object? Function(Object? key, Object? value)? reviver) {
-    return new _JsonStringParser(new _JsonListener(reviver));
-  }
-
-  void addSlice(String chunk, int start, int end, bool isLast) {
-    _parser.chunk = chunk;
-    _parser.chunkEnd = end;
-    _parser.parse(start);
-    if (isLast) _parser.close();
-  }
-
-  void add(String chunk) {
-    addSlice(chunk, 0, chunk.length, false);
-  }
+  _JsonDecoderSink(this._reviver, this._sink) : super(StringBuffer(''));
 
   void close() {
-    _parser.close();
-    var decoded = _parser.result;
+    super.close();
+    String accumulated = _stringSink.toString();
+    _stringSink.clear();
+    Object? decoded = _parseJson(accumulated, _reviver);
     _sink.add(decoded);
     _sink.close();
   }
-
-  ByteConversionSink asUtf8Sink(bool allowMalformed) {
-    return new _JsonUtf8DecoderSink(_reviver, _sink, allowMalformed);
-  }
 }
 
 /**
@@ -1477,13 +1508,13 @@
 class _JsonUtf8Parser extends _ChunkedJsonParser<List<int>> {
   static final Uint8List emptyChunk = Uint8List(0);
 
-  final _Utf8Decoder decoder;
+  final bool allowMalformed;
+
   List<int> chunk = emptyChunk;
   int chunkEnd = 0;
 
-  _JsonUtf8Parser(_JsonListener listener, bool allowMalformed)
-      : decoder = new _Utf8Decoder(allowMalformed),
-        super(listener) {
+  _JsonUtf8Parser(_JsonListener listener, this.allowMalformed)
+      : super(listener) {
     // Starts out checking for an optional BOM (KWD_BOM, count = 0).
     partialState =
         _ChunkedJsonParser.PARTIAL_KEYWORD | _ChunkedJsonParser.KWD_BOM;
@@ -1503,24 +1534,22 @@
   }
 
   void beginString() {
-    decoder.reset();
     this.buffer = new StringBuffer();
   }
 
   void addSliceToString(int start, int end) {
     final StringBuffer buffer = this.buffer;
-    buffer.write(decoder.convertChunked(chunk, start, end));
+    buffer
+        .write(_Utf8Decoder(allowMalformed).convertChunked(chunk, start, end));
   }
 
   void addCharToString(int charCode) {
     final StringBuffer buffer = this.buffer;
-    decoder.flush(buffer);
     buffer.writeCharCode(charCode);
   }
 
   String endString() {
     final StringBuffer buffer = this.buffer;
-    decoder.flush(buffer);
     this.buffer = null;
     return buffer.toString();
   }
@@ -1536,476 +1565,21 @@
   }
 }
 
-@pragma("wasm:prefer-inline")
-double _parseDouble(String source, int start, int end) =>
-    double.parse(source.substring(start, end));
-
-/**
- * Implements the chunked conversion from a UTF-8 encoding of JSON
- * to its corresponding object.
- */
-class _JsonUtf8DecoderSink extends ByteConversionSink {
-  final _JsonUtf8Parser _parser;
-  final Sink<Object?> _sink;
-
-  _JsonUtf8DecoderSink(reviver, this._sink, bool allowMalformed)
-      : _parser = _createParser(reviver, allowMalformed);
-
-  static _JsonUtf8Parser _createParser(
-      Object? Function(Object? key, Object? value)? reviver,
-      bool allowMalformed) {
-    return new _JsonUtf8Parser(new _JsonListener(reviver), allowMalformed);
-  }
-
-  void addSlice(List<int> chunk, int start, int end, bool isLast) {
-    _addChunk(chunk, start, end);
-    if (isLast) close();
-  }
-
-  void add(List<int> chunk) {
-    _addChunk(chunk, 0, chunk.length);
-  }
-
-  void _addChunk(List<int> chunk, int start, int end) {
-    _parser.chunk = chunk;
-    _parser.chunkEnd = end;
-    _parser.parse(start);
-  }
-
-  void close() {
-    _parser.close();
-    var decoded = _parser.result;
-    _sink.add(decoded);
-    _sink.close();
-  }
-}
-
 @patch
 class _Utf8Decoder {
-  /// Flags indicating presence of the various kinds of bytes in the input.
-  int _scanFlags = 0;
-
-  /// How many bytes of the BOM have been read so far. Set to -1 when the BOM
-  /// has been skipped (or was not present).
-  int _bomIndex = 0;
-
-  // Table for the scanning phase, which quickly scans through the input.
-  //
-  // Each input byte is looked up in the table, providing a size and some flags.
-  // The sizes are summed, and the flags are or'ed together.
-  //
-  // The resulting size and flags indicate:
-  // A) How many UTF-16 code units will be emitted by the decoding of this
-  //    input. This can be used to allocate a string of the correct length up
-  //    front.
-  // B) Which decoder and resulting string representation is appropriate. There
-  //    are three cases:
-  //    1) Pure ASCII (flags == 0): The input can simply be put into a
-  //       OneByteString without further decoding.
-  //    2) Latin1 (flags == (flagLatin1 | flagExtension)): The result can be
-  //       represented by a OneByteString, and the decoder can assume that only
-  //       Latin1 characters are present.
-  //    3) Arbitrary input (otherwise): Needs a full-featured decoder. Output
-  //       can be represented by a TwoByteString.
-
-  static const int sizeMask = 0x03;
-  static const int flagsMask = 0x3C;
-
-  static const int flagExtension = 1 << 2;
-  static const int flagLatin1 = 1 << 3;
-  static const int flagNonLatin1 = 1 << 4;
-  static const int flagIllegal = 1 << 5;
-
-  // ASCII     'A' = 64 + (1);
-  // Extension 'D' = 64 + (0 | flagExtension);
-  // Latin1    'I' = 64 + (1 | flagLatin1);
-  // BMP       'Q' = 64 + (1 | flagNonLatin1);
-  // Non-BMP   'R' = 64 + (2 | flagNonLatin1);
-  // Illegal   'a' = 64 + (1 | flagIllegal);
-  // Illegal   'b' = 64 + (2 | flagIllegal);
-  static const String scanTable = ""
-      "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" // 00-1F
-      "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" // 20-3F
-      "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" // 40-5F
-      "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" // 60-7F
-      "DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD" // 80-9F
-      "DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD" // A0-BF
-      "aaIIQQQQQQQQQQQQQQQQQQQQQQQQQQQQ" // C0-DF
-      "QQQQQQQQQQQQQQQQRRRRRbbbbbbbbbbb" // E0-FF
-      ;
-
-  /// Max chunk to scan at a time.
-  ///
-  /// Avoids staying away from safepoints too long.
-  /// The Utf8ScanInstr relies on this being small enough to ensure the
-  /// decoded length stays within Smi range.
-  static const int scanChunkSize = 65536;
-
-  /// Reset the decoder to a state where it is ready to decode a new string but
-  /// will not skip a leading BOM. Used by the fused UTF-8 / JSON decoder.
-  void reset() {
-    _state = initial;
-    _bomIndex = -1;
-  }
-
-  @pragma("vm:prefer-inline")
-  int scan(Uint8List bytes, int start, int end) {
-    // Assumes 0 <= start <= end <= bytes.length
-    int size = 0;
-    _scanFlags = 0;
-    int localStart = start;
-    while (end - localStart > scanChunkSize) {
-      int localEnd = localStart + scanChunkSize;
-      size += _scan(bytes, localStart, localEnd, scanTable);
-      localStart = localEnd;
-    }
-    size += _scan(bytes, localStart, end, scanTable);
-    return size;
-  }
-
-  @pragma("vm:recognized", "other")
-  @pragma("vm:prefer-inline")
-  @pragma("vm:idempotent")
-  int _scan(Uint8List bytes, int start, int end, String scanTable) {
-    int size = 0;
-    int flags = 0;
-    for (int i = start; i < end; i++) {
-      int t = scanTable.codeUnitAt(bytes[i]);
-      size += t & sizeMask;
-      flags |= t;
-    }
-    _scanFlags |= flags & flagsMask;
-    return size;
-  }
-
-  // The VM decoder handles BOM explicitly instead of via the state machine.
   @patch
-  _Utf8Decoder(this.allowMalformed) : _state = initial;
+  _Utf8Decoder(this.allowMalformed) : _state = beforeBom;
 
   @patch
   String convertSingle(List<int> codeUnits, int start, int? maybeEnd) {
-    int end = RangeError.checkValidRange(start, maybeEnd, codeUnits.length);
-
-    // Have bytes as Uint8List.
-    Uint8List bytes;
-    int errorOffset;
-    if (codeUnits is Uint8List) {
-      bytes = unsafeCast<Uint8List>(codeUnits);
-      errorOffset = 0;
-    } else {
-      bytes = _makeUint8List(codeUnits, start, end);
-      errorOffset = start;
-      end -= start;
-      start = 0;
-    }
-
-    // Skip initial BOM.
-    start = skipBomSingle(bytes, start, end);
-
-    // Special case empty input.
-    if (start == end) return "";
-
-    // Scan input to determine size and appropriate decoder.
-    int size = scan(bytes, start, end);
-    int flags = _scanFlags;
-
-    if (flags == 0) {
-      // Pure ASCII.
-      assert(size == end - start);
-      OneByteString result = OneByteString.withLength(size);
-      copyRangeFromUint8ListToOneByteString(bytes, result, start, 0, size);
-      return result;
-    }
-
-    String result;
-    if (flags == (flagLatin1 | flagExtension)) {
-      // Latin1.
-      result = decode8(bytes, start, end, size);
-    } else {
-      // Arbitrary Unicode.
-      result = decode16(bytes, start, end, size);
-    }
-    if (_state == accept) {
-      return result;
-    }
-
-    if (!allowMalformed) {
-      if (!isErrorState(_state)) {
-        // Unfinished sequence.
-        _state = errorUnfinished;
-        _charOrIndex = end;
-      }
-      final String message = errorDescription(_state);
-      throw FormatException(message, codeUnits, errorOffset + _charOrIndex);
-    }
-
-    // Start over on slow path.
-    _state = initial;
-    result = decodeGeneral(bytes, start, end, true);
-    assert(!isErrorState(_state));
-    return result;
+    return convertGeneral(codeUnits, start, maybeEnd, true);
   }
 
   @patch
   String convertChunked(List<int> codeUnits, int start, int? maybeEnd) {
-    int end = RangeError.checkValidRange(start, maybeEnd, codeUnits.length);
-
-    // Have bytes as Uint8List.
-    Uint8List bytes;
-    int errorOffset;
-    if (codeUnits is Uint8List) {
-      bytes = unsafeCast<Uint8List>(codeUnits);
-      errorOffset = 0;
-    } else {
-      bytes = _makeUint8List(codeUnits, start, end);
-      errorOffset = start;
-      end -= start;
-      start = 0;
-    }
-
-    // Skip initial BOM.
-    start = skipBomChunked(bytes, start, end);
-
-    // Special case empty input.
-    if (start == end) return "";
-
-    // Scan input to determine size and appropriate decoder.
-    int size = scan(bytes, start, end);
-    int flags = _scanFlags;
-
-    // Adjust scan flags and size based on carry-over state.
-    switch (_state) {
-      case IA:
-        break;
-      case X1:
-        flags |= _charOrIndex < (0x100 >> 6) ? flagLatin1 : flagNonLatin1;
-        if (end - start >= 1) {
-          size += _charOrIndex < (0x10000 >> 6) ? 1 : 2;
-        }
-        break;
-      case X2:
-        flags |= flagNonLatin1;
-        if (end - start >= 2) {
-          size += _charOrIndex < (0x10000 >> 12) ? 1 : 2;
-        }
-        break;
-      case TO:
-      case TS:
-        flags |= flagNonLatin1;
-        if (end - start >= 2) size += 1;
-        break;
-      case X3:
-      case QO:
-      case QR:
-        flags |= flagNonLatin1;
-        if (end - start >= 3) size += 2;
-        break;
-    }
-
-    if (flags == 0) {
-      // Pure ASCII.
-      assert(_state == accept);
-      assert(size == end - start);
-      OneByteString result = OneByteString.withLength(size);
-      copyRangeFromUint8ListToOneByteString(bytes, result, start, 0, size);
-      return result;
-    }
-
-    // Do not include any final, incomplete character in size.
-    int extensionCount = 0;
-    int i = end - 1;
-    while (i >= start && (bytes[i] & 0xC0) == 0x80) {
-      extensionCount++;
-      i--;
-    }
-    if (i >= start && bytes[i] >= ((~0x3F >> extensionCount) & 0xFF)) {
-      size -= bytes[i] >= 0xF0 ? 2 : 1;
-    }
-
-    final int carryOverState = _state;
-    final int carryOverChar = _charOrIndex;
-    String result;
-    if (flags == (flagLatin1 | flagExtension)) {
-      // Latin1.
-      result = decode8(bytes, start, end, size);
-    } else {
-      // Arbitrary Unicode.
-      result = decode16(bytes, start, end, size);
-    }
-    if (!isErrorState(_state)) {
-      return result;
-    }
-    assert(_bomIndex == -1);
-
-    if (!allowMalformed) {
-      final String message = errorDescription(_state);
-      _state = initial; // Ready for more input.
-      throw FormatException(message, codeUnits, errorOffset + _charOrIndex);
-    }
-
-    // Start over on slow path.
-    _state = carryOverState;
-    _charOrIndex = carryOverChar;
-    result = decodeGeneral(bytes, start, end, false);
-    assert(!isErrorState(_state));
-    return result;
-  }
-
-  @pragma("vm:prefer-inline")
-  int skipBomSingle(Uint8List bytes, int start, int end) {
-    if (end - start >= 3 &&
-        bytes[start] == 0xEF &&
-        bytes[start + 1] == 0xBB &&
-        bytes[start + 2] == 0xBF) {
-      return start + 3;
-    }
-    return start;
-  }
-
-  @pragma("vm:prefer-inline")
-  int skipBomChunked(Uint8List bytes, int start, int end) {
-    assert(start <= end);
-    int bomIndex = _bomIndex;
-    // Already skipped?
-    if (bomIndex == -1) return start;
-
-    const bomValues = <int>[0xEF, 0xBB, 0xBF];
-    int i = start;
-    while (bomIndex < 3) {
-      if (i == end) {
-        // Unfinished BOM.
-        _bomIndex = bomIndex;
-        return start;
-      }
-      if (bytes[i++] != bomValues[bomIndex++]) {
-        // No BOM.
-        _bomIndex = -1;
-        return start;
-      }
-    }
-    // Complete BOM.
-    _bomIndex = -1;
-    _state = initial;
-    return i;
-  }
-
-  String decode8(Uint8List bytes, int start, int end, int size) {
-    assert(start < end);
-    OneByteString result = OneByteString.withLength(size);
-    int i = start;
-    int j = 0;
-    if (_state == X1) {
-      // Half-way though 2-byte sequence
-      assert(_charOrIndex == 2 || _charOrIndex == 3);
-      final int e = bytes[i++] ^ 0x80;
-      if (e >= 0x40) {
-        _state = errorMissingExtension;
-        _charOrIndex = i - 1;
-        return "";
-      }
-      writeIntoOneByteString(result, j++, (_charOrIndex << 6) | e);
-      _state = accept;
-    }
-    assert(_state == accept);
-    while (i < end) {
-      int byte = bytes[i++];
-      if (byte >= 0x80) {
-        if (byte < 0xC0) {
-          _state = errorUnexpectedExtension;
-          _charOrIndex = i - 1;
-          return "";
-        }
-        assert(byte == 0xC2 || byte == 0xC3);
-        if (i == end) {
-          _state = X1;
-          _charOrIndex = byte & 0x1F;
-          break;
-        }
-        final int e = bytes[i++] ^ 0x80;
-        if (e >= 0x40) {
-          _state = errorMissingExtension;
-          _charOrIndex = i - 1;
-          return "";
-        }
-        byte = (byte << 6) | e;
-      }
-      writeIntoOneByteString(result, j++, byte);
-    }
-    // Output size must match, unless we are doing single conversion and are
-    // inside an unfinished sequence (which will trigger an error later).
-    assert(_bomIndex == 0 && _state != accept
-        ? (j == size - 1 || j == size - 2)
-        : (j == size));
-    return result;
-  }
-
-  String decode16(Uint8List bytes, int start, int end, int size) {
-    assert(start < end);
-    final String typeTable = _Utf8Decoder.typeTable;
-    final String transitionTable = _Utf8Decoder.transitionTable;
-    TwoByteString result = TwoByteString.withLength(size);
-    int i = start;
-    int j = 0;
-    int state = _state;
-    int char;
-
-    // First byte
-    assert(!isErrorState(state));
-    final int byte = bytes[i++];
-    final int type = typeTable.codeUnitAt(byte) & typeMask;
-    if (state == accept) {
-      char = byte & (shiftedByteMask >> type);
-      state = transitionTable.codeUnitAt(type);
-    } else {
-      char = (byte & 0x3F) | (_charOrIndex << 6);
-      state = transitionTable.codeUnitAt(state + type);
-    }
-
-    while (i < end) {
-      final int byte = bytes[i++];
-      final int type = typeTable.codeUnitAt(byte) & typeMask;
-      if (state == accept) {
-        if (char >= 0x10000) {
-          assert(char < 0x110000);
-          writeIntoTwoByteString(result, j++, 0xD7C0 + (char >> 10));
-          writeIntoTwoByteString(result, j++, 0xDC00 + (char & 0x3FF));
-        } else {
-          writeIntoTwoByteString(result, j++, char);
-        }
-        char = byte & (shiftedByteMask >> type);
-        state = transitionTable.codeUnitAt(type);
-      } else if (isErrorState(state)) {
-        _state = state;
-        _charOrIndex = i - 2;
-        return "";
-      } else {
-        char = (byte & 0x3F) | (char << 6);
-        state = transitionTable.codeUnitAt(state + type);
-      }
-    }
-
-    // Final write?
-    if (state == accept) {
-      if (char >= 0x10000) {
-        assert(char < 0x110000);
-        writeIntoTwoByteString(result, j++, 0xD7C0 + (char >> 10));
-        writeIntoTwoByteString(result, j++, 0xDC00 + (char & 0x3FF));
-      } else {
-        writeIntoTwoByteString(result, j++, char);
-      }
-    } else if (isErrorState(state)) {
-      _state = state;
-      _charOrIndex = end - 1;
-      return "";
-    }
-
-    _state = state;
-    _charOrIndex = char;
-    // Output size must match, unless we are doing single conversion and are
-    // inside an unfinished sequence (which will trigger an error later).
-    assert(_bomIndex == 0 && _state != accept
-        ? (j == size - 1 || j == size - 2)
-        : (j == size));
-    return result;
+    return convertGeneral(codeUnits, start, maybeEnd, false);
   }
 }
+
+double _parseDouble(String source, int start, int end) =>
+    double.parse(source.substring(start, end));
diff --git a/sdk/lib/_internal/wasm_js_compatibility/lib/int_patch.dart b/sdk/lib/_internal/wasm_js_compatibility/lib/int_patch.dart
new file mode 100644
index 0000000..ac170fa
--- /dev/null
+++ b/sdk/lib/_internal/wasm_js_compatibility/lib/int_patch.dart
@@ -0,0 +1,36 @@
+// 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:_internal" show patch, unsafeCast;
+import "dart:_js_types" show JSStringImpl;
+import 'dart:_js_helper' as js;
+
+@patch
+class int {
+  @patch
+  static int parse(String source,
+      {int? radix, @deprecated int onError(String source)?}) {
+    int? value = tryParse(source, radix: radix);
+    if (value != null) return value;
+    if (onError != null) return onError(source);
+    throw new FormatException(source);
+  }
+
+  @patch
+  static int? tryParse(String source, {int? radix}) {
+    // TODO(omersa): JS's `parseInt` is not compatible, copy dart2js's
+    // implementation.
+    if (radix == null) {
+      return js
+          .JS<double>('(s) => parseInt(s)',
+              unsafeCast<JSStringImpl>(source).toExternRef)
+          .toInt();
+    } else {
+      return js
+          .JS<double>('(s, r) => parseInt(s, r)',
+              unsafeCast<JSStringImpl>(source).toExternRef, radix.toDouble())
+          .toInt();
+    }
+  }
+}
diff --git a/sdk/lib/_internal/wasm_js_compatibility/lib/string_buffer_patch.dart b/sdk/lib/_internal/wasm_js_compatibility/lib/string_buffer_patch.dart
new file mode 100644
index 0000000..941bcb8
--- /dev/null
+++ b/sdk/lib/_internal/wasm_js_compatibility/lib/string_buffer_patch.dart
@@ -0,0 +1,62 @@
+// 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:_internal' show patch;
+import 'dart:_js_helper' as js;
+
+@patch
+class StringBuffer {
+  String _contents;
+
+  @patch
+  StringBuffer([Object content = ""]) : _contents = '$content';
+
+  @patch
+  int get length => _contents.length;
+
+  @patch
+  void write(Object? obj) => _writeString('$obj');
+
+  @patch
+  void writeCharCode(int charCode) =>
+      _writeString(String.fromCharCode(charCode));
+
+  @patch
+  void writeAll(Iterable<dynamic> objects, [String separator = ""]) {
+    _contents = _writeAll(_contents, objects, separator);
+  }
+
+  @patch
+  void writeln([Object? obj = ""]) => _writeString('$obj\n');
+
+  @patch
+  void clear() => _contents = "";
+
+  @patch
+  String toString() => _contents;
+
+  void _writeString(String str) {
+    _contents = _contents + str;
+  }
+
+  static String _writeAll(
+      String string, Iterable<Object?> objects, String separator) {
+    final iterator = objects.iterator;
+    if (!iterator.moveNext()) return string;
+    if (separator.isEmpty) {
+      do {
+        string = _writeOne(string, iterator.current);
+      } while (iterator.moveNext());
+    } else {
+      string = _writeOne(string, iterator.current);
+      while (iterator.moveNext()) {
+        string = _writeOne(string, separator);
+        string = _writeOne(string, iterator.current);
+      }
+    }
+    return string;
+  }
+
+  static String _writeOne(String string, Object? obj) => string + '$obj';
+}
diff --git a/sdk/lib/_internal/wasm_js_compatibility/lib/string_patch.dart b/sdk/lib/_internal/wasm_js_compatibility/lib/string_patch.dart
new file mode 100644
index 0000000..23e2052
--- /dev/null
+++ b/sdk/lib/_internal/wasm_js_compatibility/lib/string_patch.dart
@@ -0,0 +1,102 @@
+// 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:_internal' show EfficientLengthIterable, patch, unsafeCast;
+import 'dart:_js_helper' as js;
+import 'dart:_js_types';
+import 'dart:_wasm';
+import 'dart:js_interop';
+import 'dart:typed_data';
+
+@patch
+class String {
+  @patch
+  factory String.fromCharCodes(Iterable<int> charCodes,
+      [int start = 0, int? end]) {
+    final length = charCodes.length;
+
+    RangeError.checkValueInInterval(start, 0, length);
+
+    if (end != null) {
+      RangeError.checkValueInInterval(end, start, length);
+    }
+
+    // Skip until `start`.
+    final it = charCodes.iterator;
+    for (int i = 0; i < start; i++) {
+      it.moveNext();
+    }
+
+    // The part of the iterable converted to string is collected in a JS typed
+    // array, to be able to effciently get subarrays, to pass to
+    // `String.fromCharCode.apply`.
+    final charCodesLength = (end ?? length) - start;
+    final typedArrayLength = charCodesLength * 2;
+    final JSUint32ArrayImpl list =
+        unsafeCast<JSUint32ArrayImpl>(Uint32List(typedArrayLength));
+    int index = 0; // index in `list`.
+    end ??= start + charCodesLength;
+    for (int i = start; i < end; i++) {
+      if (!it.moveNext()) {
+        throw RangeError.range(end, start, i);
+      }
+      final charCode = it.current;
+      if (charCode >= 0 && charCode <= 0xffff) {
+        list[index++] = charCode;
+      } else if (charCode >= 0 && charCode <= 0x10ffff) {
+        list[index++] = 0xd800 + ((((charCode - 0x10000) >> 10) & 0x3ff));
+        list[index++] = 0xdc00 + (charCode & 0x3ff);
+      } else {
+        throw RangeError.range(charCode, 0, 0x10ffff);
+      }
+    }
+
+    // Create JS string from `list`.
+    const kMaxApply = 500;
+    if (index <= kMaxApply) {
+      return _fromCharCodeApplySubarray(list.toExternRef, 0, index.toDouble());
+    }
+
+    String result = '';
+    for (int i = 0; i < index; i += kMaxApply) {
+      final chunkEnd = (i + kMaxApply < index) ? i + kMaxApply : index;
+      result += _fromCharCodeApplySubarray(
+          list.toExternRef, i.toDouble(), chunkEnd.toDouble());
+    }
+    return result;
+  }
+
+  @patch
+  factory String.fromCharCode(int charCode) => _fromCharCode(charCode);
+
+  static String _fromOneByteCharCode(int charCode) => JSStringImpl(js
+      .JS<WasmExternRef?>('c => String.fromCharCode(c)', charCode.toDouble()));
+
+  static String _fromTwoByteCharCode(int low, int high) =>
+      JSStringImpl(js.JS<WasmExternRef?>('(l, h) => String.fromCharCode(h, l)',
+          low.toDouble(), high.toDouble()));
+
+  static String _fromCharCode(int charCode) {
+    if (0 <= charCode) {
+      if (charCode <= 0xffff) {
+        return _fromOneByteCharCode(charCode);
+      }
+      if (charCode <= 0x10ffff) {
+        var bits = charCode - 0x10000;
+        var low = 0xDC00 | (bits & 0x3ff);
+        var high = 0xD800 | (bits >> 10);
+        return _fromTwoByteCharCode(low, high);
+      }
+    }
+    throw RangeError.range(charCode, 0, 0x10ffff);
+  }
+
+  static String _fromCharCodeApplySubarray(
+          WasmExternRef? charCodes, double index, double end) =>
+      JSStringImpl(js.JS<WasmExternRef?>(
+          '(c, i, e) => String.fromCharCode.apply(null, c.subarray(i, e))',
+          charCodes,
+          index,
+          end));
+}
diff --git a/sdk/lib/libraries.json b/sdk/lib/libraries.json
index 1d61015..ccbc9a0 100644
--- a/sdk/lib/libraries.json
+++ b/sdk/lib/libraries.json
@@ -129,21 +129,23 @@
       "core": {
         "uri": "core/core.dart",
         "patches": [
-          "_internal/wasm/lib/core_patch.dart",
           "_internal/vm_shared/lib/array_patch.dart",
           "_internal/vm_shared/lib/bigint_patch.dart",
           "_internal/vm_shared/lib/bool_patch.dart",
           "_internal/vm_shared/lib/date_patch.dart",
           "_internal/vm_shared/lib/map_patch.dart",
           "_internal/vm_shared/lib/null_patch.dart",
+          "_internal/wasm/lib/boxed_double.dart",
+          "_internal/wasm/lib/boxed_int.dart",
+          "_internal/wasm/lib/boxed_int_to_string.dart",
+          "_internal/wasm/lib/core_patch.dart",
           "_internal/wasm/lib/date_patch_patch.dart",
+          "_internal/wasm/lib/int_common_patch.dart",
           "_internal/wasm/lib/int_patch.dart",
           "_internal/wasm/lib/string_buffer_patch.dart",
           "_internal/wasm/lib/string_patch.dart",
           "_internal/wasm/lib/sync_star_patch.dart",
-          "_internal/wasm/lib/weak_patch.dart",
-          "_internal/wasm/lib/boxed_double.dart",
-          "_internal/wasm/lib/boxed_int.dart"
+          "_internal/wasm/lib/weak_patch.dart"
         ]
       }
     }
@@ -158,21 +160,23 @@
       "core": {
         "uri": "core/core.dart",
         "patches": [
-          "_internal/wasm/lib/core_patch.dart",
           "_internal/vm_shared/lib/array_patch.dart",
           "_internal/vm_shared/lib/bigint_patch.dart",
           "_internal/vm_shared/lib/bool_patch.dart",
           "_internal/vm_shared/lib/date_patch.dart",
           "_internal/vm_shared/lib/map_patch.dart",
           "_internal/vm_shared/lib/null_patch.dart",
+          "_internal/wasm/lib/boxed_double.dart",
+          "_internal/wasm/lib/boxed_int.dart",
+          "_internal/wasm/lib/boxed_int_to_string.dart",
+          "_internal/wasm/lib/core_patch.dart",
           "_internal/wasm/lib/date_patch_patch.dart",
+          "_internal/wasm/lib/int_common_patch.dart",
           "_internal/wasm/lib/int_patch.dart",
           "_internal/wasm/lib/string_buffer_patch.dart",
           "_internal/wasm/lib/string_stringref_patch.dart",
           "_internal/wasm/lib/sync_star_patch.dart",
-          "_internal/wasm/lib/weak_patch.dart",
-          "_internal/wasm/lib/boxed_double.dart",
-          "_internal/wasm/lib/boxed_int.dart"
+          "_internal/wasm/lib/weak_patch.dart"
         ]
       }
     }
@@ -199,15 +203,17 @@
           "_internal/vm_shared/lib/date_patch.dart",
           "_internal/vm_shared/lib/map_patch.dart",
           "_internal/vm_shared/lib/null_patch.dart",
+          "_internal/wasm/lib/boxed_double.dart",
+          "_internal/wasm/lib/boxed_int.dart",
           "_internal/wasm/lib/core_patch.dart",
           "_internal/wasm/lib/date_patch_patch.dart",
-          "_internal/wasm/lib/int_patch.dart",
-          "_internal/wasm/lib/string_buffer_patch.dart",
-          "_internal/wasm/lib/string_patch.dart",
+          "_internal/wasm/lib/int_common_patch.dart",
           "_internal/wasm/lib/sync_star_patch.dart",
           "_internal/wasm/lib/weak_patch.dart",
-          "_internal/wasm/lib/boxed_double.dart",
-          "_internal/wasm/lib/boxed_int.dart"
+          "_internal/wasm_js_compatibility/lib/boxed_int_to_string.dart",
+          "_internal/wasm_js_compatibility/lib/int_patch.dart",
+          "_internal/wasm_js_compatibility/lib/string_buffer_patch.dart",
+          "_internal/wasm_js_compatibility/lib/string_patch.dart"
         ]
       },
       "typed_data": {
@@ -246,6 +252,9 @@
       "_simd": {
         "uri": "_internal/wasm/lib/simd.dart"
       },
+      "_string": {
+        "uri": "_internal/wasm/lib/string.dart"
+      },
       "_typed_data": {
         "uri": "_internal/wasm/lib/typed_data.dart"
       }
@@ -257,7 +266,8 @@
         "uri": "core/core.dart",
         "patches": [
           "_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"
         ]
       },
       "_http": {
@@ -281,9 +291,6 @@
       "_object_helper": {
         "uri": "_internal/wasm/lib/object_helper.dart"
       },
-      "_string": {
-        "uri": "_internal/wasm/lib/string.dart"
-      },
       "_string_helper": {
         "uri": "_internal/wasm/lib/string_helper.dart"
       },
diff --git a/sdk/lib/libraries.yaml b/sdk/lib/libraries.yaml
index ea22b5f..7c7f3d5 100644
--- a/sdk/lib/libraries.yaml
+++ b/sdk/lib/libraries.yaml
@@ -119,21 +119,23 @@
     core:
       uri: core/core.dart
       patches:
-        - _internal/wasm/lib/core_patch.dart
         - _internal/vm_shared/lib/array_patch.dart
         - _internal/vm_shared/lib/bigint_patch.dart
         - _internal/vm_shared/lib/bool_patch.dart
         - _internal/vm_shared/lib/date_patch.dart
         - _internal/vm_shared/lib/map_patch.dart
         - _internal/vm_shared/lib/null_patch.dart
+        - _internal/wasm/lib/boxed_double.dart
+        - _internal/wasm/lib/boxed_int.dart
+        - _internal/wasm/lib/boxed_int_to_string.dart
+        - _internal/wasm/lib/core_patch.dart
         - _internal/wasm/lib/date_patch_patch.dart
+        - _internal/wasm/lib/int_common_patch.dart
         - _internal/wasm/lib/int_patch.dart
         - _internal/wasm/lib/string_buffer_patch.dart
         - _internal/wasm/lib/string_patch.dart
         - _internal/wasm/lib/sync_star_patch.dart
         - _internal/wasm/lib/weak_patch.dart
-        - _internal/wasm/lib/boxed_double.dart
-        - _internal/wasm/lib/boxed_int.dart
 
 wasm_stringref:
   include:
@@ -142,21 +144,23 @@
     core:
       uri: core/core.dart
       patches:
-        - _internal/wasm/lib/core_patch.dart
         - _internal/vm_shared/lib/array_patch.dart
         - _internal/vm_shared/lib/bigint_patch.dart
         - _internal/vm_shared/lib/bool_patch.dart
         - _internal/vm_shared/lib/date_patch.dart
         - _internal/vm_shared/lib/map_patch.dart
         - _internal/vm_shared/lib/null_patch.dart
+        - _internal/wasm/lib/boxed_double.dart
+        - _internal/wasm/lib/boxed_int.dart
+        - _internal/wasm/lib/boxed_int_to_string.dart
+        - _internal/wasm/lib/core_patch.dart
         - _internal/wasm/lib/date_patch_patch.dart
+        - _internal/wasm/lib/int_common_patch.dart
         - _internal/wasm/lib/int_patch.dart
         - _internal/wasm/lib/string_buffer_patch.dart
         - _internal/wasm/lib/string_stringref_patch.dart
         - _internal/wasm/lib/sync_star_patch.dart
         - _internal/wasm/lib/weak_patch.dart
-        - _internal/wasm/lib/boxed_double.dart
-        - _internal/wasm/lib/boxed_int.dart
 
 wasm_js_compatibility:
   include:
@@ -175,15 +179,17 @@
         - _internal/vm_shared/lib/date_patch.dart
         - _internal/vm_shared/lib/map_patch.dart
         - _internal/vm_shared/lib/null_patch.dart
-        - _internal/wasm/lib/core_patch.dart
-        - _internal/wasm/lib/date_patch_patch.dart
-        - _internal/wasm/lib/int_patch.dart
-        - _internal/wasm/lib/string_buffer_patch.dart
-        - _internal/wasm/lib/string_patch.dart
-        - _internal/wasm/lib/sync_star_patch.dart
-        - _internal/wasm/lib/weak_patch.dart
         - _internal/wasm/lib/boxed_double.dart
         - _internal/wasm/lib/boxed_int.dart
+        - _internal/wasm/lib/core_patch.dart
+        - _internal/wasm/lib/date_patch_patch.dart
+        - _internal/wasm/lib/int_common_patch.dart
+        - _internal/wasm/lib/sync_star_patch.dart
+        - _internal/wasm/lib/weak_patch.dart
+        - _internal/wasm_js_compatibility/lib/boxed_int_to_string.dart
+        - _internal/wasm_js_compatibility/lib/int_patch.dart
+        - _internal/wasm_js_compatibility/lib/string_buffer_patch.dart
+        - _internal/wasm_js_compatibility/lib/string_patch.dart
     typed_data:
       uri: typed_data/typed_data.dart
       patches:
@@ -208,6 +214,8 @@
         - _internal/wasm/lib/typed_data_patch.dart
     _simd:
       uri: _internal/wasm/lib/simd.dart
+    _string:
+      uri: _internal/wasm/lib/string.dart
     _typed_data:
       uri: _internal/wasm/lib/typed_data.dart
 
@@ -218,6 +226,7 @@
       patches:
         - _internal/wasm/lib/boxed_double.dart
         - _internal/wasm/lib/boxed_int.dart
+        - _internal/wasm/lib/int_common_patch.dart
     _http:
       uri: _http/http.dart
     _internal:
@@ -232,8 +241,6 @@
       uri: _internal/wasm/lib/js_types.dart
     _object_helper:
       uri: _internal/wasm/lib/object_helper.dart
-    _string:
-      uri: _internal/wasm/lib/string.dart
     _string_helper:
       uri: _internal/wasm/lib/string_helper.dart
     _wasm:
diff --git a/tests/web/wasm/string_switch_test.dart b/tests/web/wasm/string_switch_test.dart
new file mode 100644
index 0000000..faf3460
--- /dev/null
+++ b/tests/web/wasm/string_switch_test.dart
@@ -0,0 +1,26 @@
+// 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:js_util';
+import 'dart:js_interop';
+
+import 'package:expect/expect.dart';
+
+@JS()
+external void eval(String code);
+
+void main() {
+  eval(r'''
+        globalThis.jsString = "hi";
+    ''');
+
+  String jsString = getProperty(globalThis, "jsString");
+
+  switch (jsString) {
+    case "hi":
+      break;
+    default:
+      Expect.fail("Unexpected JS String: $jsString");
+  }
+}
diff --git a/tools/VERSION b/tools/VERSION
index 2231d63..354dae7 100644
--- a/tools/VERSION
+++ b/tools/VERSION
@@ -27,5 +27,5 @@
 MAJOR 3
 MINOR 3
 PATCH 0
-PRERELEASE 81
+PRERELEASE 82
 PRERELEASE_PATCH 0