Version 2.17.0-113.0.dev

Merge commit '6faa5f3bd00ad8cbc640b3fc80cf7466c002a7df' into 'dev'
diff --git a/.dart_tool/package_config.json b/.dart_tool/package_config.json
index 76aed36..66d9ee6 100644
--- a/.dart_tool/package_config.json
+++ b/.dart_tool/package_config.json
@@ -229,6 +229,12 @@
       "languageVersion": "2.12"
     },
     {
+      "name": "dart2wasm",
+      "rootUri": "../pkg/dart2wasm",
+      "packageUri": "lib/",
+      "languageVersion": "2.12"
+    },
+    {
       "name": "dart_internal",
       "rootUri": "../pkg/dart_internal",
       "packageUri": "lib/",
@@ -781,6 +787,12 @@
       "languageVersion": "2.12"
     },
     {
+      "name": "wasm_builder",
+      "rootUri": "../pkg/wasm_builder",
+      "packageUri": "lib/",
+      "languageVersion": "2.12"
+    },
+    {
       "name": "watcher",
       "rootUri": "../third_party/pkg/watcher",
       "packageUri": "lib/",
diff --git a/.packages b/.packages
index e8855d6..38ab4a9 100644
--- a/.packages
+++ b/.packages
@@ -33,6 +33,7 @@
 dart2js_runtime_metrics:pkg/dart2js_runtime_metrics/lib
 dart2js_tools:pkg/dart2js_tools/lib
 dart2native:pkg/dart2native/lib
+dart2wasm:pkg/dart2wasm/lib
 dart_internal:pkg/dart_internal/lib
 dart_style:third_party/pkg_tested/dart_style/lib
 dartdev:pkg/dartdev/lib
@@ -117,6 +118,7 @@
 vm:pkg/vm/lib
 vm_service:pkg/vm_service/lib
 vm_snapshot_analysis:pkg/vm_snapshot_analysis/lib
+wasm_builder:pkg/wasm_builder/lib
 watcher:third_party/pkg/watcher/lib
 webdriver:third_party/pkg/webdriver/lib
 webkit_inspection_protocol:third_party/pkg/webkit_inspection_protocol/lib
diff --git a/pkg/_fe_analyzer_shared/lib/src/scanner/scanner.dart b/pkg/_fe_analyzer_shared/lib/src/scanner/scanner.dart
index 63fd51c..35a6386 100644
--- a/pkg/_fe_analyzer_shared/lib/src/scanner/scanner.dart
+++ b/pkg/_fe_analyzer_shared/lib/src/scanner/scanner.dart
@@ -72,7 +72,7 @@
     bool includeComments: false,
     LanguageVersionChanged? languageVersionChanged}) {
   if (bytes.last != 0) {
-    throw new ArgumentError("[bytes]: the last byte must be null.");
+    throw new ArgumentError("[bytes]: the last byte must be 0.");
   }
   Scanner scanner = new Utf8BytesScanner(bytes,
       configuration: configuration,
diff --git a/pkg/_fe_analyzer_shared/lib/src/testing/id_generation.dart b/pkg/_fe_analyzer_shared/lib/src/testing/id_generation.dart
index 896c670..a1bbadb 100644
--- a/pkg/_fe_analyzer_shared/lib/src/testing/id_generation.dart
+++ b/pkg/_fe_analyzer_shared/lib/src/testing/id_generation.dart
@@ -65,13 +65,13 @@
     Map<Id, Map<String, IdValue>> idValuePerId = idValuePerUri[uri] ?? {};
     Map<Id, Map<String, ActualData<T>>> actualDataPerId =
         actualDataPerUri[uri] ?? {};
-    AnnotatedCode code = annotatedCode[uri]!;
-    assert(
-        // ignore: unnecessary_null_comparison
-        code != null, "No annotated code for ${uri} in ${annotatedCode.keys}");
-    result[uri] = _computeAnnotations(code, expectedMaps.keys, actualMarkers,
+    AnnotatedCode? code = annotatedCode[uri];
+    if (code != null) {
+      // Annotations are not computed from synthesized code.
+      result[uri] = _computeAnnotations(code, expectedMaps.keys, actualMarkers,
         idValuePerId, actualDataPerId, dataInterpreter,
         sortMarkers: false, createDiff: createDiff, forceUpdate: forceUpdate);
+    }
   }
   return result;
 }
diff --git a/pkg/_fe_analyzer_shared/lib/src/testing/id_testing.dart b/pkg/_fe_analyzer_shared/lib/src/testing/id_testing.dart
index 68ad3ab..ca35c17 100644
--- a/pkg/_fe_analyzer_shared/lib/src/testing/id_testing.dart
+++ b/pkg/_fe_analyzer_shared/lib/src/testing/id_testing.dart
@@ -771,7 +771,9 @@
   List<FileSystemEntity> entities = dataDir
       .listSync()
       .where((entity) =>
-          !entity.path.endsWith('~') && !entity.path.endsWith('marker.options'))
+          !entity.path.endsWith('~') &&
+          !entity.path.endsWith('marker.options') &&
+          !entity.path.endsWith('.expect'))
       .toList();
   if (shards > 1) {
     entities.sort((a, b) => getTestName(a).compareTo(getTestName(b)));
diff --git a/pkg/dart2wasm/bin/dart2wasm.dart b/pkg/dart2wasm/bin/dart2wasm.dart
new file mode 100644
index 0000000..10f1750
--- /dev/null
+++ b/pkg/dart2wasm/bin/dart2wasm.dart
@@ -0,0 +1,99 @@
+// 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.
+
+import 'dart:io';
+import 'dart:typed_data';
+
+import 'package:front_end/src/api_unstable/vm.dart'
+    show printDiagnosticMessage, resolveInputUri;
+
+import 'package:dart2wasm/compile.dart';
+import 'package:dart2wasm/translator.dart';
+
+final Map<String, void Function(TranslatorOptions, bool)> boolOptionMap = {
+  "export-all": (o, value) => o.exportAll = value,
+  "inlining": (o, value) => o.inlining = value,
+  "lazy-constants": (o, value) => o.lazyConstants = value,
+  "local-nullability": (o, value) => o.localNullability = value,
+  "name-section": (o, value) => o.nameSection = value,
+  "nominal-types": (o, value) => o.nominalTypes = value,
+  "parameter-nullability": (o, value) => o.parameterNullability = value,
+  "polymorphic-specialization": (o, value) =>
+      o.polymorphicSpecialization = value,
+  "print-kernel": (o, value) => o.printKernel = value,
+  "print-wasm": (o, value) => o.printWasm = value,
+  "runtime-types": (o, value) => o.runtimeTypes = value,
+  "string-data-segments": (o, value) => o.stringDataSegments = value,
+};
+final Map<String, void Function(TranslatorOptions, int)> intOptionMap = {
+  "watch": (o, value) => (o.watchPoints ??= []).add(value),
+};
+
+Never usage(String message) {
+  print("Usage: dart2wasm [<options>] <infile.dart> <outfile.wasm>");
+  print("");
+  print("Options:");
+  print("  --dart-sdk=<path>");
+  print("");
+  for (String option in boolOptionMap.keys) {
+    print("  --[no-]$option");
+  }
+  print("");
+  for (String option in intOptionMap.keys) {
+    print("  --$option <value>");
+  }
+  print("");
+
+  throw message;
+}
+
+Future<int> main(List<String> args) async {
+  Uri sdkPath = Platform.script.resolve("../../../sdk");
+  TranslatorOptions options = TranslatorOptions();
+  List<String> nonOptions = [];
+  void Function(TranslatorOptions, int)? intOptionFun = null;
+  for (String arg in args) {
+    if (intOptionFun != null) {
+      intOptionFun(options, int.parse(arg));
+      intOptionFun = null;
+    } else if (arg.startsWith("--dart-sdk=")) {
+      String path = arg.substring("--dart-sdk=".length);
+      sdkPath = Uri.file(Directory(path).absolute.path);
+    } else if (arg.startsWith("--no-")) {
+      var optionFun = boolOptionMap[arg.substring(5)];
+      if (optionFun == null) usage("Unknown option $arg");
+      optionFun(options, false);
+    } else if (arg.startsWith("--")) {
+      var optionFun = boolOptionMap[arg.substring(2)];
+      if (optionFun != null) {
+        optionFun(options, true);
+      } else {
+        intOptionFun = intOptionMap[arg.substring(2)];
+        if (intOptionFun == null) usage("Unknown option $arg");
+      }
+    } else {
+      nonOptions.add(arg);
+    }
+  }
+  if (intOptionFun != null) {
+    usage("Missing argument to ${args.last}");
+  }
+
+  if (nonOptions.length != 2) usage("Requires two file arguments");
+  String input = nonOptions[0];
+  String output = nonOptions[1];
+  Uri mainUri = resolveInputUri(input);
+
+  Uint8List? module = await compileToModule(mainUri, sdkPath, options,
+      (message) => printDiagnosticMessage(message, print));
+
+  if (module == null) {
+    exitCode = 1;
+    return exitCode;
+  }
+
+  await File(output).writeAsBytes(module);
+
+  return 0;
+}
diff --git a/pkg/dart2wasm/bin/run_wasm.js b/pkg/dart2wasm/bin/run_wasm.js
new file mode 100644
index 0000000..08a6d92
--- /dev/null
+++ b/pkg/dart2wasm/bin/run_wasm.js
@@ -0,0 +1,63 @@
+// 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.
+//
+// Runner V8 script for testing dart2wasm, takes ".wasm" file as argument.
+// Run as follows:
+//
+// $> d8 --experimental-wasm-gc --wasm-gc-js-interop run_wasm.js -- <file_name>.wasm
+
+function stringFromDartString(string) {
+    var length = inst.exports.$stringLength(string);
+    var array = new Array(length);
+    for (var i = 0; i < length; i++) {
+        array[i] = inst.exports.$stringRead(string, i);
+    }
+    return String.fromCharCode(...array);
+}
+
+function stringToDartString(string) {
+    var length = string.length;
+    var range = 0;
+    for (var i = 0; i < length; i++) {
+        range |= string.codePointAt(i);
+    }
+    if (range < 256) {
+        var dartString = inst.exports.$stringAllocate1(length);
+        for (var i = 0; i < length; i++) {
+            inst.exports.$stringWrite1(dartString, i, string.codePointAt(i));
+        }
+        return dartString;
+    } else {
+        var dartString = inst.exports.$stringAllocate2(length);
+        for (var i = 0; i < length; i++) {
+            inst.exports.$stringWrite2(dartString, i, string.codePointAt(i));
+        }
+        return dartString;
+    }
+}
+
+// Imports for printing and event loop
+var dart2wasm = {
+    printToConsole: function(string) {
+        console.log(stringFromDartString(string))
+    },
+    scheduleCallback: function(milliseconds, closure) {
+        setTimeout(function() {
+            inst.exports.$call0(closure);
+        }, milliseconds);
+    }
+};
+
+// Create a Wasm module from the binary wasm file.
+var bytes = readbuffer(arguments[0]);
+var module = new WebAssembly.Module(bytes);
+
+// Instantiate the Wasm module, importing from the global scope.
+var importObject = (typeof window !== 'undefined')
+    ? window
+    : Realm.global(Realm.current());
+var inst = new WebAssembly.Instance(module, importObject);
+
+var result = inst.exports.main();
+if (result) console.log(result);
diff --git a/pkg/dart2wasm/dart2wasm.md b/pkg/dart2wasm/dart2wasm.md
new file mode 100644
index 0000000..8dc3203
--- /dev/null
+++ b/pkg/dart2wasm/dart2wasm.md
@@ -0,0 +1,65 @@
+## Running dart2wasm
+
+You don't need to build the Dart SDK to run dart2wasm, as long as you have a Dart SDK installed.
+
+To compile a Dart file to Wasm, run:
+
+`dart --enable-asserts pkg/dart2wasm/bin/dart2wasm.dart` *options* *infile*`.dart` *outfile*`.wasm`
+
+where *options* include:
+
+| Option                                  | Default | Description |
+| --------------------------------------- | ------- | ----------- |
+| `--dart-sdk=`*path*                     | relative to script | The location of the `sdk` directory inside the Dart SDK, containing the core library sources.
+| `--`[`no-`]`export-all`                 | no      | Export all functions; otherwise, just export `main`.
+| `--`[`no-`]`inlining`                   | no      | Inline small functions.
+| `--`[`no-`]`lazy-constants`             | no      | Instantiate constants lazily.
+| `--`[`no-`]`local-nullability`          | no      | Use non-nullable types for non-nullable locals and temporaries.
+| `--`[`no-`]`name-section`               | yes     | Emit Name Section with function names.
+| `--`[`no-`]`nominal-types`              | no      | Emit experimental nominal types.
+| `--`[`no-`]`parameter-nullability`      | yes     | Use non-nullable types for non-nullable parameters and return values.
+| `--`[`no-`]`polymorphic-specialization` | no      | Do virtual calls by switching on the class ID instead of using `call_indirect`.
+| `--`[`no-`]`print-kernel`               | no      | Print IR for each function before compiling it.
+| `--`[`no-`]`print-wasm`                 | no      | Print Wasm instructions of each compiled function.
+| `--`[`no-`]`runtime-types`              | yes     | Use RTTs for allocations and casts.
+| `--`[`no-`]`string-data-segments`       | no      | Use experimental array init from data segment for string constants.
+| `--watch` *offset*                      |         | Print stack trace leading to the byte at offset *offset* in the `.wasm` output file. Can be specified multiple times.
+
+The resulting `.wasm` file can be run with:
+
+`d8 --experimental-wasm-gc --wasm-gc-js-interop pkg/dart2wasm/bin/run_wasm.js -- `*outfile*`.wasm`
+
+## Imports and exports
+
+To import a function, declare it as a global, external function and mark it with a `wasm:import` pragma indicating the imported name (which must be two identifiers separated by a dot):
+```dart
+@pragma("wasm:import", "foo.bar")
+external void fooBar(Object object);
+```
+which will call `foo.bar` on the host side:
+```javascript
+var foo = {
+    bar: function(object) { /* implementation here */ }
+};
+```
+To export a function, mark it with a `wasm:export` pragma:
+```dart
+@pragma("wasm:export")
+void foo(double x) { /* implementation here */  }
+
+@pragma("wasm:export", "baz")
+void bar(double x) { /* implementation here */  }
+```
+With the Wasm module instance in `inst`, these can be called as:
+```javascript
+inst.exports.foo(1);
+inst.exports.baz(2);
+```
+
+### Types to use for interop
+
+In the signatures of imported and exported functions, use the following types:
+
+- For numbers, use `double`.
+- For Dart objects, use the corresponding Dart type. The fields of the underlying representation can be accessed on the JS side as `.$field0`, `.$field1` etc., but there is currently no defined way of finding the field index of a particular Dart field, so this mechanism is mainly useful for special objects with known layout.
+- For JS objects, use the `WasmAnyRef` type (or `WasmAnyRef?` as applicable) from the `dart:wasm` package. These can be passed around and stored as opaque values on the Dart side.
diff --git a/pkg/dart2wasm/lib/class_info.dart b/pkg/dart2wasm/lib/class_info.dart
new file mode 100644
index 0000000..54037e0
--- /dev/null
+++ b/pkg/dart2wasm/lib/class_info.dart
@@ -0,0 +1,377 @@
+// 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.
+
+import 'dart:math';
+
+import 'package:dart2wasm/translator.dart';
+
+import 'package:kernel/ast.dart';
+
+import 'package:wasm_builder/wasm_builder.dart' as w;
+
+/// Wasm struct field indices for fields that are accessed explicitly from Wasm
+/// code, e.g. in intrinsics.
+///
+/// The values are validated by asserts, typically either through
+/// [ClassInfo.addField] (for manually added fields) or by a line in
+/// [FieldIndex.validate] (for fields declared in Dart code).
+class FieldIndex {
+  static const classId = 0;
+  static const boxValue = 1;
+  static const identityHash = 1;
+  static const stringArray = 2;
+  static const closureContext = 2;
+  static const closureFunction = 3;
+  static const typedListBaseLength = 2;
+  static const typedListArray = 3;
+  static const typedListViewTypedData = 3;
+  static const typedListViewOffsetInBytes = 4;
+  static const byteDataViewLength = 2;
+  static const byteDataViewTypedData = 3;
+  static const byteDataViewOffsetInBytes = 4;
+
+  static void validate(Translator translator) {
+    void check(Class cls, String name, int expectedIndex) {
+      assert(
+          translator.fieldIndex[
+                  cls.fields.firstWhere((f) => f.name.text == name)] ==
+              expectedIndex,
+          "Unexpected field index for ${cls.name}.$name");
+    }
+
+    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);
+    check(translator.functionClass, "context", FieldIndex.closureContext);
+  }
+}
+
+const int initialIdentityHash = 0;
+
+/// Information about the Wasm representation for a class.
+class ClassInfo {
+  /// The Dart class that this info corresponds to. The top type does not have
+  /// an associated Dart class.
+  final Class? cls;
+
+  /// The Class ID of this class, stored in every instance of the class.
+  final int classId;
+
+  /// Depth of this class in the Wasm type hierarchy.
+  final int depth;
+
+  /// The Wasm struct used to represent instances of this class. A class will
+  /// sometimes use the same struct as its superclass.
+  final w.StructType struct;
+
+  /// Wasm global containing the RTT for this class.
+  late final w.DefinedGlobal rtt;
+
+  /// The superclass for this class. This will usually be the Dart superclass,
+  /// but there are a few exceptions, where the Wasm type hierarchy does not
+  /// follow the Dart class hierarchy.
+  final ClassInfo? superInfo;
+
+  /// For every type parameter which is directly mapped to a type parameter in
+  /// the superclass, this contains the corresponding superclass type
+  /// parameter. These will reuse the corresponding type parameter field of
+  /// the superclass.
+  final Map<TypeParameter, TypeParameter> typeParameterMatch;
+
+  /// The class whose struct is used as the type for variables of this type.
+  /// This is a type which is a superclass of all subtypes of this type.
+  late ClassInfo repr;
+
+  /// All classes which implement this class. This is used to compute `repr`.
+  final List<ClassInfo> implementedBy = [];
+
+  late final w.RefType nullableType = w.RefType.def(struct, nullable: true);
+  late final w.RefType nonNullableType = w.RefType.def(struct, nullable: false);
+
+  w.RefType typeWithNullability(bool nullable) =>
+      nullable ? nullableType : nonNullableType;
+
+  ClassInfo(this.cls, this.classId, this.depth, this.struct, this.superInfo,
+      ClassInfoCollector collector,
+      {this.typeParameterMatch = const {}}) {
+    if (collector.options.useRttGlobals) {
+      rtt = collector.makeRtt(struct, superInfo);
+    }
+    implementedBy.add(this);
+  }
+
+  void addField(w.FieldType fieldType, [int? expectedIndex]) {
+    assert(expectedIndex == null || expectedIndex == struct.fields.length);
+    struct.fields.add(fieldType);
+  }
+}
+
+ClassInfo upperBound(Iterable<ClassInfo> classes) {
+  while (classes.length > 1) {
+    Set<ClassInfo> newClasses = {};
+    int minDepth = 999999999;
+    int maxDepth = 0;
+    for (ClassInfo info in classes) {
+      minDepth = min(minDepth, info.depth);
+      maxDepth = max(maxDepth, info.depth);
+    }
+    int targetDepth = minDepth == maxDepth ? minDepth - 1 : minDepth;
+    for (ClassInfo info in classes) {
+      while (info.depth > targetDepth) {
+        info = info.superInfo!;
+      }
+      newClasses.add(info);
+    }
+    classes = newClasses;
+  }
+  return classes.single;
+}
+
+/// Constructs the Wasm type hierarchy.
+class ClassInfoCollector {
+  final Translator translator;
+  int nextClassId = 0;
+  late final ClassInfo topInfo;
+
+  late final w.FieldType typeType =
+      w.FieldType(translator.classInfo[translator.typeClass]!.nullableType);
+
+  ClassInfoCollector(this.translator);
+
+  w.Module get m => translator.m;
+
+  TranslatorOptions get options => translator.options;
+
+  w.DefinedGlobal makeRtt(w.StructType struct, ClassInfo? superInfo) {
+    assert(options.useRttGlobals);
+    int depth = superInfo != null ? superInfo.depth + 1 : 0;
+    final w.DefinedGlobal rtt =
+        m.addGlobal(w.GlobalType(w.Rtt(struct, depth), mutable: false));
+    final w.Instructions b = rtt.initializer;
+    if (superInfo != null) {
+      b.global_get(superInfo.rtt);
+      b.rtt_sub(struct);
+    } else {
+      b.rtt_canon(struct);
+    }
+    b.end();
+    return rtt;
+  }
+
+  void initializeTop() {
+    final w.StructType struct = translator.structType("#Top");
+    topInfo = ClassInfo(null, nextClassId++, 0, struct, null, this);
+    translator.classes.add(topInfo);
+    translator.classForHeapType[struct] = topInfo;
+  }
+
+  void initialize(Class cls) {
+    ClassInfo? info = translator.classInfo[cls];
+    if (info == null) {
+      Class? superclass = cls.superclass;
+      if (superclass == null) {
+        ClassInfo superInfo = topInfo;
+        final w.StructType struct =
+            translator.structType(cls.name, superType: superInfo.struct);
+        info = ClassInfo(
+            cls, nextClassId++, superInfo.depth + 1, struct, superInfo, this);
+        // Mark Top type as implementing Object to force the representation
+        // type of Object to be Top.
+        info.implementedBy.add(topInfo);
+      } else {
+        // Recursively initialize all supertypes before initializing this class.
+        initialize(superclass);
+        for (Supertype interface in cls.implementedTypes) {
+          initialize(interface.classNode);
+        }
+
+        // In the Wasm type hierarchy, Object, bool and num sit directly below
+        // the Top type. The implementation classes (_StringBase, _Type and the
+        // box classes) sit directly below the public classes they implement.
+        // All other classes sit below their superclass.
+        ClassInfo superInfo = cls == translator.coreTypes.boolClass ||
+                cls == translator.coreTypes.numClass
+            ? topInfo
+            : cls == translator.stringBaseClass ||
+                    cls == translator.typeClass ||
+                    translator.boxedClasses.values.contains(cls)
+                ? translator.classInfo[cls.implementedTypes.single.classNode]!
+                : translator.classInfo[superclass]!;
+
+        // Figure out which type parameters can reuse a type parameter field of
+        // the superclass.
+        Map<TypeParameter, TypeParameter> typeParameterMatch = {};
+        if (cls.typeParameters.isNotEmpty) {
+          Supertype supertype = cls.superclass == superInfo.cls
+              ? cls.supertype!
+              : cls.implementedTypes.single;
+          for (TypeParameter parameter in cls.typeParameters) {
+            for (int i = 0; i < supertype.typeArguments.length; i++) {
+              DartType arg = supertype.typeArguments[i];
+              if (arg is TypeParameterType && arg.parameter == parameter) {
+                typeParameterMatch[parameter] =
+                    superInfo.cls!.typeParameters[i];
+                break;
+              }
+            }
+          }
+        }
+
+        // A class can reuse the Wasm struct of the superclass if it doesn't
+        // declare any Wasm fields of its own. This is the case when three
+        // conditions are met:
+        //   1. All type parameters can reuse a type parameter field of the
+        //      superclass.
+        //   2. The class declares no Dart fields of its own.
+        //   3. The class is not a special class that contains hidden fields.
+        bool canReuseSuperStruct =
+            typeParameterMatch.length == cls.typeParameters.length &&
+                cls.fields.where((f) => f.isInstanceMember).isEmpty &&
+                cls != translator.typedListBaseClass &&
+                cls != translator.typedListClass &&
+                cls != translator.typedListViewClass &&
+                cls != translator.byteDataViewClass;
+        w.StructType struct = canReuseSuperStruct
+            ? superInfo.struct
+            : translator.structType(cls.name, superType: superInfo.struct);
+        info = ClassInfo(
+            cls, nextClassId++, superInfo.depth + 1, struct, superInfo, this,
+            typeParameterMatch: typeParameterMatch);
+
+        // Mark all interfaces as being implemented by this class. This is
+        // needed to calculate representation types.
+        for (Supertype interface in cls.implementedTypes) {
+          ClassInfo? interfaceInfo = translator.classInfo[interface.classNode];
+          while (interfaceInfo != null) {
+            interfaceInfo.implementedBy.add(info);
+            interfaceInfo = interfaceInfo.superInfo;
+          }
+        }
+      }
+      translator.classes.add(info);
+      translator.classInfo[cls] = info;
+      translator.classForHeapType.putIfAbsent(info.struct, () => info!);
+    }
+  }
+
+  void computeRepresentation(ClassInfo info) {
+    info.repr = upperBound(info.implementedBy);
+  }
+
+  void generateFields(ClassInfo info) {
+    ClassInfo? superInfo = info.superInfo;
+    if (superInfo == null) {
+      // Top - add class id field
+      info.addField(w.FieldType(w.NumType.i32), FieldIndex.classId);
+    } else if (info.struct != superInfo.struct) {
+      // Copy fields from superclass
+      for (w.FieldType fieldType in superInfo.struct.fields) {
+        info.addField(fieldType);
+      }
+      if (info.cls!.superclass == null) {
+        // Object - add identity hash code field
+        info.addField(w.FieldType(w.NumType.i32), FieldIndex.identityHash);
+      }
+      // Add fields for type variables
+      for (TypeParameter parameter in info.cls!.typeParameters) {
+        TypeParameter? match = info.typeParameterMatch[parameter];
+        if (match != null) {
+          // Reuse supertype type variable
+          translator.typeParameterIndex[parameter] =
+              translator.typeParameterIndex[match]!;
+        } else {
+          translator.typeParameterIndex[parameter] = info.struct.fields.length;
+          info.addField(typeType);
+        }
+      }
+      // Add fields for Dart instance fields
+      for (Field field in info.cls!.fields) {
+        if (field.isInstanceMember) {
+          w.ValueType wasmType = translator.translateType(field.type);
+          // TODO(askesc): Generalize this check for finer nullability control
+          if (wasmType != w.RefType.data()) {
+            wasmType = wasmType.withNullability(true);
+          }
+          translator.fieldIndex[field] = info.struct.fields.length;
+          info.addField(w.FieldType(wasmType));
+        }
+      }
+    } else {
+      for (TypeParameter parameter in info.cls!.typeParameters) {
+        // Reuse supertype type variable
+        translator.typeParameterIndex[parameter] =
+            translator.typeParameterIndex[info.typeParameterMatch[parameter]]!;
+      }
+    }
+  }
+
+  void collect() {
+    // Create class info and Wasm structs for all classes.
+    initializeTop();
+    for (Library library in translator.component.libraries) {
+      for (Class cls in library.classes) {
+        initialize(cls);
+      }
+    }
+
+    // For each class, compute which Wasm struct should be used for the type of
+    // variables bearing that class as their Dart type. This is the struct
+    // corresponding to the least common supertype of all Dart classes
+    // implementing this class.
+    for (ClassInfo info in translator.classes) {
+      computeRepresentation(info);
+    }
+
+    // Now that the representation types for all classes have been computed,
+    // fill in the types of the fields in the generated Wasm structs.
+    for (ClassInfo info in translator.classes) {
+      generateFields(info);
+    }
+
+    // Add hidden fields of typed_data classes.
+    addTypedDataFields();
+
+    // Validate that all internally used fields have the expected indices.
+    FieldIndex.validate(translator);
+  }
+
+  void addTypedDataFields() {
+    ClassInfo typedListBaseInfo =
+        translator.classInfo[translator.typedListBaseClass]!;
+    typedListBaseInfo.addField(w.FieldType(w.NumType.i32, mutable: false),
+        FieldIndex.typedListBaseLength);
+
+    ClassInfo typedListInfo = translator.classInfo[translator.typedListClass]!;
+    typedListInfo.addField(w.FieldType(w.NumType.i32, mutable: false),
+        FieldIndex.typedListBaseLength);
+    w.RefType bytesArrayType = w.RefType.def(
+        translator.wasmArrayType(w.PackedType.i8, "i8"),
+        nullable: false);
+    typedListInfo.addField(
+        w.FieldType(bytesArrayType, mutable: false), FieldIndex.typedListArray);
+
+    w.RefType typedListType =
+        w.RefType.def(typedListInfo.struct, nullable: false);
+
+    ClassInfo typedListViewInfo =
+        translator.classInfo[translator.typedListViewClass]!;
+    typedListViewInfo.addField(w.FieldType(w.NumType.i32, mutable: false),
+        FieldIndex.typedListBaseLength);
+    typedListViewInfo.addField(w.FieldType(typedListType, mutable: false),
+        FieldIndex.typedListViewTypedData);
+    typedListViewInfo.addField(w.FieldType(w.NumType.i32, mutable: false),
+        FieldIndex.typedListViewOffsetInBytes);
+
+    ClassInfo byteDataViewInfo =
+        translator.classInfo[translator.byteDataViewClass]!;
+    byteDataViewInfo.addField(w.FieldType(w.NumType.i32, mutable: false),
+        FieldIndex.byteDataViewLength);
+    byteDataViewInfo.addField(w.FieldType(typedListType, mutable: false),
+        FieldIndex.byteDataViewTypedData);
+    byteDataViewInfo.addField(w.FieldType(w.NumType.i32, mutable: false),
+        FieldIndex.byteDataViewOffsetInBytes);
+  }
+}
diff --git a/pkg/dart2wasm/lib/closures.dart b/pkg/dart2wasm/lib/closures.dart
new file mode 100644
index 0000000..cde1b4c
--- /dev/null
+++ b/pkg/dart2wasm/lib/closures.dart
@@ -0,0 +1,318 @@
+// 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.
+
+import 'package:dart2wasm/code_generator.dart';
+import 'package:dart2wasm/translator.dart';
+
+import 'package:kernel/ast.dart';
+
+import 'package:wasm_builder/wasm_builder.dart' as w;
+
+/// A local function or function expression.
+class Lambda {
+  final FunctionNode functionNode;
+  final w.DefinedFunction function;
+
+  Lambda(this.functionNode, this.function);
+}
+
+/// The context for one or more closures, containing their captured variables.
+///
+/// Contexts can be nested, corresponding to the scopes covered by the contexts.
+/// Each local function, function expression or loop (`while`, `do`/`while` or
+/// `for`) gives rise to its own context nested inside the context of its
+/// surrounding scope. At runtime, each context has a reference to its parent
+/// context.
+///
+/// Closures corresponding to local functions or function expressions in the
+/// same scope share the same context. Thus, a closure can potentially keep more
+/// values alive than the ones captured by the closure itself.
+///
+/// A context may be empty (containing no captured variables), in which case it
+/// is skipped in the context parent chain and never allocated. A context can
+/// also be skipped if it only contains variables that are not in scope for the
+/// child context (and its descendants).
+class Context {
+  /// The node containing the scope covered by the context. This is either a
+  /// [FunctionNode] (for members, local functions and function expressions),
+  /// a [ForStatement], a [DoStatement] or a [WhileStatement].
+  final TreeNode owner;
+
+  /// The parent of this context, corresponding to the lexically enclosing
+  /// owner. This is null if the context is a member context, or if all contexts
+  /// in the parent chain are skipped.
+  final Context? parent;
+
+  /// The variables captured by this context.
+  final List<VariableDeclaration> variables = [];
+
+  /// Whether this context contains a captured `this`. Only member contexts can.
+  bool containsThis = false;
+
+  /// The Wasm struct representing this context at runtime.
+  late final w.StructType struct;
+
+  /// The local variable currently pointing to this context. Used during code
+  /// generation.
+  late w.Local currentLocal;
+
+  bool get isEmpty => variables.isEmpty && !containsThis;
+
+  int get parentFieldIndex {
+    assert(parent != null);
+    return 0;
+  }
+
+  int get thisFieldIndex {
+    assert(containsThis);
+    return 0;
+  }
+
+  Context(this.owner, this.parent);
+}
+
+/// A captured variable.
+class Capture {
+  final VariableDeclaration variable;
+  late final Context context;
+  late final int fieldIndex;
+  bool written = false;
+
+  Capture(this.variable);
+
+  w.ValueType get type => context.struct.fields[fieldIndex].type.unpacked;
+}
+
+/// Compiler passes to find all captured variables and construct the context
+/// tree for a member.
+class Closures {
+  final CodeGenerator codeGen;
+  final Map<VariableDeclaration, Capture> captures = {};
+  bool isThisCaptured = false;
+  final Map<FunctionNode, Lambda> lambdas = {};
+  final Map<TreeNode, Context> contexts = {};
+  final Set<FunctionDeclaration> closurizedFunctions = {};
+
+  Closures(this.codeGen);
+
+  Translator get translator => codeGen.translator;
+
+  void findCaptures(Member member) {
+    var find = CaptureFinder(this, member);
+    if (member is Constructor) {
+      Class cls = member.enclosingClass;
+      for (Field field in cls.fields) {
+        if (field.isInstanceMember && field.initializer != null) {
+          field.initializer!.accept(find);
+        }
+      }
+    }
+    member.accept(find);
+  }
+
+  void collectContexts(TreeNode node, {TreeNode? container}) {
+    if (captures.isNotEmpty || isThisCaptured) {
+      node.accept(ContextCollector(this, container));
+    }
+  }
+
+  void buildContexts() {
+    // Make struct definitions
+    for (Context context in contexts.values) {
+      if (!context.isEmpty) {
+        context.struct = translator.structType("<context>");
+      }
+    }
+
+    // Build object layouts
+    for (Context context in contexts.values) {
+      if (!context.isEmpty) {
+        w.StructType struct = context.struct;
+        if (context.parent != null) {
+          assert(!context.containsThis);
+          struct.fields.add(w.FieldType(
+              w.RefType.def(context.parent!.struct, nullable: true)));
+        }
+        if (context.containsThis) {
+          struct.fields.add(w.FieldType(
+              codeGen.preciseThisLocal!.type.withNullability(true)));
+        }
+        for (VariableDeclaration variable in context.variables) {
+          int index = struct.fields.length;
+          struct.fields.add(w.FieldType(
+              translator.translateType(variable.type).withNullability(true)));
+          captures[variable]!.fieldIndex = index;
+        }
+      }
+    }
+  }
+}
+
+class CaptureFinder extends RecursiveVisitor {
+  final Closures closures;
+  final Member member;
+  final Map<VariableDeclaration, int> variableDepth = {};
+  int depth = 0;
+
+  CaptureFinder(this.closures, this.member);
+
+  Translator get translator => closures.translator;
+
+  @override
+  void visitAssertStatement(AssertStatement node) {}
+
+  @override
+  void visitVariableDeclaration(VariableDeclaration node) {
+    if (depth > 0) {
+      variableDepth[node] = depth;
+    }
+    super.visitVariableDeclaration(node);
+  }
+
+  void _visitVariableUse(VariableDeclaration variable) {
+    int declDepth = variableDepth[variable] ?? 0;
+    assert(declDepth <= depth);
+    if (declDepth < depth) {
+      closures.captures[variable] = Capture(variable);
+    } else if (variable.parent is FunctionDeclaration) {
+      closures.closurizedFunctions.add(variable.parent as FunctionDeclaration);
+    }
+  }
+
+  @override
+  void visitVariableGet(VariableGet node) {
+    _visitVariableUse(node.variable);
+    super.visitVariableGet(node);
+  }
+
+  @override
+  void visitVariableSet(VariableSet node) {
+    _visitVariableUse(node.variable);
+    super.visitVariableSet(node);
+  }
+
+  void _visitThis() {
+    if (depth > 0) {
+      closures.isThisCaptured = true;
+    }
+  }
+
+  @override
+  void visitThisExpression(ThisExpression node) {
+    _visitThis();
+  }
+
+  @override
+  void visitSuperMethodInvocation(SuperMethodInvocation node) {
+    _visitThis();
+    super.visitSuperMethodInvocation(node);
+  }
+
+  @override
+  void visitTypeParameterType(TypeParameterType node) {
+    if (node.parameter.parent == member.enclosingClass) {
+      _visitThis();
+    }
+  }
+
+  void _visitLambda(FunctionNode node) {
+    if (node.positionalParameters.length != node.requiredParameterCount ||
+        node.namedParameters.isNotEmpty) {
+      throw "Not supported: Optional parameters for "
+          "function expression or local function at ${node.location}";
+    }
+    int parameterCount = node.requiredParameterCount;
+    w.FunctionType type = translator.closureFunctionType(parameterCount);
+    w.DefinedFunction function =
+        translator.m.addFunction(type, "$member (closure)");
+    closures.lambdas[node] = Lambda(node, function);
+
+    depth++;
+    node.visitChildren(this);
+    depth--;
+  }
+
+  @override
+  void visitFunctionExpression(FunctionExpression node) {
+    _visitLambda(node.function);
+  }
+
+  @override
+  void visitFunctionDeclaration(FunctionDeclaration node) {
+    // Variable is in outer scope
+    node.variable.accept(this);
+    _visitLambda(node.function);
+  }
+}
+
+class ContextCollector extends RecursiveVisitor {
+  final Closures closures;
+  Context? currentContext;
+
+  ContextCollector(this.closures, TreeNode? container) {
+    if (container != null) {
+      currentContext = closures.contexts[container]!;
+    }
+  }
+
+  @override
+  void visitAssertStatement(AssertStatement node) {}
+
+  void _newContext(TreeNode node) {
+    bool outerMost = currentContext == null;
+    Context? oldContext = currentContext;
+    Context? parent = currentContext;
+    while (parent != null && parent.isEmpty) parent = parent.parent;
+    currentContext = Context(node, parent);
+    if (closures.isThisCaptured && outerMost) {
+      currentContext!.containsThis = true;
+    }
+    closures.contexts[node] = currentContext!;
+    node.visitChildren(this);
+    currentContext = oldContext;
+  }
+
+  @override
+  void visitConstructor(Constructor node) {
+    node.function.accept(this);
+    currentContext = closures.contexts[node.function]!;
+    visitList(node.initializers, this);
+  }
+
+  @override
+  void visitFunctionNode(FunctionNode node) {
+    _newContext(node);
+  }
+
+  @override
+  void visitWhileStatement(WhileStatement node) {
+    _newContext(node);
+  }
+
+  @override
+  void visitDoStatement(DoStatement node) {
+    _newContext(node);
+  }
+
+  @override
+  void visitForStatement(ForStatement node) {
+    _newContext(node);
+  }
+
+  @override
+  void visitVariableDeclaration(VariableDeclaration node) {
+    Capture? capture = closures.captures[node];
+    if (capture != null) {
+      currentContext!.variables.add(node);
+      capture.context = currentContext!;
+    }
+    super.visitVariableDeclaration(node);
+  }
+
+  @override
+  void visitVariableSet(VariableSet node) {
+    closures.captures[node.variable]?.written = true;
+    super.visitVariableSet(node);
+  }
+}
diff --git a/pkg/dart2wasm/lib/code_generator.dart b/pkg/dart2wasm/lib/code_generator.dart
new file mode 100644
index 0000000..d4ef423
--- /dev/null
+++ b/pkg/dart2wasm/lib/code_generator.dart
@@ -0,0 +1,1940 @@
+// 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.
+
+import 'package:dart2wasm/class_info.dart';
+import 'package:dart2wasm/closures.dart';
+import 'package:dart2wasm/dispatch_table.dart';
+import 'package:dart2wasm/intrinsics.dart';
+import 'package:dart2wasm/param_info.dart';
+import 'package:dart2wasm/reference_extensions.dart';
+import 'package:dart2wasm/translator.dart';
+
+import 'package:kernel/ast.dart';
+import 'package:kernel/type_environment.dart';
+
+import 'package:wasm_builder/wasm_builder.dart' as w;
+
+/// Main code generator for member bodies.
+///
+/// The [generate] method first collects all local functions and function
+/// expressions in the body and then generates code for the body. Code for the
+/// local functions and function expressions must be generated separately by
+/// calling the [generateLambda] method on all lambdas in [closures].
+///
+/// A new [CodeGenerator] object must be created for each new member or lambda.
+///
+/// Every visitor method for an expression takes in the Wasm type that it is
+/// expected to leave on the stack (or the special [voidMarker] to indicate that
+/// it should leave nothing). It returns what it actually left on the stack. The
+/// code generation for every expression or subexpression is done via the [wrap]
+/// method, which emits appropriate conversion code if the produced type is not
+/// a subtype of the expected type.
+class CodeGenerator extends ExpressionVisitor1<w.ValueType, w.ValueType>
+    implements InitializerVisitor<void>, StatementVisitor<void> {
+  final Translator translator;
+  final w.DefinedFunction function;
+  final Reference reference;
+  late final List<w.Local> paramLocals;
+  final w.Label? returnLabel;
+
+  late final Intrinsifier intrinsifier;
+  late final StaticTypeContext typeContext;
+  late final w.Instructions b;
+
+  late final Closures closures;
+
+  final Map<VariableDeclaration, w.Local> locals = {};
+  w.Local? thisLocal;
+  w.Local? preciseThisLocal;
+  final Map<TypeParameter, w.Local> typeLocals = {};
+  final List<Statement> finalizers = [];
+  final Map<LabeledStatement, w.Label> labels = {};
+  final Map<SwitchCase, w.Label> switchLabels = {};
+
+  /// Create a code generator for a member or one of its lambdas.
+  ///
+  /// The [paramLocals] and [returnLabel] parameters can be used to generate
+  /// code for an inlined function by specifying the locals containing the
+  /// parameters (instead of the function inputs) and the label to jump to on
+  /// return (instead of emitting a `return` instruction).
+  CodeGenerator(this.translator, this.function, this.reference,
+      {List<w.Local>? paramLocals, this.returnLabel}) {
+    this.paramLocals = paramLocals ?? function.locals;
+    intrinsifier = Intrinsifier(this);
+    typeContext = StaticTypeContext(member, translator.typeEnvironment);
+    b = function.body;
+  }
+
+  Member get member => reference.asMember;
+
+  w.ValueType get returnType => translator
+      .outputOrVoid(returnLabel?.targetTypes ?? function.type.outputs);
+
+  TranslatorOptions get options => translator.options;
+
+  w.ValueType get voidMarker => translator.voidMarker;
+
+  w.ValueType translateType(DartType type) => translator.translateType(type);
+
+  w.Local addLocal(w.ValueType type) {
+    return function.addLocal(translator.typeForLocal(type));
+  }
+
+  DartType dartTypeOf(Expression exp) {
+    return exp.getStaticType(typeContext);
+  }
+
+  void _unimplemented(
+      TreeNode node, Object message, List<w.ValueType> expectedTypes) {
+    final text = "Not implemented: $message at ${node.location}";
+    print(text);
+    b.comment(text);
+    b.block(const [], expectedTypes);
+    b.unreachable();
+    b.end();
+  }
+
+  @override
+  void defaultInitializer(Initializer node) {
+    _unimplemented(node, node.runtimeType, const []);
+  }
+
+  @override
+  w.ValueType defaultExpression(Expression node, w.ValueType expectedType) {
+    _unimplemented(node, node.runtimeType, [expectedType]);
+    return expectedType;
+  }
+
+  @override
+  void defaultStatement(Statement node) {
+    _unimplemented(node, node.runtimeType, const []);
+  }
+
+  /// Generate code for the body of the member.
+  void generate() {
+    closures = Closures(this);
+
+    Member member = this.member;
+
+    if (reference.isTearOffReference) {
+      // Tear-off getter
+      w.DefinedFunction closureFunction =
+          translator.getTearOffFunction(member as Procedure);
+
+      int parameterCount = member.function.requiredParameterCount;
+      w.DefinedGlobal global = translator.makeFunctionRef(closureFunction);
+
+      ClassInfo info = translator.classInfo[translator.functionClass]!;
+      translator.functions.allocateClass(info.classId);
+
+      b.i32_const(info.classId);
+      b.i32_const(initialIdentityHash);
+      b.local_get(paramLocals[0]);
+      b.global_get(global);
+      translator.struct_new(b, parameterCount);
+      b.end();
+      return;
+    }
+
+    if (intrinsifier.generateMemberIntrinsic(
+        reference, function, paramLocals, returnLabel)) {
+      b.end();
+      return;
+    }
+
+    if (member.isExternal) {
+      final text =
+          "Unimplemented external member $member at ${member.location}";
+      print(text);
+      b.comment(text);
+      b.unreachable();
+      b.end();
+      return;
+    }
+
+    if (member is Field) {
+      if (member.isStatic) {
+        // Static field initializer function
+        assert(reference == member.fieldReference);
+        closures.findCaptures(member);
+        closures.collectContexts(member);
+        closures.buildContexts();
+
+        w.Global global = translator.globals.getGlobal(member);
+        w.Global? flag = translator.globals.getGlobalInitializedFlag(member);
+        wrap(member.initializer!, global.type.type);
+        b.global_set(global);
+        if (flag != null) {
+          b.i32_const(1);
+          b.global_set(flag);
+        }
+        b.global_get(global);
+        translator.convertType(
+            function, global.type.type, function.type.outputs.single);
+        b.end();
+        return;
+      }
+
+      // Implicit getter or setter
+      w.StructType struct =
+          translator.classInfo[member.enclosingClass!]!.struct;
+      int fieldIndex = translator.fieldIndex[member]!;
+      w.ValueType fieldType = struct.fields[fieldIndex].type.unpacked;
+
+      void getThis() {
+        w.Local thisLocal = paramLocals[0];
+        w.RefType structType = w.RefType.def(struct, nullable: true);
+        b.local_get(thisLocal);
+        translator.convertType(function, thisLocal.type, structType);
+      }
+
+      if (reference.isImplicitGetter) {
+        // Implicit getter
+        getThis();
+        b.struct_get(struct, fieldIndex);
+        translator.convertType(function, fieldType, returnType);
+      } else {
+        // Implicit setter
+        w.Local valueLocal = paramLocals[1];
+        getThis();
+        b.local_get(valueLocal);
+        translator.convertType(function, valueLocal.type, fieldType);
+        b.struct_set(struct, fieldIndex);
+      }
+      b.end();
+      return;
+    }
+
+    ParameterInfo paramInfo = translator.paramInfoFor(reference);
+    bool hasThis = member.isInstanceMember || member is Constructor;
+    int typeParameterOffset = hasThis ? 1 : 0;
+    int implicitParams = typeParameterOffset + paramInfo.typeParamCount;
+    List<VariableDeclaration> positional =
+        member.function!.positionalParameters;
+    for (int i = 0; i < positional.length; i++) {
+      locals[positional[i]] = paramLocals[implicitParams + i];
+    }
+    List<VariableDeclaration> named = member.function!.namedParameters;
+    for (var param in named) {
+      locals[param] =
+          paramLocals[implicitParams + paramInfo.nameIndex[param.name]!];
+    }
+    List<TypeParameter> typeParameters = member is Constructor
+        ? member.enclosingClass.typeParameters
+        : member.function!.typeParameters;
+    for (int i = 0; i < typeParameters.length; i++) {
+      typeLocals[typeParameters[i]] = paramLocals[typeParameterOffset + i];
+    }
+
+    closures.findCaptures(member);
+
+    if (hasThis) {
+      Class cls = member.enclosingClass!;
+      ClassInfo info = translator.classInfo[cls]!;
+      thisLocal = paramLocals[0];
+      w.RefType thisType = info.nonNullableType;
+      if (translator.needsConversion(paramLocals[0].type, thisType)) {
+        preciseThisLocal = addLocal(thisType);
+        b.local_get(paramLocals[0]);
+        translator.ref_cast(b, info);
+        b.local_set(preciseThisLocal!);
+      } else {
+        preciseThisLocal = paramLocals[0];
+      }
+    }
+
+    closures.collectContexts(member);
+    if (member is Constructor) {
+      for (Field field in member.enclosingClass.fields) {
+        if (field.isInstanceMember && field.initializer != null) {
+          closures.collectContexts(field.initializer!,
+              container: member.function);
+        }
+      }
+    }
+    closures.buildContexts();
+
+    allocateContext(member.function!);
+    captureParameters();
+
+    if (member is Constructor) {
+      Class cls = member.enclosingClass;
+      ClassInfo info = translator.classInfo[cls]!;
+      for (TypeParameter typeParam in cls.typeParameters) {
+        b.local_get(thisLocal!);
+        b.local_get(typeLocals[typeParam]!);
+        b.struct_set(info.struct, translator.typeParameterIndex[typeParam]!);
+      }
+      for (Field field in cls.fields) {
+        if (field.isInstanceMember && field.initializer != null) {
+          int fieldIndex = translator.fieldIndex[field]!;
+          b.local_get(thisLocal!);
+          wrap(
+              field.initializer!, info.struct.fields[fieldIndex].type.unpacked);
+          b.struct_set(info.struct, fieldIndex);
+        }
+      }
+      for (Initializer initializer in member.initializers) {
+        initializer.accept(this);
+      }
+    }
+
+    member.function!.body?.accept(this);
+    _implicitReturn();
+    b.end();
+  }
+
+  /// Generate code for the body of a lambda.
+  void generateLambda(Lambda lambda, Closures closures) {
+    this.closures = closures;
+
+    final int implicitParams = 1;
+    List<VariableDeclaration> positional =
+        lambda.functionNode.positionalParameters;
+    for (int i = 0; i < positional.length; i++) {
+      locals[positional[i]] = paramLocals[implicitParams + i];
+    }
+
+    Context? context = closures.contexts[lambda.functionNode]?.parent;
+    if (context != null) {
+      b.local_get(paramLocals[0]);
+      translator.ref_cast(b, context.struct);
+      while (true) {
+        w.Local contextLocal =
+            addLocal(w.RefType.def(context!.struct, nullable: false));
+        context.currentLocal = contextLocal;
+        if (context.parent != null || context.containsThis) {
+          b.local_tee(contextLocal);
+        } else {
+          b.local_set(contextLocal);
+        }
+        if (context.parent == null) break;
+
+        b.struct_get(context.struct, context.parentFieldIndex);
+        if (options.localNullability) {
+          b.ref_as_non_null();
+        }
+        context = context.parent!;
+      }
+      if (context.containsThis) {
+        thisLocal = addLocal(
+            context.struct.fields[context.thisFieldIndex].type.unpacked);
+        preciseThisLocal = thisLocal;
+        b.struct_get(context.struct, context.thisFieldIndex);
+        b.local_set(thisLocal!);
+      }
+    }
+    allocateContext(lambda.functionNode);
+    captureParameters();
+
+    lambda.functionNode.body!.accept(this);
+    _implicitReturn();
+    b.end();
+  }
+
+  void _implicitReturn() {
+    if (function.type.outputs.length > 0) {
+      w.ValueType returnType = function.type.outputs[0];
+      if (returnType is w.RefType && returnType.nullable) {
+        // Dart body may have an implicit return null.
+        b.ref_null(returnType.heapType);
+      } else {
+        // This point is unreachable, but the Wasm validator still expects the
+        // stack to contain a value matching the Wasm function return type.
+        b.block(const [], function.type.outputs);
+        b.comment("Unreachable implicit return");
+        b.unreachable();
+        b.end();
+      }
+    }
+  }
+
+  void allocateContext(TreeNode node) {
+    Context? context = closures.contexts[node];
+    if (context != null && !context.isEmpty) {
+      w.Local contextLocal =
+          addLocal(w.RefType.def(context.struct, nullable: false));
+      context.currentLocal = contextLocal;
+      translator.struct_new_default(b, context.struct);
+      b.local_set(contextLocal);
+      if (context.containsThis) {
+        b.local_get(contextLocal);
+        b.local_get(preciseThisLocal!);
+        b.struct_set(context.struct, context.thisFieldIndex);
+      }
+      if (context.parent != null) {
+        w.Local parentLocal = context.parent!.currentLocal;
+        b.local_get(contextLocal);
+        b.local_get(parentLocal);
+        b.struct_set(context.struct, context.parentFieldIndex);
+      }
+    }
+  }
+
+  void captureParameters() {
+    locals.forEach((variable, local) {
+      Capture? capture = closures.captures[variable];
+      if (capture != null) {
+        b.local_get(capture.context.currentLocal);
+        b.local_get(local);
+        translator.convertType(function, local.type, capture.type);
+        b.struct_set(capture.context.struct, capture.fieldIndex);
+      }
+    });
+  }
+
+  /// Generates code for an expression plus conversion code to convert the
+  /// result to the expected type if needed. All expression code generation goes
+  /// through this method.
+  w.ValueType wrap(Expression node, w.ValueType expectedType) {
+    w.ValueType resultType = node.accept1(this, expectedType);
+    translator.convertType(function, resultType, expectedType);
+    return expectedType;
+  }
+
+  w.ValueType _call(Reference target) {
+    w.BaseFunction targetFunction = translator.functions.getFunction(target);
+    if (translator.shouldInline(target)) {
+      List<w.Local> inlinedLocals =
+          targetFunction.type.inputs.map((t) => addLocal(t)).toList();
+      for (w.Local local in inlinedLocals.reversed) {
+        b.local_set(local);
+      }
+      w.Label block = b.block(const [], targetFunction.type.outputs);
+      b.comment("Inlined ${target.asMember}");
+      CodeGenerator(translator, function, target,
+              paramLocals: inlinedLocals, returnLabel: block)
+          .generate();
+    } else {
+      String access =
+          target.isGetter ? "get" : (target.isSetter ? "set" : "call");
+      b.comment("Direct $access of '${target.asMember}'");
+      b.call(targetFunction);
+    }
+    return translator.outputOrVoid(targetFunction.type.outputs);
+  }
+
+  @override
+  void visitInvalidInitializer(InvalidInitializer node) {}
+
+  @override
+  void visitAssertInitializer(AssertInitializer node) {}
+
+  @override
+  void visitLocalInitializer(LocalInitializer node) {
+    node.variable.accept(this);
+  }
+
+  @override
+  void visitFieldInitializer(FieldInitializer node) {
+    Class cls = (node.parent as Constructor).enclosingClass;
+    w.StructType struct = translator.classInfo[cls]!.struct;
+    int fieldIndex = translator.fieldIndex[node.field]!;
+
+    b.local_get(thisLocal!);
+    wrap(node.value, struct.fields[fieldIndex].type.unpacked);
+    b.struct_set(struct, fieldIndex);
+  }
+
+  @override
+  void visitRedirectingInitializer(RedirectingInitializer node) {
+    Class cls = (node.parent as Constructor).enclosingClass;
+    b.local_get(thisLocal!);
+    if (options.parameterNullability && thisLocal!.type.nullable) {
+      b.ref_as_non_null();
+    }
+    for (TypeParameter typeParam in cls.typeParameters) {
+      _makeType(TypeParameterType(typeParam, Nullability.nonNullable), node);
+    }
+    _visitArguments(node.arguments, node.targetReference, 1);
+    _call(node.targetReference);
+  }
+
+  @override
+  void visitSuperInitializer(SuperInitializer node) {
+    Supertype? supertype =
+        (node.parent as Constructor).enclosingClass.supertype;
+    if (supertype?.classNode.superclass == null) {
+      return;
+    }
+    b.local_get(thisLocal!);
+    if (options.parameterNullability && thisLocal!.type.nullable) {
+      b.ref_as_non_null();
+    }
+    for (DartType typeArg in supertype!.typeArguments) {
+      _makeType(typeArg, node);
+    }
+    _visitArguments(node.arguments, node.targetReference,
+        1 + supertype.typeArguments.length);
+    _call(node.targetReference);
+  }
+
+  @override
+  void visitBlock(Block node) {
+    for (Statement statement in node.statements) {
+      statement.accept(this);
+    }
+  }
+
+  @override
+  void visitLabeledStatement(LabeledStatement node) {
+    w.Label label = b.block();
+    labels[node] = label;
+    node.body.accept(this);
+    labels.remove(node);
+    b.end();
+  }
+
+  @override
+  void visitBreakStatement(BreakStatement node) {
+    b.br(labels[node.target]!);
+  }
+
+  @override
+  void visitVariableDeclaration(VariableDeclaration node) {
+    if (node.type is VoidType) {
+      if (node.initializer != null) {
+        wrap(node.initializer!, voidMarker);
+      }
+      return;
+    }
+    w.ValueType type = translateType(node.type);
+    w.Local? local;
+    Capture? capture = closures.captures[node];
+    if (capture == null || !capture.written) {
+      local = addLocal(type);
+      locals[node] = local;
+    }
+    if (node.initializer != null) {
+      if (capture != null) {
+        w.ValueType expectedType = capture.written ? capture.type : local!.type;
+        b.local_get(capture.context.currentLocal);
+        wrap(node.initializer!, expectedType);
+        if (!capture.written) {
+          b.local_tee(local!);
+        }
+        b.struct_set(capture.context.struct, capture.fieldIndex);
+      } else {
+        wrap(node.initializer!, local!.type);
+        b.local_set(local);
+      }
+    } else if (local != null && !local.type.defaultable) {
+      // Uninitialized variable
+      translator.globals.instantiateDummyValue(b, local.type);
+      b.local_set(local);
+    }
+  }
+
+  @override
+  void visitEmptyStatement(EmptyStatement node) {}
+
+  @override
+  void visitAssertStatement(AssertStatement node) {}
+
+  @override
+  void visitAssertBlock(AssertBlock node) {}
+
+  @override
+  void visitTryCatch(TryCatch node) {
+    // TODO(joshualitt): Include catches
+    node.body.accept(this);
+  }
+
+  @override
+  void visitTryFinally(TryFinally node) {
+    finalizers.add(node.finalizer);
+    node.body.accept(this);
+    finalizers.removeLast().accept(this);
+  }
+
+  @override
+  void visitExpressionStatement(ExpressionStatement node) {
+    wrap(node.expression, voidMarker);
+  }
+
+  bool _hasLogicalOperator(Expression condition) {
+    while (condition is Not) condition = condition.operand;
+    return condition is LogicalExpression;
+  }
+
+  void _branchIf(Expression? condition, w.Label target,
+      {required bool negated}) {
+    if (condition == null) {
+      if (!negated) b.br(target);
+      return;
+    }
+    while (condition is Not) {
+      negated = !negated;
+      condition = condition.operand;
+    }
+    if (condition is LogicalExpression) {
+      bool isConjunctive =
+          (condition.operatorEnum == LogicalExpressionOperator.AND) ^ negated;
+      if (isConjunctive) {
+        w.Label conditionBlock = b.block();
+        _branchIf(condition.left, conditionBlock, negated: !negated);
+        _branchIf(condition.right, target, negated: negated);
+        b.end();
+      } else {
+        _branchIf(condition.left, target, negated: negated);
+        _branchIf(condition.right, target, negated: negated);
+      }
+    } else {
+      wrap(condition!, w.NumType.i32);
+      if (negated) {
+        b.i32_eqz();
+      }
+      b.br_if(target);
+    }
+  }
+
+  void _conditional(Expression condition, void then(), void otherwise()?,
+      List<w.ValueType> result) {
+    if (!_hasLogicalOperator(condition)) {
+      // Simple condition
+      wrap(condition, w.NumType.i32);
+      b.if_(const [], result);
+      then();
+      if (otherwise != null) {
+        b.else_();
+        otherwise();
+      }
+      b.end();
+    } else {
+      // Complex condition
+      w.Label ifBlock = b.block(const [], result);
+      if (otherwise != null) {
+        w.Label elseBlock = b.block();
+        _branchIf(condition, elseBlock, negated: true);
+        then();
+        b.br(ifBlock);
+        b.end();
+        otherwise();
+      } else {
+        _branchIf(condition, ifBlock, negated: true);
+        then();
+      }
+      b.end();
+    }
+  }
+
+  @override
+  void visitIfStatement(IfStatement node) {
+    _conditional(
+        node.condition,
+        () => node.then.accept(this),
+        node.otherwise != null ? () => node.otherwise!.accept(this) : null,
+        const []);
+  }
+
+  @override
+  void visitDoStatement(DoStatement node) {
+    w.Label loop = b.loop();
+    allocateContext(node);
+    node.body.accept(this);
+    _branchIf(node.condition, loop, negated: false);
+    b.end();
+  }
+
+  @override
+  void visitWhileStatement(WhileStatement node) {
+    w.Label block = b.block();
+    w.Label loop = b.loop();
+    _branchIf(node.condition, block, negated: true);
+    allocateContext(node);
+    node.body.accept(this);
+    b.br(loop);
+    b.end();
+    b.end();
+  }
+
+  @override
+  void visitForStatement(ForStatement node) {
+    Context? context = closures.contexts[node];
+    allocateContext(node);
+    for (VariableDeclaration variable in node.variables) {
+      variable.accept(this);
+    }
+    w.Label block = b.block();
+    w.Label loop = b.loop();
+    _branchIf(node.condition, block, negated: true);
+    node.body.accept(this);
+    if (node.variables.any((v) => closures.captures.containsKey(v))) {
+      w.Local oldContext = context!.currentLocal;
+      allocateContext(node);
+      w.Local newContext = context.currentLocal;
+      for (VariableDeclaration variable in node.variables) {
+        Capture? capture = closures.captures[variable];
+        if (capture != null) {
+          b.local_get(oldContext);
+          b.struct_get(context.struct, capture.fieldIndex);
+          b.local_get(newContext);
+          b.struct_set(context.struct, capture.fieldIndex);
+        }
+      }
+    } else {
+      allocateContext(node);
+    }
+    for (Expression update in node.updates) {
+      wrap(update, voidMarker);
+    }
+    b.br(loop);
+    b.end();
+    b.end();
+  }
+
+  @override
+  void visitForInStatement(ForInStatement node) {
+    throw "ForInStatement should have been desugared: $node";
+  }
+
+  @override
+  void visitReturnStatement(ReturnStatement node) {
+    Expression? expression = node.expression;
+    if (expression != null) {
+      wrap(expression, returnType);
+    } else {
+      translator.convertType(function, voidMarker, returnType);
+    }
+    for (Statement finalizer in finalizers.reversed) {
+      finalizer.accept(this);
+    }
+    if (returnLabel != null) {
+      b.br(returnLabel!);
+    } else {
+      b.return_();
+    }
+  }
+
+  @override
+  void visitSwitchStatement(SwitchStatement node) {
+    bool check<L extends Expression, C extends Constant>() =>
+        node.cases.expand((c) => c.expressions).every((e) =>
+            e is L ||
+            e is NullLiteral ||
+            e is ConstantExpression &&
+                (e.constant is C || e.constant is NullConstant));
+
+    // Identify kind of switch
+    w.ValueType valueType;
+    w.ValueType nullableType;
+    void Function() compare;
+    if (check<BoolLiteral, BoolConstant>()) {
+      // bool switch
+      valueType = w.NumType.i32;
+      nullableType =
+          translator.classInfo[translator.boxedBoolClass]!.nullableType;
+      compare = () => b.i32_eq();
+    } else if (check<IntLiteral, IntConstant>()) {
+      // int switch
+      valueType = w.NumType.i64;
+      nullableType =
+          translator.classInfo[translator.boxedIntClass]!.nullableType;
+      compare = () => b.i64_eq();
+    } else if (check<StringLiteral, StringConstant>()) {
+      // String switch
+      valueType =
+          translator.classInfo[translator.stringBaseClass]!.nonNullableType;
+      nullableType = valueType.withNullability(true);
+      compare = () => _call(translator.stringEquals.reference);
+    } else {
+      // Object switch
+      assert(check<InvalidExpression, InstanceConstant>());
+      valueType = w.RefType.eq(nullable: false);
+      nullableType = w.RefType.eq(nullable: true);
+      compare = () => b.ref_eq();
+    }
+    w.Local valueLocal = addLocal(valueType);
+
+    // Special cases
+    SwitchCase? defaultCase = node.cases
+        .cast<SwitchCase?>()
+        .firstWhere((c) => c!.isDefault, orElse: () => null);
+    SwitchCase? nullCase = node.cases.cast<SwitchCase?>().firstWhere(
+        (c) => c!.expressions.any((e) =>
+            e is NullLiteral ||
+            e is ConstantExpression && e.constant is NullConstant),
+        orElse: () => null);
+
+    // Set up blocks, in reverse order of cases so they end in forward order
+    w.Label doneLabel = b.block();
+    for (SwitchCase c in node.cases.reversed) {
+      switchLabels[c] = b.block();
+    }
+
+    // Compute value and handle null
+    bool isNullable = dartTypeOf(node.expression).isPotentiallyNullable;
+    if (isNullable) {
+      w.Label nullLabel = nullCase != null
+          ? switchLabels[nullCase]!
+          : defaultCase != null
+              ? switchLabels[defaultCase]!
+              : doneLabel;
+      wrap(node.expression, nullableType);
+      b.br_on_null(nullLabel);
+      translator.convertType(
+          function, nullableType.withNullability(false), valueType);
+    } else {
+      assert(nullCase == null);
+      wrap(node.expression, valueType);
+    }
+    b.local_set(valueLocal);
+
+    // Compare against all case values
+    for (SwitchCase c in node.cases) {
+      for (Expression exp in c.expressions) {
+        if (exp is NullLiteral ||
+            exp is ConstantExpression && exp.constant is NullConstant) {
+          // Null already checked, skip
+        } else {
+          wrap(exp, valueType);
+          b.local_get(valueLocal);
+          translator.convertType(function, valueLocal.type, valueType);
+          compare();
+          b.br_if(switchLabels[c]!);
+        }
+      }
+    }
+    w.Label defaultLabel =
+        defaultCase != null ? switchLabels[defaultCase]! : doneLabel;
+    b.br(defaultLabel);
+
+    // Emit case bodies
+    for (SwitchCase c in node.cases) {
+      switchLabels.remove(c);
+      b.end();
+      c.body.accept(this);
+      b.br(doneLabel);
+    }
+    b.end();
+  }
+
+  @override
+  void visitContinueSwitchStatement(ContinueSwitchStatement node) {
+    w.Label? label = switchLabels[node.target];
+    if (label != null) {
+      b.br(label);
+    } else {
+      throw "Not supported: Backward jump to switch case at ${node.location}";
+    }
+  }
+
+  @override
+  void visitYieldStatement(YieldStatement node) => defaultStatement(node);
+
+  @override
+  w.ValueType visitBlockExpression(
+      BlockExpression node, w.ValueType expectedType) {
+    node.body.accept(this);
+    return wrap(node.value, expectedType);
+  }
+
+  @override
+  w.ValueType visitLet(Let node, w.ValueType expectedType) {
+    node.variable.accept(this);
+    return wrap(node.body, expectedType);
+  }
+
+  @override
+  w.ValueType visitThisExpression(
+      ThisExpression node, w.ValueType expectedType) {
+    return _visitThis(expectedType);
+  }
+
+  w.ValueType _visitThis(w.ValueType expectedType) {
+    w.ValueType thisType = thisLocal!.type.withNullability(false);
+    w.ValueType preciseThisType = preciseThisLocal!.type.withNullability(false);
+    if (!thisType.isSubtypeOf(expectedType) &&
+        preciseThisType.isSubtypeOf(expectedType)) {
+      b.local_get(preciseThisLocal!);
+      return preciseThisLocal!.type;
+    } else {
+      b.local_get(thisLocal!);
+      return thisLocal!.type;
+    }
+  }
+
+  @override
+  w.ValueType visitConstructorInvocation(
+      ConstructorInvocation node, w.ValueType expectedType) {
+    ClassInfo info = translator.classInfo[node.target.enclosingClass]!;
+    translator.functions.allocateClass(info.classId);
+    w.Local temp = addLocal(info.nonNullableType);
+    translator.struct_new_default(b, info);
+    b.local_tee(temp);
+    b.local_get(temp);
+    b.i32_const(info.classId);
+    b.struct_set(info.struct, FieldIndex.classId);
+    if (options.parameterNullability && temp.type.nullable) {
+      b.ref_as_non_null();
+    }
+    _visitArguments(node.arguments, node.targetReference, 1);
+    _call(node.targetReference);
+    if (expectedType != voidMarker) {
+      b.local_get(temp);
+      return temp.type;
+    } else {
+      return voidMarker;
+    }
+  }
+
+  @override
+  w.ValueType visitStaticInvocation(
+      StaticInvocation node, w.ValueType expectedType) {
+    w.ValueType? intrinsicResult = intrinsifier.generateStaticIntrinsic(node);
+    if (intrinsicResult != null) return intrinsicResult;
+    _visitArguments(node.arguments, node.targetReference, 0);
+    return _call(node.targetReference);
+  }
+
+  Member _lookupSuperTarget(Member interfaceTarget, {required bool setter}) {
+    return translator.hierarchy.getDispatchTarget(
+        member.enclosingClass!.superclass!, interfaceTarget.name,
+        setter: setter)!;
+  }
+
+  @override
+  w.ValueType visitSuperMethodInvocation(
+      SuperMethodInvocation node, w.ValueType expectedType) {
+    Reference target =
+        _lookupSuperTarget(node.interfaceTarget!, setter: false).reference;
+    w.BaseFunction targetFunction = translator.functions.getFunction(target);
+    w.ValueType receiverType = targetFunction.type.inputs.first;
+    w.ValueType thisType = _visitThis(receiverType);
+    translator.convertType(function, thisType, receiverType);
+    _visitArguments(node.arguments, target, 1);
+    return _call(target);
+  }
+
+  @override
+  w.ValueType visitInstanceInvocation(
+      InstanceInvocation node, w.ValueType expectedType) {
+    w.ValueType? intrinsicResult = intrinsifier.generateInstanceIntrinsic(node);
+    if (intrinsicResult != null) return intrinsicResult;
+    Procedure target = node.interfaceTarget;
+    if (node.kind == InstanceAccessKind.Object) {
+      switch (target.name.text) {
+        case "toString":
+          late w.Label done;
+          w.ValueType resultType = _virtualCall(node, target, (signature) {
+            done = b.block(const [], signature.outputs);
+            w.Label nullString = b.block();
+            wrap(node.receiver, translator.topInfo.nullableType);
+            b.br_on_null(nullString);
+          }, (_) {
+            _visitArguments(node.arguments, node.interfaceTargetReference, 1);
+          }, getter: false, setter: false);
+          b.br(done);
+          b.end();
+          wrap(StringLiteral("null"), resultType);
+          b.end();
+          return resultType;
+        default:
+          _unimplemented(node, "Nullable invocation of ${target.name.text}",
+              [if (expectedType != voidMarker) expectedType]);
+          return expectedType;
+      }
+    }
+    Member? singleTarget = translator.singleTarget(node);
+    if (singleTarget != null) {
+      w.BaseFunction targetFunction =
+          translator.functions.getFunction(singleTarget.reference);
+      wrap(node.receiver, targetFunction.type.inputs.first);
+      _visitArguments(node.arguments, node.interfaceTargetReference, 1);
+      return _call(singleTarget.reference);
+    }
+    return _virtualCall(node, target,
+        (signature) => wrap(node.receiver, signature.inputs.first), (_) {
+      _visitArguments(node.arguments, node.interfaceTargetReference, 1);
+    }, getter: false, setter: false);
+  }
+
+  @override
+  w.ValueType visitDynamicInvocation(
+      DynamicInvocation node, w.ValueType expectedType) {
+    if (node.name.text != "call") {
+      _unimplemented(node, "Dynamic invocation of ${node.name.text}",
+          [if (expectedType != voidMarker) expectedType]);
+      return expectedType;
+    }
+    return _functionCall(
+        node.arguments.positional.length, node.receiver, node.arguments);
+  }
+
+  @override
+  w.ValueType visitEqualsCall(EqualsCall node, w.ValueType expectedType) {
+    w.ValueType? intrinsicResult = intrinsifier.generateEqualsIntrinsic(node);
+    if (intrinsicResult != null) return intrinsicResult;
+    Member? singleTarget = translator.singleTarget(node);
+    if (singleTarget == translator.coreTypes.objectEquals) {
+      // Plain reference comparison
+      wrap(node.left, w.RefType.eq(nullable: true));
+      wrap(node.right, w.RefType.eq(nullable: true));
+      b.ref_eq();
+    } else {
+      // Check operands for null, then call implementation
+      bool leftNullable = dartTypeOf(node.left).isPotentiallyNullable;
+      bool rightNullable = dartTypeOf(node.right).isPotentiallyNullable;
+      w.RefType leftType = translator.topInfo.typeWithNullability(leftNullable);
+      w.RefType rightType =
+          translator.topInfo.typeWithNullability(rightNullable);
+      w.Local leftLocal = addLocal(leftType);
+      w.Local rightLocal = addLocal(rightType);
+      w.Label? operandNull;
+      w.Label? done;
+      if (leftNullable || rightNullable) {
+        done = b.block(const [], const [w.NumType.i32]);
+        operandNull = b.block();
+      }
+      wrap(node.left, leftLocal.type);
+      b.local_set(leftLocal);
+      wrap(node.right, rightLocal.type);
+      if (rightNullable) {
+        b.local_tee(rightLocal);
+        b.br_on_null(operandNull!);
+        b.drop();
+      } else {
+        b.local_set(rightLocal);
+      }
+
+      void left([_]) {
+        b.local_get(leftLocal);
+        if (leftNullable) {
+          b.br_on_null(operandNull!);
+        } else if (leftLocal.type.nullable) {
+          b.ref_as_non_null();
+        }
+      }
+
+      void right([_]) {
+        b.local_get(rightLocal);
+        if (rightLocal.type.nullable) {
+          b.ref_as_non_null();
+        }
+      }
+
+      if (singleTarget != null) {
+        left();
+        right();
+        _call(singleTarget.reference);
+      } else {
+        _virtualCall(node, node.interfaceTarget, left, right,
+            getter: false, setter: false);
+      }
+      if (leftNullable || rightNullable) {
+        b.br(done!);
+        b.end(); // operandNull
+        if (leftNullable && rightNullable) {
+          // Both sides nullable - compare references
+          b.local_get(leftLocal);
+          b.local_get(rightLocal);
+          b.ref_eq();
+        } else {
+          // Only one side nullable - not equal if one is null
+          b.i32_const(0);
+        }
+        b.end(); // done
+      }
+    }
+    return w.NumType.i32;
+  }
+
+  @override
+  w.ValueType visitEqualsNull(EqualsNull node, w.ValueType expectedType) {
+    wrap(node.expression, translator.topInfo.nullableType);
+    b.ref_is_null();
+    return w.NumType.i32;
+  }
+
+  w.ValueType _virtualCall(
+      TreeNode node,
+      Member interfaceTarget,
+      void pushReceiver(w.FunctionType signature),
+      void pushArguments(w.FunctionType signature),
+      {required bool getter,
+      required bool setter}) {
+    SelectorInfo selector = translator.dispatchTable.selectorForTarget(
+        interfaceTarget.referenceAs(getter: getter, setter: setter));
+    assert(selector.name == interfaceTarget.name.text);
+
+    pushReceiver(selector.signature);
+
+    int? offset = selector.offset;
+    if (offset == null) {
+      // Singular target or unreachable call
+      assert(selector.targetCount <= 1);
+      if (selector.targetCount == 1) {
+        pushArguments(selector.signature);
+        return _call(selector.singularTarget!);
+      } else {
+        b.comment("Virtual call of ${selector.name} with no targets"
+            " at ${node.location}");
+        b.drop();
+        b.block(const [], selector.signature.outputs);
+        b.unreachable();
+        b.end();
+        return translator.outputOrVoid(selector.signature.outputs);
+      }
+    }
+
+    // Receiver is already on stack.
+    w.Local receiverVar = addLocal(selector.signature.inputs.first);
+    b.local_tee(receiverVar);
+    if (options.parameterNullability && receiverVar.type.nullable) {
+      b.ref_as_non_null();
+    }
+    pushArguments(selector.signature);
+
+    if (options.polymorphicSpecialization) {
+      _polymorphicSpecialization(selector, receiverVar);
+    } else {
+      String access = getter ? "get" : (setter ? "set" : "call");
+      b.comment("Instance $access of '${selector.name}'");
+      b.local_get(receiverVar);
+      b.struct_get(translator.topInfo.struct, FieldIndex.classId);
+      if (offset != 0) {
+        b.i32_const(offset);
+        b.i32_add();
+      }
+      b.call_indirect(selector.signature);
+
+      translator.functions.activateSelector(selector);
+    }
+
+    return translator.outputOrVoid(selector.signature.outputs);
+  }
+
+  void _polymorphicSpecialization(SelectorInfo selector, w.Local receiver) {
+    Map<int, Reference> implementations = Map.from(selector.targets);
+    implementations.removeWhere((id, target) => target.asMember.isAbstract);
+
+    w.Local idVar = addLocal(w.NumType.i32);
+    b.local_get(receiver);
+    b.struct_get(translator.topInfo.struct, FieldIndex.classId);
+    b.local_set(idVar);
+
+    w.Label block =
+        b.block(selector.signature.inputs, selector.signature.outputs);
+    calls:
+    while (Set.from(implementations.values).length > 1) {
+      for (int id in implementations.keys) {
+        Reference target = implementations[id]!;
+        if (implementations.values.where((t) => t == target).length == 1) {
+          // Single class id implements method.
+          b.local_get(idVar);
+          b.i32_const(id);
+          b.i32_eq();
+          b.if_(selector.signature.inputs, selector.signature.inputs);
+          _call(target);
+          b.br(block);
+          b.end();
+          implementations.remove(id);
+          continue calls;
+        }
+      }
+      // Find class id that separates remaining classes in two.
+      List<int> sorted = implementations.keys.toList()..sort();
+      int pivotId = sorted.firstWhere(
+          (id) => implementations[id] != implementations[sorted.first]);
+      // Fail compilation if no such id exists.
+      assert(sorted.lastWhere(
+              (id) => implementations[id] != implementations[pivotId]) ==
+          pivotId - 1);
+      Reference target = implementations[sorted.first]!;
+      b.local_get(idVar);
+      b.i32_const(pivotId);
+      b.i32_lt_u();
+      b.if_(selector.signature.inputs, selector.signature.inputs);
+      _call(target);
+      b.br(block);
+      b.end();
+      for (int id in sorted) {
+        if (id == pivotId) break;
+        implementations.remove(id);
+      }
+      continue calls;
+    }
+    // Call remaining implementation.
+    Reference target = implementations.values.first;
+    _call(target);
+    b.end();
+  }
+
+  @override
+  w.ValueType visitVariableGet(VariableGet node, w.ValueType expectedType) {
+    w.Local? local = locals[node.variable];
+    Capture? capture = closures.captures[node.variable];
+    if (capture != null) {
+      if (!capture.written && local != null) {
+        b.local_get(local);
+        return local.type;
+      } else {
+        b.local_get(capture.context.currentLocal);
+        b.struct_get(capture.context.struct, capture.fieldIndex);
+        return capture.type;
+      }
+    } else {
+      if (local == null) {
+        throw "Read of undefined variable ${node.variable}";
+      }
+      b.local_get(local);
+      return local.type;
+    }
+  }
+
+  @override
+  w.ValueType visitVariableSet(VariableSet node, w.ValueType expectedType) {
+    w.Local? local = locals[node.variable];
+    Capture? capture = closures.captures[node.variable];
+    bool preserved = expectedType != voidMarker;
+    if (capture != null) {
+      assert(capture.written);
+      b.local_get(capture.context.currentLocal);
+      wrap(node.value, capture.type);
+      if (preserved) {
+        w.Local temp = addLocal(translateType(node.variable.type));
+        b.local_tee(temp);
+        b.struct_set(capture.context.struct, capture.fieldIndex);
+        b.local_get(temp);
+        return temp.type;
+      } else {
+        b.struct_set(capture.context.struct, capture.fieldIndex);
+        return voidMarker;
+      }
+    } else {
+      if (local == null) {
+        throw "Write of undefined variable ${node.variable}";
+      }
+      wrap(node.value, local.type);
+      if (preserved) {
+        b.local_tee(local);
+        return local.type;
+      } else {
+        b.local_set(local);
+        return voidMarker;
+      }
+    }
+  }
+
+  @override
+  w.ValueType visitStaticGet(StaticGet node, w.ValueType expectedType) {
+    w.ValueType? intrinsicResult =
+        intrinsifier.generateStaticGetterIntrinsic(node);
+    if (intrinsicResult != null) return intrinsicResult;
+    Member target = node.target;
+    if (target is Field) {
+      return translator.globals.readGlobal(b, target);
+    } else {
+      return _call(target.reference);
+    }
+  }
+
+  @override
+  w.ValueType visitStaticTearOff(StaticTearOff node, w.ValueType expectedType) {
+    translator.constants.instantiateConstant(
+        function, b, StaticTearOffConstant(node.target), expectedType);
+    return expectedType;
+  }
+
+  @override
+  w.ValueType visitStaticSet(StaticSet node, w.ValueType expectedType) {
+    bool preserved = expectedType != voidMarker;
+    Member target = node.target;
+    if (target is Field) {
+      w.Global global = translator.globals.getGlobal(target);
+      wrap(node.value, global.type.type);
+      b.global_set(global);
+      if (preserved) {
+        b.global_get(global);
+        return global.type.type;
+      } else {
+        return voidMarker;
+      }
+    } else {
+      w.BaseFunction targetFunction =
+          translator.functions.getFunction(target.reference);
+      wrap(node.value, targetFunction.type.inputs.single);
+      w.Local? temp;
+      if (preserved) {
+        temp = addLocal(translateType(dartTypeOf(node.value)));
+        b.local_tee(temp);
+      }
+      _call(target.reference);
+      if (preserved) {
+        b.local_get(temp!);
+        return temp.type;
+      } else {
+        return voidMarker;
+      }
+    }
+  }
+
+  @override
+  w.ValueType visitSuperPropertyGet(
+      SuperPropertyGet node, w.ValueType expectedType) {
+    Member target = _lookupSuperTarget(node.interfaceTarget!, setter: false);
+    if (target is Procedure && !target.isGetter) {
+      throw "Not supported: Super tear-off at ${node.location}";
+    }
+    return _directGet(target, ThisExpression(), () => null);
+  }
+
+  @override
+  w.ValueType visitSuperPropertySet(
+      SuperPropertySet node, w.ValueType expectedType) {
+    Member target = _lookupSuperTarget(node.interfaceTarget!, setter: true);
+    return _directSet(target, ThisExpression(), node.value,
+        preserved: expectedType != voidMarker);
+  }
+
+  @override
+  w.ValueType visitInstanceGet(InstanceGet node, w.ValueType expectedType) {
+    Member target = node.interfaceTarget;
+    if (node.kind == InstanceAccessKind.Object) {
+      late w.Label doneLabel;
+      w.ValueType resultType = _virtualCall(node, target, (signature) {
+        doneLabel = b.block(const [], signature.outputs);
+        w.Label nullLabel = b.block();
+        wrap(node.receiver, translator.topInfo.nullableType);
+        b.br_on_null(nullLabel);
+      }, (_) {}, getter: true, setter: false);
+      b.br(doneLabel);
+      b.end(); // nullLabel
+      switch (target.name.text) {
+        case "hashCode":
+          b.i64_const(2011);
+          break;
+        case "runtimeType":
+          wrap(ConstantExpression(TypeLiteralConstant(NullType())), resultType);
+          break;
+        default:
+          _unimplemented(
+              node, "Nullable get of ${target.name.text}", [resultType]);
+          break;
+      }
+      b.end(); // doneLabel
+      return resultType;
+    }
+    Member? singleTarget = translator.singleTarget(node);
+    if (singleTarget != null) {
+      return _directGet(singleTarget, node.receiver,
+          () => intrinsifier.generateInstanceGetterIntrinsic(node));
+    } else {
+      return _virtualCall(node, target,
+          (signature) => wrap(node.receiver, signature.inputs.first), (_) {},
+          getter: true, setter: false);
+    }
+  }
+
+  @override
+  w.ValueType visitDynamicGet(DynamicGet node, w.ValueType expectedType) {
+    // Provisional implementation of dynamic get which assumes the getter
+    // is present (otherwise it traps or calls something random) and
+    // does not support tearoffs. This is sufficient to handle the
+    // dynamic .length calls in the core libraries.
+
+    SelectorInfo selector =
+        translator.dispatchTable.selectorForDynamicName(node.name.text);
+
+    // Evaluate receiver
+    wrap(node.receiver, selector.signature.inputs.first);
+    w.Local receiverVar = addLocal(selector.signature.inputs.first);
+    b.local_tee(receiverVar);
+    if (options.parameterNullability && receiverVar.type.nullable) {
+      b.ref_as_non_null();
+    }
+
+    // Dispatch table call
+    b.comment("Dynamic get of '${selector.name}'");
+    int offset = selector.offset!;
+    b.local_get(receiverVar);
+    b.struct_get(translator.topInfo.struct, FieldIndex.classId);
+    if (offset != 0) {
+      b.i32_const(offset);
+      b.i32_add();
+    }
+    b.call_indirect(selector.signature);
+
+    translator.functions.activateSelector(selector);
+
+    return translator.outputOrVoid(selector.signature.outputs);
+  }
+
+  w.ValueType _directGet(
+      Member target, Expression receiver, w.ValueType? Function() intrinsify) {
+    if (target is Field) {
+      ClassInfo info = translator.classInfo[target.enclosingClass]!;
+      int fieldIndex = translator.fieldIndex[target]!;
+      w.ValueType receiverType = info.nullableType;
+      w.ValueType fieldType = info.struct.fields[fieldIndex].type.unpacked;
+      wrap(receiver, receiverType);
+      b.struct_get(info.struct, fieldIndex);
+      return fieldType;
+    } else {
+      // Instance call of getter
+      assert(target is Procedure && target.isGetter);
+      w.ValueType? intrinsicResult = intrinsify();
+      if (intrinsicResult != null) return intrinsicResult;
+      w.BaseFunction targetFunction =
+          translator.functions.getFunction(target.reference);
+      wrap(receiver, targetFunction.type.inputs.single);
+      return _call(target.reference);
+    }
+  }
+
+  @override
+  w.ValueType visitInstanceTearOff(
+      InstanceTearOff node, w.ValueType expectedType) {
+    return _virtualCall(node, node.interfaceTarget,
+        (signature) => wrap(node.receiver, signature.inputs.first), (_) {},
+        getter: true, setter: false);
+  }
+
+  @override
+  w.ValueType visitInstanceSet(InstanceSet node, w.ValueType expectedType) {
+    bool preserved = expectedType != voidMarker;
+    w.Local? temp;
+    Member? singleTarget = translator.singleTarget(node);
+    if (singleTarget != null) {
+      return _directSet(singleTarget, node.receiver, node.value,
+          preserved: preserved);
+    } else {
+      _virtualCall(node, node.interfaceTarget,
+          (signature) => wrap(node.receiver, signature.inputs.first),
+          (signature) {
+        w.ValueType paramType = signature.inputs.last;
+        wrap(node.value, paramType);
+        if (preserved) {
+          temp = addLocal(paramType);
+          b.local_tee(temp!);
+        }
+      }, getter: false, setter: true);
+      if (preserved) {
+        b.local_get(temp!);
+        return temp!.type;
+      } else {
+        return voidMarker;
+      }
+    }
+  }
+
+  w.ValueType _directSet(Member target, Expression receiver, Expression value,
+      {required bool preserved}) {
+    w.Local? temp;
+    if (target is Field) {
+      ClassInfo info = translator.classInfo[target.enclosingClass]!;
+      int fieldIndex = translator.fieldIndex[target]!;
+      w.ValueType receiverType = info.nullableType;
+      w.ValueType fieldType = info.struct.fields[fieldIndex].type.unpacked;
+      wrap(receiver, receiverType);
+      wrap(value, fieldType);
+      if (preserved) {
+        temp = addLocal(fieldType);
+        b.local_tee(temp);
+      }
+      b.struct_set(info.struct, fieldIndex);
+    } else {
+      w.BaseFunction targetFunction =
+          translator.functions.getFunction(target.reference);
+      w.ValueType paramType = targetFunction.type.inputs.last;
+      wrap(receiver, targetFunction.type.inputs.first);
+      wrap(value, paramType);
+      if (preserved) {
+        temp = addLocal(paramType);
+        b.local_tee(temp);
+        translator.convertType(function, temp.type, paramType);
+      }
+      _call(target.reference);
+    }
+    if (preserved) {
+      b.local_get(temp!);
+      return temp.type;
+    } else {
+      return voidMarker;
+    }
+  }
+
+  @override
+  void visitFunctionDeclaration(FunctionDeclaration node) {
+    Capture? capture = closures.captures[node.variable];
+    bool locallyClosurized = closures.closurizedFunctions.contains(node);
+    if (capture != null || locallyClosurized) {
+      if (capture != null) {
+        b.local_get(capture.context.currentLocal);
+      }
+      w.StructType struct = _instantiateClosure(node.function);
+      if (locallyClosurized) {
+        w.Local local = addLocal(w.RefType.def(struct, nullable: false));
+        locals[node.variable] = local;
+        if (capture != null) {
+          b.local_tee(local);
+        } else {
+          b.local_set(local);
+        }
+      }
+      if (capture != null) {
+        b.struct_set(capture.context.struct, capture.fieldIndex);
+      }
+    }
+  }
+
+  @override
+  w.ValueType visitFunctionExpression(
+      FunctionExpression node, w.ValueType expectedType) {
+    w.StructType struct = _instantiateClosure(node.function);
+    return w.RefType.def(struct, nullable: false);
+  }
+
+  w.StructType _instantiateClosure(FunctionNode functionNode) {
+    int parameterCount = functionNode.requiredParameterCount;
+    Lambda lambda = closures.lambdas[functionNode]!;
+    w.DefinedGlobal global = translator.makeFunctionRef(lambda.function);
+
+    ClassInfo info = translator.classInfo[translator.functionClass]!;
+    translator.functions.allocateClass(info.classId);
+    w.StructType struct = translator.closureStructType(parameterCount);
+
+    b.i32_const(info.classId);
+    b.i32_const(initialIdentityHash);
+    _pushContext(functionNode);
+    b.global_get(global);
+    translator.struct_new(b, parameterCount);
+
+    return struct;
+  }
+
+  void _pushContext(FunctionNode functionNode) {
+    Context? context = closures.contexts[functionNode]?.parent;
+    if (context != null) {
+      b.local_get(context.currentLocal);
+      if (context.currentLocal.type.nullable) {
+        b.ref_as_non_null();
+      }
+    } else {
+      b.global_get(translator.globals.dummyGlobal); // Dummy context
+    }
+  }
+
+  @override
+  w.ValueType visitFunctionInvocation(
+      FunctionInvocation node, w.ValueType expectedType) {
+    FunctionType functionType = node.functionType!;
+    int parameterCount = functionType.requiredParameterCount;
+    return _functionCall(parameterCount, node.receiver, node.arguments);
+  }
+
+  w.ValueType _functionCall(
+      int parameterCount, Expression receiver, Arguments arguments) {
+    w.StructType struct = translator.closureStructType(parameterCount);
+    w.Local temp = addLocal(w.RefType.def(struct, nullable: false));
+    wrap(receiver, temp.type);
+    b.local_tee(temp);
+    b.struct_get(struct, FieldIndex.closureContext);
+    for (Expression arg in arguments.positional) {
+      wrap(arg, translator.topInfo.nullableType);
+    }
+    b.local_get(temp);
+    b.struct_get(struct, FieldIndex.closureFunction);
+    b.call_ref();
+    return translator.topInfo.nullableType;
+  }
+
+  @override
+  w.ValueType visitLocalFunctionInvocation(
+      LocalFunctionInvocation node, w.ValueType expectedType) {
+    var decl = node.variable.parent as FunctionDeclaration;
+    _pushContext(decl.function);
+    for (Expression arg in node.arguments.positional) {
+      wrap(arg, translator.topInfo.nullableType);
+    }
+    Lambda lambda = closures.lambdas[decl.function]!;
+    b.comment("Local call of ${decl.variable.name}");
+    b.call(lambda.function);
+    return translator.topInfo.nullableType;
+  }
+
+  @override
+  w.ValueType visitLogicalExpression(
+      LogicalExpression node, w.ValueType expectedType) {
+    _conditional(node, () => b.i32_const(1), () => b.i32_const(0),
+        const [w.NumType.i32]);
+    return w.NumType.i32;
+  }
+
+  @override
+  w.ValueType visitNot(Not node, w.ValueType expectedType) {
+    wrap(node.operand, w.NumType.i32);
+    b.i32_eqz();
+    return w.NumType.i32;
+  }
+
+  @override
+  w.ValueType visitConditionalExpression(
+      ConditionalExpression node, w.ValueType expectedType) {
+    _conditional(
+        node.condition,
+        () => wrap(node.then, expectedType),
+        () => wrap(node.otherwise, expectedType),
+        [if (expectedType != voidMarker) expectedType]);
+    return expectedType;
+  }
+
+  @override
+  w.ValueType visitNullCheck(NullCheck node, w.ValueType expectedType) {
+    // TODO(joshualitt): Check and throw exception
+    return wrap(node.operand, expectedType);
+  }
+
+  void _visitArguments(Arguments node, Reference target, int signatureOffset) {
+    final w.FunctionType signature = translator.signatureFor(target);
+    final ParameterInfo paramInfo = translator.paramInfoFor(target);
+    for (int i = 0; i < node.types.length; i++) {
+      _makeType(node.types[i], node);
+    }
+    signatureOffset += node.types.length;
+    for (int i = 0; i < node.positional.length; i++) {
+      wrap(node.positional[i], signature.inputs[signatureOffset + i]);
+    }
+    // Default values for positional parameters
+    for (int i = node.positional.length; i < paramInfo.positional.length; i++) {
+      final w.ValueType type = signature.inputs[signatureOffset + i];
+      translator.constants
+          .instantiateConstant(function, b, paramInfo.positional[i]!, type);
+    }
+    // Named arguments
+    final Map<String, w.Local> namedLocals = {};
+    for (var namedArg in node.named) {
+      final w.ValueType type = signature
+          .inputs[signatureOffset + paramInfo.nameIndex[namedArg.name]!];
+      final w.Local namedLocal = addLocal(type);
+      namedLocals[namedArg.name] = namedLocal;
+      wrap(namedArg.value, namedLocal.type);
+      b.local_set(namedLocal);
+    }
+    for (String name in paramInfo.names) {
+      w.Local? namedLocal = namedLocals[name];
+      final w.ValueType type =
+          signature.inputs[signatureOffset + paramInfo.nameIndex[name]!];
+      if (namedLocal != null) {
+        b.local_get(namedLocal);
+        translator.convertType(function, namedLocal.type, type);
+      } else {
+        translator.constants
+            .instantiateConstant(function, b, paramInfo.named[name]!, type);
+      }
+    }
+  }
+
+  @override
+  w.ValueType visitStringConcatenation(
+      StringConcatenation node, w.ValueType expectedType) {
+    _makeList(
+        node.expressions,
+        translator.fixedLengthListClass,
+        InterfaceType(translator.stringBaseClass, Nullability.nonNullable),
+        node);
+    return _call(translator.stringInterpolate.reference);
+  }
+
+  @override
+  w.ValueType visitThrow(Throw node, w.ValueType expectedType) {
+    wrap(node.expression, translator.topInfo.nullableType);
+    // TODO(joshualitt): Throw exception
+    b.comment(node.toStringInternal());
+    b.drop();
+    b.block(const [], [if (expectedType != voidMarker) expectedType]);
+    b.unreachable();
+    b.end();
+    return expectedType;
+  }
+
+  @override
+  w.ValueType visitInstantiation(Instantiation node, w.ValueType expectedType) {
+    throw "Not supported: Generic function instantiation at ${node.location}";
+  }
+
+  @override
+  w.ValueType visitConstantExpression(
+      ConstantExpression node, w.ValueType expectedType) {
+    translator.constants
+        .instantiateConstant(function, b, node.constant, expectedType);
+    return expectedType;
+  }
+
+  @override
+  w.ValueType visitNullLiteral(NullLiteral node, w.ValueType expectedType) {
+    translator.constants
+        .instantiateConstant(function, b, NullConstant(), expectedType);
+    return expectedType;
+  }
+
+  @override
+  w.ValueType visitStringLiteral(StringLiteral node, w.ValueType expectedType) {
+    translator.constants.instantiateConstant(
+        function, b, StringConstant(node.value), expectedType);
+    return expectedType;
+  }
+
+  @override
+  w.ValueType visitBoolLiteral(BoolLiteral node, w.ValueType expectedType) {
+    b.i32_const(node.value ? 1 : 0);
+    return w.NumType.i32;
+  }
+
+  @override
+  w.ValueType visitIntLiteral(IntLiteral node, w.ValueType expectedType) {
+    b.i64_const(node.value);
+    return w.NumType.i64;
+  }
+
+  @override
+  w.ValueType visitDoubleLiteral(DoubleLiteral node, w.ValueType expectedType) {
+    b.f64_const(node.value);
+    return w.NumType.f64;
+  }
+
+  @override
+  w.ValueType visitListLiteral(ListLiteral node, w.ValueType expectedType) {
+    return _makeList(node.expressions, translator.growableListClass,
+        node.typeArgument, node);
+  }
+
+  w.ValueType _makeList(List<Expression> expressions, Class cls,
+      DartType typeArg, TreeNode node) {
+    ClassInfo info = translator.classInfo[cls]!;
+    translator.functions.allocateClass(info.classId);
+    w.RefType refType = info.struct.fields.last.type.unpacked as w.RefType;
+    w.ArrayType arrayType = refType.heapType as w.ArrayType;
+    w.ValueType elementType = arrayType.elementType.type.unpacked;
+    int length = expressions.length;
+
+    b.i32_const(info.classId);
+    b.i32_const(initialIdentityHash);
+    _makeType(typeArg, node);
+    b.i64_const(length);
+    if (options.lazyConstants) {
+      // Avoid array.init instruction in lazy constants mode
+      b.i32_const(length);
+      translator.array_new_default(b, arrayType);
+      if (length > 0) {
+        w.Local arrayLocal = addLocal(refType.withNullability(false));
+        b.local_set(arrayLocal);
+        for (int i = 0; i < length; i++) {
+          b.local_get(arrayLocal);
+          b.i32_const(i);
+          wrap(expressions[i], elementType);
+          b.array_set(arrayType);
+        }
+        b.local_get(arrayLocal);
+        if (arrayLocal.type.nullable) {
+          b.ref_as_non_null();
+        }
+      }
+    } else {
+      for (Expression expression in expressions) {
+        wrap(expression, elementType);
+      }
+      translator.array_init(b, arrayType, length);
+    }
+    translator.struct_new(b, info);
+
+    return info.nonNullableType;
+  }
+
+  @override
+  w.ValueType visitMapLiteral(MapLiteral node, w.ValueType expectedType) {
+    w.BaseFunction mapFactory =
+        translator.functions.getFunction(translator.mapFactory.reference);
+    w.ValueType factoryReturnType = mapFactory.type.outputs.single;
+    _makeType(node.keyType, node);
+    _makeType(node.valueType, node);
+    b.call(mapFactory);
+    if (node.entries.isEmpty) {
+      return factoryReturnType;
+    }
+    w.BaseFunction mapPut =
+        translator.functions.getFunction(translator.mapPut.reference);
+    w.ValueType putReceiverType = mapPut.type.inputs[0];
+    w.ValueType putKeyType = mapPut.type.inputs[1];
+    w.ValueType putValueType = mapPut.type.inputs[2];
+    w.Local mapLocal = addLocal(putReceiverType);
+    translator.convertType(function, factoryReturnType, mapLocal.type);
+    b.local_set(mapLocal);
+    for (MapLiteralEntry entry in node.entries) {
+      b.local_get(mapLocal);
+      translator.convertType(function, mapLocal.type, putReceiverType);
+      wrap(entry.key, putKeyType);
+      wrap(entry.value, putValueType);
+      b.call(mapPut);
+    }
+    b.local_get(mapLocal);
+    return mapLocal.type;
+  }
+
+  @override
+  w.ValueType visitTypeLiteral(TypeLiteral node, w.ValueType expectedType) {
+    return _makeType(node.type, node);
+  }
+
+  w.ValueType _makeType(DartType type, TreeNode node) {
+    w.ValueType typeType =
+        translator.classInfo[translator.typeClass]!.nullableType;
+    if (_isTypeConstant(type)) {
+      return wrap(ConstantExpression(TypeLiteralConstant(type)), typeType);
+    }
+    if (type is TypeParameterType) {
+      if (type.parameter.parent is FunctionNode) {
+        // Type argument to function
+        w.Local? local = typeLocals[type.parameter];
+        if (local != null) {
+          b.local_get(local);
+          return local.type;
+        } else {
+          _unimplemented(
+              node, "Type parameter access inside lambda", [typeType]);
+          return typeType;
+        }
+      }
+      // Type argument of class
+      Class cls = type.parameter.parent as Class;
+      ClassInfo info = translator.classInfo[cls]!;
+      int fieldIndex = translator.typeParameterIndex[type.parameter]!;
+      w.ValueType thisType = _visitThis(info.nullableType);
+      translator.convertType(function, thisType, info.nullableType);
+      b.struct_get(info.struct, fieldIndex);
+      return typeType;
+    }
+    ClassInfo info = translator.classInfo[translator.typeClass]!;
+    translator.functions.allocateClass(info.classId);
+    if (type is FutureOrType) {
+      // TODO(askesc): Have an actual representation of FutureOr types
+      b.ref_null(info.nullableType.heapType);
+      return info.nullableType;
+    }
+    if (type is! InterfaceType) {
+      _unimplemented(node, type, [info.nullableType]);
+      return info.nullableType;
+    }
+    ClassInfo typeInfo = translator.classInfo[type.classNode]!;
+    w.ValueType typeListExpectedType = info.struct.fields[3].type.unpacked;
+    b.i32_const(info.classId);
+    b.i32_const(initialIdentityHash);
+    b.i64_const(typeInfo.classId);
+    if (type.typeArguments.isEmpty) {
+      b.global_get(translator.constants.emptyTypeList);
+      translator.convertType(function,
+          translator.constants.emptyTypeList.type.type, typeListExpectedType);
+    } else if (type.typeArguments.every(_isTypeConstant)) {
+      ListConstant typeArgs = ListConstant(
+          InterfaceType(translator.typeClass, Nullability.nonNullable),
+          type.typeArguments.map((t) => TypeLiteralConstant(t)).toList());
+      translator.constants
+          .instantiateConstant(function, b, typeArgs, typeListExpectedType);
+    } else {
+      w.ValueType listType = _makeList(
+          type.typeArguments.map((t) => TypeLiteral(t)).toList(),
+          translator.fixedLengthListClass,
+          InterfaceType(translator.typeClass, Nullability.nonNullable),
+          node);
+      translator.convertType(function, listType, typeListExpectedType);
+    }
+    translator.struct_new(b, info);
+    return info.nullableType;
+  }
+
+  bool _isTypeConstant(DartType type) {
+    return type is DynamicType ||
+        type is VoidType ||
+        type is NeverType ||
+        type is NullType ||
+        type is FunctionType ||
+        type is InterfaceType && type.typeArguments.every(_isTypeConstant);
+  }
+
+  @override
+  w.ValueType visitIsExpression(IsExpression node, w.ValueType expectedType) {
+    wrap(node.operand, translator.topInfo.nullableType);
+    emitTypeTest(node.type, dartTypeOf(node.operand), node);
+    return w.NumType.i32;
+  }
+
+  /// Test value against a Dart type. Expects the value on the stack as a
+  /// (ref null #Top) and leaves the result on the stack as an i32.
+  void emitTypeTest(DartType type, DartType operandType, TreeNode node) {
+    if (type is! InterfaceType) {
+      // TODO(askesc): Implement type test for remaining types
+      print("Not implemented: Type test with non-interface type $type"
+          " at ${node.location}");
+      b.drop();
+      b.i32_const(1);
+      return;
+    }
+    bool isNullable = operandType.isPotentiallyNullable;
+    w.Label? resultLabel;
+    if (isNullable) {
+      // Store operand in a temporary variable, since Binaryen does not support
+      // block inputs.
+      w.Local operand = addLocal(translator.topInfo.nullableType);
+      b.local_set(operand);
+      resultLabel = b.block(const [], const [w.NumType.i32]);
+      w.Label nullLabel = b.block(const [], const []);
+      b.local_get(operand);
+      b.br_on_null(nullLabel);
+    }
+    if (type.typeArguments.any((t) => t is! DynamicType)) {
+      // If the tested-against type as an instance of the static operand type
+      // has the same type arguments as the static operand type, it is not
+      // necessary to test the type arguments.
+      Class cls = translator.classForType(operandType);
+      InterfaceType? base = translator.hierarchy
+          .getTypeAsInstanceOf(type, cls, member.enclosingLibrary)
+          ?.withDeclaredNullability(operandType.declaredNullability);
+      if (base != operandType) {
+        print("Not implemented: Type test with type arguments"
+            " at ${node.location}");
+      }
+    }
+    List<Class> concrete = translator.subtypes
+        .getSubtypesOf(type.classNode)
+        .where((c) => !c.isAbstract)
+        .toList();
+    if (concrete.isEmpty) {
+      b.drop();
+      b.i32_const(0);
+    } else if (concrete.length == 1) {
+      ClassInfo info = translator.classInfo[concrete.single]!;
+      b.struct_get(translator.topInfo.struct, FieldIndex.classId);
+      b.i32_const(info.classId);
+      b.i32_eq();
+    } else {
+      w.Local idLocal = addLocal(w.NumType.i32);
+      b.struct_get(translator.topInfo.struct, FieldIndex.classId);
+      b.local_set(idLocal);
+      w.Label done = b.block(const [], const [w.NumType.i32]);
+      b.i32_const(1);
+      for (Class cls in concrete) {
+        ClassInfo info = translator.classInfo[cls]!;
+        b.i32_const(info.classId);
+        b.local_get(idLocal);
+        b.i32_eq();
+        b.br_if(done);
+      }
+      b.drop();
+      b.i32_const(0);
+      b.end(); // done
+    }
+    if (isNullable) {
+      b.br(resultLabel!);
+      b.end(); // nullLabel
+      b.i32_const(type.declaredNullability == Nullability.nullable ? 1 : 0);
+      b.end(); // resultLabel
+    }
+  }
+
+  @override
+  w.ValueType visitAsExpression(AsExpression node, w.ValueType expectedType) {
+    // TODO(joshualitt): Emit type test and throw exception on failure
+    return wrap(node.operand, expectedType);
+  }
+}
diff --git a/pkg/dart2wasm/lib/compile.dart b/pkg/dart2wasm/lib/compile.dart
new file mode 100644
index 0000000..c69fdab
--- /dev/null
+++ b/pkg/dart2wasm/lib/compile.dart
@@ -0,0 +1,69 @@
+// 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.
+
+import 'dart:typed_data';
+
+import 'package:front_end/src/api_unstable/vm.dart'
+    show
+        CompilerOptions,
+        CompilerResult,
+        DiagnosticMessage,
+        kernelForProgram,
+        Severity;
+
+import 'package:kernel/ast.dart';
+import 'package:kernel/core_types.dart';
+import 'package:kernel/target/targets.dart';
+import 'package:kernel/type_environment.dart';
+
+import 'package:vm/transformations/type_flow/transformer.dart' as globalTypeFlow
+    show transformComponent;
+
+import 'package:dart2wasm/target.dart';
+import 'package:dart2wasm/translator.dart';
+
+/// Compile a Dart file into a Wasm module.
+///
+/// Returns `null` if an error occurred during compilation. The
+/// [handleDiagnosticMessage] callback will have received an error message
+/// describing the error.
+Future<Uint8List?> compileToModule(
+    Uri mainUri,
+    Uri sdkRoot,
+    TranslatorOptions options,
+    void Function(DiagnosticMessage) handleDiagnosticMessage) async {
+  var succeeded = true;
+  void diagnosticMessageHandler(DiagnosticMessage message) {
+    if (message.severity == Severity.error) {
+      succeeded = false;
+    }
+    handleDiagnosticMessage(message);
+  }
+
+  Target target = WasmTarget();
+  CompilerOptions compilerOptions = CompilerOptions()
+    ..target = target
+    ..compileSdk = true
+    ..sdkRoot = sdkRoot
+    ..environmentDefines = {}
+    ..verbose = false
+    ..onDiagnostic = diagnosticMessageHandler;
+
+  CompilerResult? compilerResult =
+      await kernelForProgram(mainUri, compilerOptions);
+  if (compilerResult == null || !succeeded) {
+    return null;
+  }
+  Component component = compilerResult.component!;
+  CoreTypes coreTypes = compilerResult.coreTypes!;
+
+  globalTypeFlow.transformComponent(target, coreTypes, component,
+      treeShakeSignatures: true,
+      treeShakeWriteOnlyFields: true,
+      useRapidTypeAnalysis: false);
+
+  var translator = Translator(component, coreTypes,
+      TypeEnvironment(coreTypes, compilerResult.classHierarchy!), options);
+  return translator.translate();
+}
diff --git a/pkg/dart2wasm/lib/constants.dart b/pkg/dart2wasm/lib/constants.dart
new file mode 100644
index 0000000..789f070
--- /dev/null
+++ b/pkg/dart2wasm/lib/constants.dart
@@ -0,0 +1,637 @@
+// 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.
+
+import 'dart:typed_data';
+
+import 'package:dart2wasm/class_info.dart';
+import 'package:dart2wasm/translator.dart';
+
+import 'package:kernel/ast.dart';
+import 'package:kernel/type_algebra.dart' show substitute;
+
+import 'package:wasm_builder/wasm_builder.dart' as w;
+
+class ConstantInfo {
+  final Constant constant;
+  final w.DefinedGlobal global;
+  final w.DefinedFunction? function;
+
+  ConstantInfo(this.constant, this.global, this.function);
+}
+
+typedef ConstantCodeGenerator = void Function(
+    w.DefinedFunction?, w.Instructions);
+
+/// Handles the creation of Dart constants. Can operate in two modes - eager and
+/// lazy - controlled by [TranslatorOptions.lazyConstants].
+///
+/// Each (non-trivial) constant is assigned to a Wasm global. Multiple
+/// occurrences of the same constant use the same global.
+///
+/// In eager mode, the constant is contained within the global initializer,
+/// meaning all constants are initialized eagerly during module initialization.
+/// In lazy mode, the global starts out uninitialized, and every use of the
+/// constant checks the global to see if it has been initialized and calls an
+/// initialization function otherwise.
+class Constants {
+  final Translator translator;
+  final Map<Constant, ConstantInfo> constantInfo = {};
+  final StringBuffer oneByteStrings = StringBuffer();
+  final StringBuffer twoByteStrings = StringBuffer();
+  late final w.DefinedFunction oneByteStringFunction;
+  late final w.DefinedFunction twoByteStringFunction;
+  late final w.DataSegment oneByteStringSegment;
+  late final w.DataSegment twoByteStringSegment;
+  late final w.DefinedGlobal emptyString;
+  late final w.DefinedGlobal emptyTypeList;
+  late final ClassInfo typeInfo = translator.classInfo[translator.typeClass]!;
+
+  bool currentlyCreating = false;
+
+  Constants(this.translator) {
+    if (lazyConstants) {
+      oneByteStringFunction = makeStringFunction(translator.oneByteStringClass);
+      twoByteStringFunction = makeStringFunction(translator.twoByteStringClass);
+    } else if (stringDataSegments) {
+      oneByteStringSegment = m.addDataSegment();
+      twoByteStringSegment = m.addDataSegment();
+    }
+    initEmptyString();
+    initEmptyTypeList();
+  }
+
+  w.Module get m => translator.m;
+  bool get lazyConstants => translator.options.lazyConstants;
+  bool get stringDataSegments => translator.options.stringDataSegments;
+
+  void initEmptyString() {
+    ClassInfo info = translator.classInfo[translator.oneByteStringClass]!;
+    translator.functions.allocateClass(info.classId);
+    w.ArrayType arrayType =
+        (info.struct.fields.last.type as w.RefType).heapType as w.ArrayType;
+
+    if (lazyConstants) {
+      w.RefType emptyStringType = info.nullableType;
+      emptyString = m.addGlobal(w.GlobalType(emptyStringType));
+      emptyString.initializer.ref_null(emptyStringType.heapType);
+      emptyString.initializer.end();
+
+      w.Instructions b = translator.initFunction.body;
+      b.i32_const(info.classId);
+      b.i32_const(initialIdentityHash);
+      b.i32_const(0);
+      translator.array_new_default(b, arrayType);
+      translator.struct_new(b, info);
+      b.global_set(emptyString);
+    } else {
+      w.RefType emptyStringType = info.nonNullableType;
+      emptyString = m.addGlobal(w.GlobalType(emptyStringType, mutable: false));
+      w.Instructions ib = emptyString.initializer;
+      ib.i32_const(info.classId);
+      ib.i32_const(initialIdentityHash);
+      translator.array_init(ib, arrayType, 0);
+      translator.struct_new(ib, info);
+      ib.end();
+    }
+
+    Constant emptyStringConstant = StringConstant("");
+    constantInfo[emptyStringConstant] =
+        ConstantInfo(emptyStringConstant, emptyString, null);
+  }
+
+  void initEmptyTypeList() {
+    ClassInfo info = translator.classInfo[translator.immutableListClass]!;
+    translator.functions.allocateClass(info.classId);
+    w.RefType refType = info.struct.fields.last.type.unpacked as w.RefType;
+    w.ArrayType arrayType = refType.heapType as w.ArrayType;
+
+    // Create the empty type list with its type parameter uninitialized for now.
+    if (lazyConstants) {
+      w.RefType emptyListType = info.nullableType;
+      emptyTypeList = m.addGlobal(w.GlobalType(emptyListType));
+      emptyTypeList.initializer.ref_null(emptyListType.heapType);
+      emptyTypeList.initializer.end();
+
+      w.Instructions b = translator.initFunction.body;
+      b.i32_const(info.classId);
+      b.i32_const(initialIdentityHash);
+      b.ref_null(typeInfo.struct); // Initialized later
+      b.i64_const(0);
+      b.i32_const(0);
+      translator.array_new_default(b, arrayType);
+      translator.struct_new(b, info);
+      b.global_set(emptyTypeList);
+    } else {
+      w.RefType emptyListType = info.nonNullableType;
+      emptyTypeList = m.addGlobal(w.GlobalType(emptyListType, mutable: false));
+      w.Instructions ib = emptyTypeList.initializer;
+      ib.i32_const(info.classId);
+      ib.i32_const(initialIdentityHash);
+      ib.ref_null(typeInfo.struct); // Initialized later
+      ib.i64_const(0);
+      translator.array_init(ib, arrayType, 0);
+      translator.struct_new(ib, info);
+      ib.end();
+    }
+
+    Constant emptyTypeListConstant = ListConstant(
+        InterfaceType(translator.typeClass, Nullability.nonNullable), const []);
+    constantInfo[emptyTypeListConstant] =
+        ConstantInfo(emptyTypeListConstant, emptyTypeList, null);
+
+    // Initialize the type parameter of the empty type list to the type object
+    // for _Type, which itself refers to the empty type list.
+    w.Instructions b = translator.initFunction.body;
+    b.global_get(emptyTypeList);
+    instantiateConstant(
+        translator.initFunction,
+        b,
+        TypeLiteralConstant(
+            InterfaceType(translator.typeClass, Nullability.nonNullable)),
+        typeInfo.nullableType);
+    b.struct_set(info.struct,
+        translator.typeParameterIndex[info.cls!.typeParameters.single]!);
+  }
+
+  void finalize() {
+    if (lazyConstants) {
+      finalizeStrings();
+    }
+  }
+
+  void finalizeStrings() {
+    Uint8List oneByteStringsAsBytes =
+        Uint8List.fromList(oneByteStrings.toString().codeUnits);
+    assert(Endian.host == Endian.little);
+    Uint8List twoByteStringsAsBytes =
+        Uint16List.fromList(twoByteStrings.toString().codeUnits)
+            .buffer
+            .asUint8List();
+    Uint8List stringsAsBytes = (BytesBuilder()
+          ..add(twoByteStringsAsBytes)
+          ..add(oneByteStringsAsBytes))
+        .toBytes();
+
+    w.Memory stringMemory =
+        m.addMemory(stringsAsBytes.length, stringsAsBytes.length);
+    m.addDataSegment(stringsAsBytes, stringMemory, 0);
+    makeStringFunctionBody(translator.oneByteStringClass, oneByteStringFunction,
+        (b) {
+      b.i32_load8_u(stringMemory, twoByteStringsAsBytes.length);
+    });
+    makeStringFunctionBody(translator.twoByteStringClass, twoByteStringFunction,
+        (b) {
+      b.i32_const(1);
+      b.i32_shl();
+      b.i32_load16_u(stringMemory, 0);
+    });
+  }
+
+  /// Create one of the two Wasm functions (one for each string type) called
+  /// from every lazily initialized string constant (of that type) to create and
+  /// initialize the string.
+  ///
+  /// The function signature is (i32 offset, i32 length) -> (ref stringClass)
+  /// where offset and length are measured in characters and indicate the place
+  /// in the corresponding string data segment from which to copy this string.
+  w.DefinedFunction makeStringFunction(Class cls) {
+    ClassInfo info = translator.classInfo[cls]!;
+    w.FunctionType ftype = translator.functionType(
+        const [w.NumType.i32, w.NumType.i32], [info.nonNullableType]);
+    return m.addFunction(ftype, "makeString (${cls.name})");
+  }
+
+  void makeStringFunctionBody(Class cls, w.DefinedFunction function,
+      void Function(w.Instructions) emitLoad) {
+    ClassInfo info = translator.classInfo[cls]!;
+    w.ArrayType arrayType =
+        (info.struct.fields.last.type as w.RefType).heapType as w.ArrayType;
+
+    w.Local offset = function.locals[0];
+    w.Local length = function.locals[1];
+    w.Local array = function.addLocal(
+        translator.typeForLocal(w.RefType.def(arrayType, nullable: false)));
+    w.Local index = function.addLocal(w.NumType.i32);
+
+    w.Instructions b = function.body;
+    b.local_get(length);
+    translator.array_new_default(b, arrayType);
+    b.local_set(array);
+
+    b.i32_const(0);
+    b.local_set(index);
+    w.Label loop = b.loop();
+    b.local_get(array);
+    b.local_get(index);
+    b.local_get(offset);
+    b.local_get(index);
+    b.i32_add();
+    emitLoad(b);
+    b.array_set(arrayType);
+    b.local_get(index);
+    b.i32_const(1);
+    b.i32_add();
+    b.local_tee(index);
+    b.local_get(length);
+    b.i32_lt_u();
+    b.br_if(loop);
+    b.end();
+
+    b.i32_const(info.classId);
+    b.i32_const(initialIdentityHash);
+    b.local_get(array);
+    translator.struct_new(b, info);
+    b.end();
+  }
+
+  /// Ensure that the constant has a Wasm global assigned.
+  ///
+  /// In eager mode, sub-constants must have Wasm globals assigned before the
+  /// global for the composite constant is assigned, since global initializers
+  /// can only refer to earlier globals.
+  void ensureConstant(Constant constant) {
+    ConstantCreator(this).ensureConstant(constant);
+  }
+
+  /// Emit code to push a constant onto the stack.
+  void instantiateConstant(w.DefinedFunction? function, w.Instructions b,
+      Constant constant, w.ValueType expectedType) {
+    if (expectedType == translator.voidMarker) return;
+    ConstantInstantiator(this, function, b, expectedType).instantiate(constant);
+  }
+}
+
+class ConstantInstantiator extends ConstantVisitor<w.ValueType> {
+  final Constants constants;
+  final w.DefinedFunction? function;
+  final w.Instructions b;
+  final w.ValueType expectedType;
+
+  ConstantInstantiator(
+      this.constants, this.function, this.b, this.expectedType);
+
+  Translator get translator => constants.translator;
+  w.Module get m => translator.m;
+
+  void instantiate(Constant constant) {
+    w.ValueType resultType = constant.accept(this);
+    assert(!translator.needsConversion(resultType, expectedType),
+        "For $constant: expected $expectedType, got $resultType");
+  }
+
+  @override
+  w.ValueType defaultConstant(Constant constant) {
+    ConstantInfo info = ConstantCreator(constants).ensureConstant(constant)!;
+    w.ValueType globalType = info.global.type.type;
+    if (globalType.nullable) {
+      if (info.function != null) {
+        // Lazily initialized constant.
+        w.Label done = b.block(const [], [globalType.withNullability(false)]);
+        b.global_get(info.global);
+        b.br_on_non_null(done);
+        b.call(info.function!);
+        b.end();
+      } else {
+        // Constant initialized in the module init function.
+        b.global_get(info.global);
+        b.ref_as_non_null();
+      }
+      return globalType.withNullability(false);
+    } else {
+      // Constant initialized eagerly in a global initializer.
+      b.global_get(info.global);
+      return globalType;
+    }
+  }
+
+  @override
+  w.ValueType visitNullConstant(NullConstant node) {
+    w.ValueType? expectedType = this.expectedType;
+    if (expectedType != translator.voidMarker) {
+      if (expectedType.nullable) {
+        w.HeapType heapType =
+            expectedType is w.RefType ? expectedType.heapType : w.HeapType.data;
+        b.ref_null(heapType);
+      } else {
+        // This only happens in invalid but unreachable code produced by the
+        // TFA dead-code elimination.
+        b.comment("Non-nullable null constant");
+        b.block(const [], [expectedType]);
+        b.unreachable();
+        b.end();
+      }
+    }
+    return expectedType;
+  }
+
+  w.ValueType _maybeBox(w.ValueType wasmType, void Function() pushValue) {
+    if (expectedType is w.RefType) {
+      ClassInfo info = translator.classInfo[translator.boxedClasses[wasmType]]!;
+      b.i32_const(info.classId);
+      pushValue();
+      translator.struct_new(b, info);
+      return info.nonNullableType;
+    } else {
+      pushValue();
+      return wasmType;
+    }
+  }
+
+  @override
+  w.ValueType visitBoolConstant(BoolConstant constant) {
+    return _maybeBox(w.NumType.i32, () {
+      b.i32_const(constant.value ? 1 : 0);
+    });
+  }
+
+  @override
+  w.ValueType visitIntConstant(IntConstant constant) {
+    return _maybeBox(w.NumType.i64, () {
+      b.i64_const(constant.value);
+    });
+  }
+
+  @override
+  w.ValueType visitDoubleConstant(DoubleConstant constant) {
+    return _maybeBox(w.NumType.f64, () {
+      b.f64_const(constant.value);
+    });
+  }
+}
+
+class ConstantCreator extends ConstantVisitor<ConstantInfo?> {
+  final Constants constants;
+
+  ConstantCreator(this.constants);
+
+  Translator get translator => constants.translator;
+  w.Module get m => constants.m;
+  bool get lazyConstants => constants.lazyConstants;
+
+  ConstantInfo? ensureConstant(Constant constant) {
+    ConstantInfo? info = constants.constantInfo[constant];
+    if (info == null) {
+      info = constant.accept(this);
+      if (info != null) {
+        constants.constantInfo[constant] = info;
+      }
+    }
+    return info;
+  }
+
+  ConstantInfo createConstant(
+      Constant constant, w.RefType type, ConstantCodeGenerator generator) {
+    assert(!type.nullable);
+    if (lazyConstants) {
+      // Create uninitialized global and function to initialize it.
+      w.DefinedGlobal global =
+          m.addGlobal(w.GlobalType(type.withNullability(true)));
+      global.initializer.ref_null(type.heapType);
+      global.initializer.end();
+      w.FunctionType ftype = translator.functionType(const [], [type]);
+      w.DefinedFunction function = m.addFunction(ftype, "$constant");
+      generator(function, function.body);
+      w.Local temp = function.addLocal(translator.typeForLocal(type));
+      w.Instructions b2 = function.body;
+      b2.local_tee(temp);
+      b2.global_set(global);
+      b2.local_get(temp);
+      translator.convertType(function, temp.type, type);
+      b2.end();
+
+      return ConstantInfo(constant, global, function);
+    } else {
+      // Create global with the constant in its initializer.
+      assert(!constants.currentlyCreating);
+      constants.currentlyCreating = true;
+      w.DefinedGlobal global = m.addGlobal(w.GlobalType(type, mutable: false));
+      generator(null, global.initializer);
+      global.initializer.end();
+      constants.currentlyCreating = false;
+
+      return ConstantInfo(constant, global, null);
+    }
+  }
+
+  @override
+  ConstantInfo? defaultConstant(Constant constant) => null;
+
+  @override
+  ConstantInfo? visitStringConstant(StringConstant constant) {
+    bool isOneByte = constant.value.codeUnits.every((c) => c <= 255);
+    ClassInfo info = translator.classInfo[isOneByte
+        ? translator.oneByteStringClass
+        : translator.twoByteStringClass]!;
+    translator.functions.allocateClass(info.classId);
+    w.RefType type = info.nonNullableType;
+    return createConstant(constant, type, (function, b) {
+      if (lazyConstants) {
+        // Copy string contents from linear memory on initialization. The memory
+        // is initialized by an active data segment for each string type.
+        StringBuffer buffer =
+            isOneByte ? constants.oneByteStrings : constants.twoByteStrings;
+        int offset = buffer.length;
+        int length = constant.value.length;
+        buffer.write(constant.value);
+
+        b.i32_const(offset);
+        b.i32_const(length);
+        b.call(isOneByte
+            ? constants.oneByteStringFunction
+            : constants.twoByteStringFunction);
+      } else {
+        w.ArrayType arrayType =
+            (info.struct.fields.last.type as w.RefType).heapType as w.ArrayType;
+
+        b.i32_const(info.classId);
+        b.i32_const(initialIdentityHash);
+        if (constants.stringDataSegments) {
+          // Initialize string contents from passive data segment.
+          w.DataSegment segment;
+          Uint8List bytes;
+          if (isOneByte) {
+            segment = constants.oneByteStringSegment;
+            bytes = Uint8List.fromList(constant.value.codeUnits);
+          } else {
+            assert(Endian.host == Endian.little);
+            segment = constants.twoByteStringSegment;
+            bytes = Uint16List.fromList(constant.value.codeUnits)
+                .buffer
+                .asUint8List();
+          }
+          int offset = segment.length;
+          segment.append(bytes);
+          b.i32_const(offset);
+          b.i32_const(constant.value.length);
+          translator.array_init_from_data(b, arrayType, segment);
+        } else {
+          // Initialize string contents from i32 constants on the stack.
+          for (int charCode in constant.value.codeUnits) {
+            b.i32_const(charCode);
+          }
+          translator.array_init(b, arrayType, constant.value.length);
+        }
+        translator.struct_new(b, info);
+      }
+    });
+  }
+
+  @override
+  ConstantInfo? visitInstanceConstant(InstanceConstant constant) {
+    Class cls = constant.classNode;
+    ClassInfo info = translator.classInfo[cls]!;
+    translator.functions.allocateClass(info.classId);
+    w.RefType type = info.nonNullableType;
+
+    // Collect sub-constants for field values.
+    const int baseFieldCount = 2;
+    int fieldCount = info.struct.fields.length;
+    List<Constant?> subConstants = List.filled(fieldCount, null);
+    constant.fieldValues.forEach((reference, subConstant) {
+      int index = translator.fieldIndex[reference.asField]!;
+      assert(subConstants[index] == null);
+      subConstants[index] = subConstant;
+      ensureConstant(subConstant);
+    });
+
+    // Collect sub-constants for type arguments.
+    Map<TypeParameter, DartType> substitution = {};
+    List<DartType> args = constant.typeArguments;
+    while (true) {
+      for (int i = 0; i < cls.typeParameters.length; i++) {
+        TypeParameter parameter = cls.typeParameters[i];
+        DartType arg = substitute(args[i], substitution);
+        substitution[parameter] = arg;
+        int index = translator.typeParameterIndex[parameter]!;
+        Constant typeArgConstant = TypeLiteralConstant(arg);
+        subConstants[index] = typeArgConstant;
+        ensureConstant(typeArgConstant);
+      }
+      Supertype? supertype = cls.supertype;
+      if (supertype == null) break;
+      cls = supertype.classNode;
+      args = supertype.typeArguments;
+    }
+
+    return createConstant(constant, type, (function, b) {
+      b.i32_const(info.classId);
+      b.i32_const(initialIdentityHash);
+      for (int i = baseFieldCount; i < fieldCount; i++) {
+        Constant subConstant = subConstants[i]!;
+        constants.instantiateConstant(
+            function, b, subConstant, info.struct.fields[i].type.unpacked);
+      }
+      translator.struct_new(b, info);
+    });
+  }
+
+  @override
+  ConstantInfo? visitListConstant(ListConstant constant) {
+    Constant typeArgConstant = TypeLiteralConstant(constant.typeArgument);
+    ensureConstant(typeArgConstant);
+    for (Constant subConstant in constant.entries) {
+      ensureConstant(subConstant);
+    }
+
+    ClassInfo info = translator.classInfo[translator.immutableListClass]!;
+    translator.functions.allocateClass(info.classId);
+    w.RefType type = info.nonNullableType;
+    return createConstant(constant, type, (function, b) {
+      w.RefType refType = info.struct.fields.last.type.unpacked as w.RefType;
+      w.ArrayType arrayType = refType.heapType as w.ArrayType;
+      w.ValueType elementType = arrayType.elementType.type.unpacked;
+      int length = constant.entries.length;
+      b.i32_const(info.classId);
+      b.i32_const(initialIdentityHash);
+      constants.instantiateConstant(
+          function, b, typeArgConstant, constants.typeInfo.nullableType);
+      b.i64_const(length);
+      if (lazyConstants) {
+        // Allocate array and set each entry to the corresponding sub-constant.
+        w.Local arrayLocal = function!.addLocal(
+            refType.withNullability(!translator.options.localNullability));
+        b.i32_const(length);
+        translator.array_new_default(b, arrayType);
+        b.local_set(arrayLocal);
+        for (int i = 0; i < length; i++) {
+          b.local_get(arrayLocal);
+          b.i32_const(i);
+          constants.instantiateConstant(
+              function, b, constant.entries[i], elementType);
+          b.array_set(arrayType);
+        }
+        b.local_get(arrayLocal);
+        if (arrayLocal.type.nullable) {
+          b.ref_as_non_null();
+        }
+      } else {
+        // Push all sub-constants on the stack and initialize array from them.
+        for (int i = 0; i < length; i++) {
+          constants.instantiateConstant(
+              function, b, constant.entries[i], elementType);
+        }
+        translator.array_init(b, arrayType, length);
+      }
+      translator.struct_new(b, info);
+    });
+  }
+
+  @override
+  ConstantInfo? visitStaticTearOffConstant(StaticTearOffConstant constant) {
+    w.DefinedFunction closureFunction =
+        translator.getTearOffFunction(constant.targetReference.asProcedure);
+    int parameterCount = closureFunction.type.inputs.length - 1;
+    w.StructType struct = translator.closureStructType(parameterCount);
+    w.RefType type = w.RefType.def(struct, nullable: false);
+    return createConstant(constant, type, (function, b) {
+      ClassInfo info = translator.classInfo[translator.functionClass]!;
+      translator.functions.allocateClass(info.classId);
+
+      b.i32_const(info.classId);
+      b.i32_const(initialIdentityHash);
+      b.global_get(translator.globals.dummyGlobal); // Dummy context
+      if (lazyConstants) {
+        w.DefinedGlobal global = translator.makeFunctionRef(closureFunction);
+        b.global_get(global);
+      } else {
+        b.ref_func(closureFunction);
+      }
+      translator.struct_new(b, parameterCount);
+    });
+  }
+
+  @override
+  ConstantInfo? visitTypeLiteralConstant(TypeLiteralConstant constant) {
+    DartType cType = constant.type;
+    assert(cType is! TypeParameterType);
+    DartType type = cType is DynamicType ||
+            cType is VoidType ||
+            cType is NeverType ||
+            cType is NullType
+        ? translator.coreTypes.objectRawType(Nullability.nullable)
+        : cType is FunctionType
+            ? InterfaceType(translator.functionClass, cType.declaredNullability)
+            : cType;
+    if (type is! InterfaceType) throw "Not implemented: $constant";
+
+    ListConstant typeArgs = ListConstant(
+        InterfaceType(translator.typeClass, Nullability.nonNullable),
+        type.typeArguments.map((t) => TypeLiteralConstant(t)).toList());
+    ensureConstant(typeArgs);
+
+    ClassInfo info = constants.typeInfo;
+    translator.functions.allocateClass(info.classId);
+    return createConstant(constant, info.nonNullableType, (function, b) {
+      ClassInfo typeInfo = translator.classInfo[type.classNode]!;
+      w.ValueType typeListExpectedType = info.struct.fields[3].type.unpacked;
+
+      b.i32_const(info.classId);
+      b.i32_const(initialIdentityHash);
+      b.i64_const(typeInfo.classId);
+      constants.instantiateConstant(
+          function, b, typeArgs, typeListExpectedType);
+      translator.struct_new(b, info);
+    });
+  }
+}
diff --git a/pkg/dart2wasm/lib/constants_backend.dart b/pkg/dart2wasm/lib/constants_backend.dart
new file mode 100644
index 0000000..6871599
--- /dev/null
+++ b/pkg/dart2wasm/lib/constants_backend.dart
@@ -0,0 +1,110 @@
+// 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.
+
+import 'package:kernel/ast.dart';
+import 'package:kernel/target/targets.dart';
+import 'package:kernel/core_types.dart';
+
+class WasmConstantsBackend extends ConstantsBackend {
+  final Class immutableMapClass;
+  final Class unmodifiableSetClass;
+  final Field unmodifiableSetMap;
+
+  WasmConstantsBackend._(this.immutableMapClass, this.unmodifiableSetMap,
+      this.unmodifiableSetClass);
+
+  factory WasmConstantsBackend(CoreTypes coreTypes) {
+    final Library coreLibrary = coreTypes.coreLibrary;
+    final Class immutableMapClass = coreLibrary.classes
+        .firstWhere((Class klass) => klass.name == '_ImmutableMap');
+    Field unmodifiableSetMap =
+        coreTypes.index.getField('dart:collection', '_UnmodifiableSet', '_map');
+
+    return new WasmConstantsBackend._(immutableMapClass, unmodifiableSetMap,
+        unmodifiableSetMap.enclosingClass!);
+  }
+
+  @override
+  Constant lowerMapConstant(MapConstant constant) {
+    // The _ImmutableMap class is implemented via one field pointing to a list
+    // of key/value pairs -- see runtime/lib/immutable_map.dart!
+    final List<Constant> kvListPairs =
+        new List<Constant>.generate(2 * constant.entries.length, (int i) {
+      final int index = i ~/ 2;
+      final ConstantMapEntry entry = constant.entries[index];
+      return i % 2 == 0 ? entry.key : entry.value;
+    });
+    // This is a bit fishy, since we merge the key and the value type by
+    // putting both into the same list.
+    final ListConstant kvListConstant =
+        new ListConstant(const DynamicType(), kvListPairs);
+    assert(immutableMapClass.fields.length == 1);
+    final Field kvPairListField = immutableMapClass.fields[0];
+    return new InstanceConstant(immutableMapClass.reference, <DartType>[
+      constant.keyType,
+      constant.valueType,
+    ], <Reference, Constant>{
+      // We use getterReference as we refer to the field itself.
+      kvPairListField.getterReference: kvListConstant,
+    });
+  }
+
+  @override
+  bool isLoweredMapConstant(Constant constant) {
+    return constant is InstanceConstant &&
+        constant.classNode == immutableMapClass;
+  }
+
+  @override
+  void forEachLoweredMapConstantEntry(
+      Constant constant, void Function(Constant key, Constant value) f) {
+    assert(isLoweredMapConstant(constant));
+    final InstanceConstant instance = constant as InstanceConstant;
+    assert(immutableMapClass.fields.length == 1);
+    final Field kvPairListField = immutableMapClass.fields[0];
+    final ListConstant kvListConstant =
+        instance.fieldValues[kvPairListField.getterReference] as ListConstant;
+    assert(kvListConstant.entries.length % 2 == 0);
+    for (int index = 0; index < kvListConstant.entries.length; index += 2) {
+      f(kvListConstant.entries[index], kvListConstant.entries[index + 1]);
+    }
+  }
+
+  @override
+  Constant lowerSetConstant(SetConstant constant) {
+    final DartType elementType = constant.typeArgument;
+    final List<Constant> entries = constant.entries;
+    final List<ConstantMapEntry> mapEntries =
+        new List<ConstantMapEntry>.generate(entries.length, (int index) {
+      return new ConstantMapEntry(entries[index], new NullConstant());
+    });
+    Constant map = lowerMapConstant(
+        new MapConstant(elementType, const NullType(), mapEntries));
+    return new InstanceConstant(unmodifiableSetClass.reference, [elementType],
+        <Reference, Constant>{unmodifiableSetMap.getterReference: map});
+  }
+
+  @override
+  bool isLoweredSetConstant(Constant constant) {
+    if (constant is InstanceConstant &&
+        constant.classNode == unmodifiableSetClass) {
+      InstanceConstant instance = constant;
+      return isLoweredMapConstant(
+          instance.fieldValues[unmodifiableSetMap.getterReference]!);
+    }
+    return false;
+  }
+
+  @override
+  void forEachLoweredSetConstantElement(
+      Constant constant, void Function(Constant element) f) {
+    assert(isLoweredSetConstant(constant));
+    final InstanceConstant instance = constant as InstanceConstant;
+    final Constant mapConstant =
+        instance.fieldValues[unmodifiableSetMap.getterReference]!;
+    forEachLoweredMapConstantEntry(mapConstant, (Constant key, Constant value) {
+      f(key);
+    });
+  }
+}
diff --git a/pkg/dart2wasm/lib/dispatch_table.dart b/pkg/dart2wasm/lib/dispatch_table.dart
new file mode 100644
index 0000000..3c6b024
--- /dev/null
+++ b/pkg/dart2wasm/lib/dispatch_table.dart
@@ -0,0 +1,308 @@
+// 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.
+
+import 'dart:math';
+
+import 'package:dart2wasm/class_info.dart';
+import 'package:dart2wasm/param_info.dart';
+import 'package:dart2wasm/reference_extensions.dart';
+import 'package:dart2wasm/translator.dart';
+
+import 'package:kernel/ast.dart';
+
+import 'package:vm/metadata/procedure_attributes.dart';
+import 'package:vm/metadata/table_selector.dart';
+
+import 'package:wasm_builder/wasm_builder.dart' as w;
+
+/// Information for a dispatch table selector.
+class SelectorInfo {
+  final Translator translator;
+
+  final int id;
+  final int callCount;
+  final bool tornOff;
+  final ParameterInfo paramInfo;
+  int returnCount;
+
+  final Map<int, Reference> targets = {};
+  late final w.FunctionType signature = computeSignature();
+
+  late final List<int> classIds;
+  late final int targetCount;
+  bool forced = false;
+  Reference? singularTarget;
+  int? offset;
+
+  String get name => paramInfo.member.name.text;
+
+  bool get alive => callCount > 0 && targetCount > 1 || forced;
+
+  int get sortWeight => classIds.length * 10 + callCount;
+
+  SelectorInfo(this.translator, this.id, this.callCount, this.tornOff,
+      this.paramInfo, this.returnCount);
+
+  /// Compute the signature for the functions implementing members targeted by
+  /// this selector.
+  ///
+  /// When the selector has multiple targets, the type of each parameter/return
+  /// is the upper bound across all targets, such that all targets have the
+  /// same signature, and the actual representation types of the parameters and
+  /// returns are subtypes (resp. supertypes) of the types in the signature.
+  w.FunctionType computeSignature() {
+    var nameIndex = paramInfo.nameIndex;
+    List<Set<ClassInfo>> inputSets =
+        List.generate(1 + paramInfo.paramCount, (_) => {});
+    List<Set<ClassInfo>> outputSets = List.generate(returnCount, (_) => {});
+    List<bool> inputNullable = List.filled(1 + paramInfo.paramCount, false);
+    List<bool> outputNullable = List.filled(returnCount, false);
+    targets.forEach((id, target) {
+      ClassInfo receiver = translator.classes[id];
+      List<DartType> positional;
+      Map<String, DartType> named;
+      List<DartType> returns;
+      Member member = target.asMember;
+      if (member is Field) {
+        if (target.isImplicitGetter) {
+          positional = const [];
+          named = const {};
+          returns = [member.getterType];
+        } else {
+          positional = [member.setterType];
+          named = const {};
+          returns = const [];
+        }
+      } else {
+        FunctionNode function = member.function!;
+        if (target.isTearOffReference) {
+          positional = const [];
+          named = const {};
+          returns = [function.computeFunctionType(Nullability.nonNullable)];
+        } else {
+          positional = [
+            for (VariableDeclaration param in function.positionalParameters)
+              param.type
+          ];
+          named = {
+            for (VariableDeclaration param in function.namedParameters)
+              param.name!: param.type
+          };
+          returns = function.returnType is VoidType
+              ? const []
+              : [function.returnType];
+        }
+      }
+      assert(returns.length <= outputSets.length);
+      inputSets[0].add(receiver);
+      for (int i = 0; i < positional.length; i++) {
+        DartType type = positional[i];
+        inputSets[1 + i]
+            .add(translator.classInfo[translator.classForType(type)]!);
+        inputNullable[1 + i] |= type.isPotentiallyNullable;
+      }
+      for (String name in named.keys) {
+        int i = nameIndex[name]!;
+        DartType type = named[name]!;
+        inputSets[1 + i]
+            .add(translator.classInfo[translator.classForType(type)]!);
+        inputNullable[1 + i] |= type.isPotentiallyNullable;
+      }
+      for (int i = 0; i < returnCount; i++) {
+        if (i < returns.length) {
+          outputSets[i]
+              .add(translator.classInfo[translator.classForType(returns[i])]!);
+          outputNullable[i] |= returns[i].isPotentiallyNullable;
+        } else {
+          outputNullable[i] = true;
+        }
+      }
+    });
+
+    List<w.ValueType> typeParameters = List.filled(paramInfo.typeParamCount,
+        translator.classInfo[translator.typeClass]!.nullableType);
+    List<w.ValueType> inputs = List.generate(
+        inputSets.length,
+        (i) => translator.typeForInfo(
+            upperBound(inputSets[i]), inputNullable[i]) as w.ValueType);
+    inputs[0] = translator.ensureBoxed(inputs[0]);
+    if (name == '==') {
+      // == can't be called with null
+      inputs[1] = inputs[1].withNullability(false);
+    }
+    List<w.ValueType> outputs = List.generate(
+        outputSets.length,
+        (i) => translator.typeForInfo(
+            upperBound(outputSets[i]), outputNullable[i]) as w.ValueType);
+    return translator.functionType(
+        [inputs[0], ...typeParameters, ...inputs.sublist(1)], outputs);
+  }
+}
+
+// Build dispatch table for member calls.
+class DispatchTable {
+  final Translator translator;
+  final List<TableSelectorInfo> selectorMetadata;
+  final Map<TreeNode, ProcedureAttributesMetadata> procedureAttributeMetadata;
+
+  final Map<int, SelectorInfo> selectorInfo = {};
+  final Map<String, int> dynamicGets = {};
+  late final List<Reference?> table;
+
+  DispatchTable(this.translator)
+      : selectorMetadata =
+            (translator.component.metadata["vm.table-selector.metadata"]
+                    as TableSelectorMetadataRepository)
+                .mapping[translator.component]!
+                .selectors,
+        procedureAttributeMetadata =
+            (translator.component.metadata["vm.procedure-attributes.metadata"]
+                    as ProcedureAttributesMetadataRepository)
+                .mapping;
+
+  SelectorInfo selectorForTarget(Reference target) {
+    Member member = target.asMember;
+    bool isGetter = target.isGetter || target.isTearOffReference;
+    ProcedureAttributesMetadata metadata = procedureAttributeMetadata[member]!;
+    int selectorId = isGetter
+        ? metadata.getterSelectorId
+        : metadata.methodOrSetterSelectorId;
+    ParameterInfo paramInfo = ParameterInfo.fromMember(target);
+    int returnCount = isGetter ||
+            member is Procedure && member.function.returnType is! VoidType
+        ? 1
+        : 0;
+    bool calledDynamically = isGetter && metadata.getterCalledDynamically;
+    if (calledDynamically) {
+      // Merge all same-named getter selectors that are called dynamically.
+      selectorId = dynamicGets.putIfAbsent(member.name.text, () => selectorId);
+    }
+    var selector = selectorInfo.putIfAbsent(
+        selectorId,
+        () => SelectorInfo(
+            translator,
+            selectorId,
+            selectorMetadata[selectorId].callCount,
+            selectorMetadata[selectorId].tornOff,
+            paramInfo,
+            returnCount)
+          ..forced = calledDynamically);
+    selector.paramInfo.merge(paramInfo);
+    selector.returnCount = max(selector.returnCount, returnCount);
+    return selector;
+  }
+
+  SelectorInfo selectorForDynamicName(String name) {
+    return selectorInfo[dynamicGets[name]!]!;
+  }
+
+  void build() {
+    // Collect class/selector combinations
+    List<List<int>> selectorsInClass = [];
+    for (ClassInfo info in translator.classes) {
+      List<int> selectorIds = [];
+      ClassInfo? superInfo = info.superInfo;
+      if (superInfo != null) {
+        int superId = superInfo.classId;
+        selectorIds = List.of(selectorsInClass[superId]);
+        for (int selectorId in selectorIds) {
+          SelectorInfo selector = selectorInfo[selectorId]!;
+          selector.targets[info.classId] = selector.targets[superId]!;
+        }
+      }
+
+      SelectorInfo addMember(Reference reference) {
+        SelectorInfo selector = selectorForTarget(reference);
+        if (reference.asMember.isAbstract) {
+          selector.targets[info.classId] ??= reference;
+        } else {
+          selector.targets[info.classId] = reference;
+        }
+        selectorIds.add(selector.id);
+        return selector;
+      }
+
+      for (Member member
+          in info.cls?.members ?? translator.coreTypes.objectClass.members) {
+        if (member.isInstanceMember) {
+          if (member is Field) {
+            addMember(member.getterReference);
+            if (member.hasSetter) addMember(member.setterReference!);
+          } else if (member is Procedure) {
+            SelectorInfo method = addMember(member.reference);
+            if (method.tornOff) {
+              addMember(member.tearOffReference);
+            }
+          }
+        }
+      }
+      selectorsInClass.add(selectorIds);
+    }
+
+    // Build lists of class IDs and count targets
+    for (SelectorInfo selector in selectorInfo.values) {
+      selector.classIds = selector.targets.keys
+          .where((id) => !(translator.classes[id].cls?.isAbstract ?? true))
+          .toList()
+        ..sort();
+      Set<Reference> targets =
+          selector.targets.values.where((t) => !t.asMember.isAbstract).toSet();
+      selector.targetCount = targets.length;
+      if (targets.length == 1) selector.singularTarget = targets.single;
+    }
+
+    // Assign selector offsets
+    List<SelectorInfo> selectors = selectorInfo.values
+        .where((s) => s.alive)
+        .toList()
+      ..sort((a, b) => b.sortWeight - a.sortWeight);
+    int firstAvailable = 0;
+    table = [];
+    bool first = true;
+    for (SelectorInfo selector in selectors) {
+      int offset = first ? 0 : firstAvailable - selector.classIds.first;
+      first = false;
+      bool fits;
+      do {
+        fits = true;
+        for (int classId in selector.classIds) {
+          int entry = offset + classId;
+          if (entry >= table.length) {
+            // Fits
+            break;
+          }
+          if (table[entry] != null) {
+            fits = false;
+            break;
+          }
+        }
+        if (!fits) offset++;
+      } while (!fits);
+      selector.offset = offset;
+      for (int classId in selector.classIds) {
+        int entry = offset + classId;
+        while (table.length <= entry) table.add(null);
+        assert(table[entry] == null);
+        table[entry] = selector.targets[classId];
+      }
+      while (firstAvailable < table.length && table[firstAvailable] != null) {
+        firstAvailable++;
+      }
+    }
+  }
+
+  void output() {
+    w.Module m = translator.m;
+    w.Table wasmTable = m.addTable(table.length);
+    for (int i = 0; i < table.length; i++) {
+      Reference? target = table[i];
+      if (target != null) {
+        w.BaseFunction? fun = translator.functions.getExistingFunction(target);
+        if (fun != null) {
+          wasmTable.setElement(i, fun);
+        }
+      }
+    }
+  }
+}
diff --git a/pkg/dart2wasm/lib/functions.dart b/pkg/dart2wasm/lib/functions.dart
new file mode 100644
index 0000000..eaee6d1
--- /dev/null
+++ b/pkg/dart2wasm/lib/functions.dart
@@ -0,0 +1,216 @@
+// 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.
+
+import 'package:dart2wasm/dispatch_table.dart';
+import 'package:dart2wasm/reference_extensions.dart';
+import 'package:dart2wasm/translator.dart';
+
+import 'package:kernel/ast.dart';
+
+import 'package:wasm_builder/wasm_builder.dart' as w;
+
+/// This class is responsible for collecting import and export annotations.
+/// It also creates Wasm functions for Dart members and manages the worklist
+/// used to achieve tree shaking.
+class FunctionCollector extends MemberVisitor1<w.FunctionType, Reference> {
+  final Translator translator;
+
+  // Wasm function for each Dart function
+  final Map<Reference, w.BaseFunction> _functions = {};
+  // Names of exported functions
+  final Map<Reference, String> exports = {};
+  // Functions for which code has not yet been generated
+  final List<Reference> worklist = [];
+  // Class IDs for classes that are allocated somewhere in the program
+  final Set<int> _allocatedClasses = {};
+  // For each class ID, which functions should be added to the worklist if an
+  // allocation of that class is encountered
+  final Map<int, List<Reference>> _pendingAllocation = {};
+
+  FunctionCollector(this.translator);
+
+  w.Module get m => translator.m;
+
+  void collectImportsAndExports() {
+    for (Library library in translator.libraries) {
+      for (Procedure procedure in library.procedures) {
+        _importOrExport(procedure);
+      }
+      for (Class cls in library.classes) {
+        for (Procedure procedure in cls.procedures) {
+          _importOrExport(procedure);
+        }
+      }
+    }
+  }
+
+  void _importOrExport(Procedure procedure) {
+    String? importName = translator.getPragma(procedure, "wasm:import");
+    if (importName != null) {
+      int dot = importName.indexOf('.');
+      if (dot != -1) {
+        assert(!procedure.isInstanceMember);
+        String module = importName.substring(0, dot);
+        String name = importName.substring(dot + 1);
+        w.FunctionType ftype = _makeFunctionType(
+            procedure.reference, procedure.function.returnType, null,
+            isImportOrExport: true);
+        _functions[procedure.reference] =
+            m.importFunction(module, name, ftype, "$importName (import)");
+      }
+    }
+    String? exportName =
+        translator.getPragma(procedure, "wasm:export", procedure.name.text);
+    if (exportName != null) {
+      addExport(procedure.reference, exportName);
+    }
+  }
+
+  void addExport(Reference target, String exportName) {
+    exports[target] = exportName;
+  }
+
+  void initialize() {
+    // Add all exports to the worklist
+    for (Reference target in exports.keys) {
+      worklist.add(target);
+      Procedure node = target.asProcedure;
+      assert(!node.isInstanceMember);
+      assert(!node.isGetter);
+      w.FunctionType ftype = _makeFunctionType(
+          target, node.function.returnType, null,
+          isImportOrExport: true);
+      _functions[target] = m.addFunction(ftype, "$node");
+    }
+
+    // Value classes are always implicitly allocated.
+    allocateClass(translator.classInfo[translator.boxedBoolClass]!.classId);
+    allocateClass(translator.classInfo[translator.boxedIntClass]!.classId);
+    allocateClass(translator.classInfo[translator.boxedDoubleClass]!.classId);
+  }
+
+  w.BaseFunction? getExistingFunction(Reference target) {
+    return _functions[target];
+  }
+
+  w.BaseFunction getFunction(Reference target) {
+    return _functions.putIfAbsent(target, () {
+      worklist.add(target);
+      w.FunctionType ftype = target.isTearOffReference
+          ? translator.dispatchTable.selectorForTarget(target).signature
+          : target.asMember.accept1(this, target);
+      return m.addFunction(ftype, "${target.asMember}");
+    });
+  }
+
+  void activateSelector(SelectorInfo selector) {
+    selector.targets.forEach((classId, target) {
+      if (!target.asMember.isAbstract) {
+        if (_allocatedClasses.contains(classId)) {
+          // Class declaring or inheriting member is allocated somewhere.
+          getFunction(target);
+        } else {
+          // Remember the member in case an allocation is encountered later.
+          _pendingAllocation.putIfAbsent(classId, () => []).add(target);
+        }
+      }
+    });
+  }
+
+  void allocateClass(int classId) {
+    if (_allocatedClasses.add(classId)) {
+      // Schedule all members that were pending allocation of this class.
+      for (Reference target in _pendingAllocation[classId] ?? const []) {
+        getFunction(target);
+      }
+    }
+  }
+
+  @override
+  w.FunctionType defaultMember(Member node, Reference target) {
+    throw "No Wasm function for member: $node";
+  }
+
+  @override
+  w.FunctionType visitField(Field node, Reference target) {
+    if (!node.isInstanceMember) {
+      if (target == node.fieldReference) {
+        // Static field initializer function
+        return _makeFunctionType(target, node.type, null);
+      }
+      String kind = target == node.setterReference ? "setter" : "getter";
+      throw "No implicit $kind function for static field: $node";
+    }
+    return translator.dispatchTable.selectorForTarget(target).signature;
+  }
+
+  @override
+  w.FunctionType visitProcedure(Procedure node, Reference target) {
+    assert(!node.isAbstract);
+    return node.isInstanceMember
+        ? translator.dispatchTable.selectorForTarget(node.reference).signature
+        : _makeFunctionType(target, node.function.returnType, null);
+  }
+
+  @override
+  w.FunctionType visitConstructor(Constructor node, Reference target) {
+    return _makeFunctionType(target, VoidType(),
+        translator.classInfo[node.enclosingClass]!.nonNullableType);
+  }
+
+  w.FunctionType _makeFunctionType(
+      Reference target, DartType returnType, w.ValueType? receiverType,
+      {bool isImportOrExport = false}) {
+    Member member = target.asMember;
+    int typeParamCount = 0;
+    Iterable<DartType> params;
+    if (member is Field) {
+      params = [if (target.isImplicitSetter) member.setterType];
+    } else {
+      FunctionNode function = member.function!;
+      typeParamCount = (member is Constructor
+              ? member.enclosingClass.typeParameters
+              : function.typeParameters)
+          .length;
+      List<String> names = [for (var p in function.namedParameters) p.name!]
+        ..sort();
+      Map<String, DartType> nameTypes = {
+        for (var p in function.namedParameters) p.name!: p.type
+      };
+      params = [
+        for (var p in function.positionalParameters) p.type,
+        for (String name in names) nameTypes[name]!
+      ];
+      function.positionalParameters.map((p) => p.type);
+    }
+
+    List<w.ValueType> typeParameters = List.filled(typeParamCount,
+        translator.classInfo[translator.typeClass]!.nullableType);
+
+    // The JS embedder will not accept Wasm struct types as parameter or return
+    // types for functions called from JS. We need to use eqref instead.
+    w.ValueType adjustExternalType(w.ValueType type) {
+      if (isImportOrExport && type.isSubtypeOf(w.RefType.eq())) {
+        return w.RefType.eq();
+      }
+      return type;
+    }
+
+    List<w.ValueType> inputs = [];
+    if (receiverType != null) {
+      inputs.add(adjustExternalType(receiverType));
+    }
+    inputs.addAll(typeParameters.map(adjustExternalType));
+    inputs.addAll(
+        params.map((t) => adjustExternalType(translator.translateType(t))));
+
+    List<w.ValueType> outputs = returnType is VoidType ||
+            returnType is NeverType ||
+            returnType is NullType
+        ? const []
+        : [adjustExternalType(translator.translateType(returnType))];
+
+    return translator.functionType(inputs, outputs);
+  }
+}
diff --git a/pkg/dart2wasm/lib/globals.dart b/pkg/dart2wasm/lib/globals.dart
new file mode 100644
index 0000000..c72aeb1
--- /dev/null
+++ b/pkg/dart2wasm/lib/globals.dart
@@ -0,0 +1,198 @@
+// 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.
+
+import 'package:kernel/ast.dart';
+
+import 'package:wasm_builder/wasm_builder.dart' as w;
+
+import 'package:dart2wasm/translator.dart';
+
+/// Handles lazy initialization of static fields.
+class Globals {
+  final Translator translator;
+
+  final Map<Field, w.Global> globals = {};
+  final Map<Field, w.BaseFunction> globalInitializers = {};
+  final Map<Field, w.Global> globalInitializedFlag = {};
+  final Map<w.HeapType, w.DefinedGlobal> dummyValues = {};
+  late final w.DefinedGlobal dummyGlobal;
+
+  Globals(this.translator) {
+    _initDummyValues();
+  }
+
+  void _initDummyValues() {
+    // Create dummy struct for anyref/eqref/dataref/context dummy values
+    w.StructType structType = translator.structType("#Dummy");
+    w.RefType type = w.RefType.def(structType, nullable: false);
+    dummyGlobal = translator.m.addGlobal(w.GlobalType(type, mutable: false));
+    w.Instructions ib = dummyGlobal.initializer;
+    translator.struct_new(ib, structType);
+    ib.end();
+    dummyValues[w.HeapType.any] = dummyGlobal;
+    dummyValues[w.HeapType.eq] = dummyGlobal;
+    dummyValues[w.HeapType.data] = dummyGlobal;
+  }
+
+  w.Global? prepareDummyValue(w.ValueType type) {
+    if (type is w.RefType && !type.nullable) {
+      w.HeapType heapType = type.heapType;
+      w.DefinedGlobal? global = dummyValues[heapType];
+      if (global != null) return global;
+      if (heapType is w.DefType) {
+        if (heapType is w.StructType) {
+          for (w.FieldType field in heapType.fields) {
+            prepareDummyValue(field.type.unpacked);
+          }
+          global = translator.m.addGlobal(w.GlobalType(type, mutable: false));
+          w.Instructions ib = global.initializer;
+          for (w.FieldType field in heapType.fields) {
+            instantiateDummyValue(ib, field.type.unpacked);
+          }
+          translator.struct_new(ib, heapType);
+          ib.end();
+        } else if (heapType is w.ArrayType) {
+          global = translator.m.addGlobal(w.GlobalType(type, mutable: false));
+          w.Instructions ib = global.initializer;
+          translator.array_init(ib, heapType, 0);
+          ib.end();
+        } else if (heapType is w.FunctionType) {
+          w.DefinedFunction function =
+              translator.m.addFunction(heapType, "#dummy function $heapType");
+          w.Instructions b = function.body;
+          b.unreachable();
+          b.end();
+          global = translator.m.addGlobal(w.GlobalType(type, mutable: false));
+          w.Instructions ib = global.initializer;
+          ib.ref_func(function);
+          ib.end();
+        }
+        dummyValues[heapType] = global!;
+      }
+      return global;
+    }
+
+    return null;
+  }
+
+  void instantiateDummyValue(w.Instructions b, w.ValueType type) {
+    w.Global? global = prepareDummyValue(type);
+    switch (type) {
+      case w.NumType.i32:
+        b.i32_const(0);
+        break;
+      case w.NumType.i64:
+        b.i64_const(0);
+        break;
+      case w.NumType.f32:
+        b.f32_const(0);
+        break;
+      case w.NumType.f64:
+        b.f64_const(0);
+        break;
+      default:
+        if (type is w.RefType) {
+          w.HeapType heapType = type.heapType;
+          if (type.nullable) {
+            b.ref_null(heapType);
+          } else {
+            b.global_get(global!);
+          }
+        } else {
+          throw "Unsupported global type ${type} ($type)";
+        }
+        break;
+    }
+  }
+
+  Constant? _getConstantInitializer(Field variable) {
+    Expression? init = variable.initializer;
+    if (init == null || init is NullLiteral) return NullConstant();
+    if (init is IntLiteral) return IntConstant(init.value);
+    if (init is DoubleLiteral) return DoubleConstant(init.value);
+    if (init is BoolLiteral) return BoolConstant(init.value);
+    if (translator.options.lazyConstants) return null;
+    if (init is StringLiteral) return StringConstant(init.value);
+    if (init is ConstantExpression) return init.constant;
+    return null;
+  }
+
+  /// Return (and if needed create) the Wasm global corresponding to a static
+  /// field.
+  w.Global getGlobal(Field variable) {
+    assert(!variable.isLate);
+    return globals.putIfAbsent(variable, () {
+      w.ValueType type = translator.translateType(variable.type);
+      Constant? init = _getConstantInitializer(variable);
+      if (init != null) {
+        // Initialized to a constant
+        translator.constants.ensureConstant(init);
+        w.DefinedGlobal global = translator.m
+            .addGlobal(w.GlobalType(type, mutable: !variable.isFinal));
+        translator.constants
+            .instantiateConstant(null, global.initializer, init, type);
+        global.initializer.end();
+        return global;
+      } else {
+        if (type is w.RefType && !type.nullable) {
+          // Null signals uninitialized
+          type = type.withNullability(true);
+        } else {
+          // Explicit initialization flag
+          w.DefinedGlobal flag =
+              translator.m.addGlobal(w.GlobalType(w.NumType.i32));
+          flag.initializer.i32_const(0);
+          flag.initializer.end();
+          globalInitializedFlag[variable] = flag;
+        }
+
+        w.DefinedGlobal global = translator.m.addGlobal(w.GlobalType(type));
+        instantiateDummyValue(global.initializer, type);
+        global.initializer.end();
+
+        globalInitializers[variable] =
+            translator.functions.getFunction(variable.fieldReference);
+        return global;
+      }
+    });
+  }
+
+  /// Return the Wasm global containing the flag indicating whether this static
+  /// field has been initialized, if such a flag global is needed.
+  ///
+  /// Note that [getGlobal] must have been called for the field beforehand.
+  w.Global? getGlobalInitializedFlag(Field variable) {
+    return globalInitializedFlag[variable];
+  }
+
+  /// Emit code to read a static field.
+  w.ValueType readGlobal(w.Instructions b, Field variable) {
+    w.Global global = getGlobal(variable);
+    w.BaseFunction? initFunction = globalInitializers[variable];
+    if (initFunction == null) {
+      // Statically initialized
+      b.global_get(global);
+      return global.type.type;
+    }
+    w.Global? flag = globalInitializedFlag[variable];
+    if (flag != null) {
+      // Explicit initialization flag
+      assert(global.type.type == initFunction.type.outputs.single);
+      b.global_get(flag);
+      b.if_(const [], [global.type.type]);
+      b.global_get(global);
+      b.else_();
+      b.call(initFunction);
+      b.end();
+    } else {
+      // Null signals uninitialized
+      w.Label block = b.block(const [], [initFunction.type.outputs.single]);
+      b.global_get(global);
+      b.br_on_non_null(block);
+      b.call(initFunction);
+      b.end();
+    }
+    return initFunction.type.outputs.single;
+  }
+}
diff --git a/pkg/dart2wasm/lib/intrinsics.dart b/pkg/dart2wasm/lib/intrinsics.dart
new file mode 100644
index 0000000..d94cd8c
--- /dev/null
+++ b/pkg/dart2wasm/lib/intrinsics.dart
@@ -0,0 +1,931 @@
+// 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.
+
+import 'package:dart2wasm/class_info.dart';
+import 'package:dart2wasm/code_generator.dart';
+import 'package:dart2wasm/translator.dart';
+
+import 'package:kernel/ast.dart';
+
+import 'package:wasm_builder/wasm_builder.dart' as w;
+
+/// Specialized code generation for external members.
+///
+/// The code is generated either inlined at the call site, or as the body of the
+/// member in [generateMemberIntrinsic].
+class Intrinsifier {
+  final CodeGenerator codeGen;
+  static const w.ValueType boolType = w.NumType.i32;
+  static const w.ValueType intType = w.NumType.i64;
+  static const w.ValueType doubleType = w.NumType.f64;
+
+  static final Map<w.ValueType, Map<w.ValueType, Map<String, CodeGenCallback>>>
+      binaryOperatorMap = {
+    intType: {
+      intType: {
+        '+': (b) => b.i64_add(),
+        '-': (b) => b.i64_sub(),
+        '*': (b) => b.i64_mul(),
+        '~/': (b) => b.i64_div_s(),
+        '&': (b) => b.i64_and(),
+        '|': (b) => b.i64_or(),
+        '^': (b) => b.i64_xor(),
+        '<<': (b) => b.i64_shl(),
+        '>>': (b) => b.i64_shr_s(),
+        '>>>': (b) => b.i64_shr_u(),
+        '<': (b) => b.i64_lt_s(),
+        '<=': (b) => b.i64_le_s(),
+        '>': (b) => b.i64_gt_s(),
+        '>=': (b) => b.i64_ge_s(),
+      }
+    },
+    doubleType: {
+      doubleType: {
+        '+': (b) => b.f64_add(),
+        '-': (b) => b.f64_sub(),
+        '*': (b) => b.f64_mul(),
+        '/': (b) => b.f64_div(),
+        '<': (b) => b.f64_lt(),
+        '<=': (b) => b.f64_le(),
+        '>': (b) => b.f64_gt(),
+        '>=': (b) => b.f64_ge(),
+      }
+    },
+  };
+  static final Map<w.ValueType, Map<String, CodeGenCallback>> unaryOperatorMap =
+      {
+    intType: {
+      'unary-': (b) {
+        b.i64_const(-1);
+        b.i64_mul();
+      },
+      '~': (b) {
+        b.i64_const(-1);
+        b.i64_xor();
+      },
+      'toDouble': (b) {
+        b.f64_convert_i64_s();
+      },
+    },
+    doubleType: {
+      'unary-': (b) {
+        b.f64_neg();
+      },
+      'toInt': (b) {
+        b.i64_trunc_sat_f64_s();
+      },
+      'roundToDouble': (b) {
+        b.f64_nearest();
+      },
+      'floorToDouble': (b) {
+        b.f64_floor();
+      },
+      'ceilToDouble': (b) {
+        b.f64_ceil();
+      },
+      'truncateToDouble': (b) {
+        b.f64_trunc();
+      },
+    },
+  };
+  static final Map<String, w.ValueType> unaryResultMap = {
+    'toDouble': w.NumType.f64,
+    'toInt': w.NumType.i64,
+    'roundToDouble': w.NumType.f64,
+    'floorToDouble': w.NumType.f64,
+    'ceilToDouble': w.NumType.f64,
+    'truncateToDouble': w.NumType.f64,
+  };
+
+  Translator get translator => codeGen.translator;
+  w.Instructions get b => codeGen.b;
+
+  DartType dartTypeOf(Expression exp) => codeGen.dartTypeOf(exp);
+
+  w.ValueType typeOfExp(Expression exp) {
+    return translator.translateType(dartTypeOf(exp));
+  }
+
+  static bool isComparison(String op) =>
+      op == '<' || op == '<=' || op == '>' || op == '>=';
+
+  Intrinsifier(this.codeGen);
+
+  w.ValueType? generateInstanceGetterIntrinsic(InstanceGet node) {
+    DartType receiverType = dartTypeOf(node.receiver);
+    String name = node.name.text;
+
+    // _WasmArray.length
+    if (node.interfaceTarget.enclosingClass == translator.wasmArrayBaseClass) {
+      assert(name == 'length');
+      DartType elementType =
+          (receiverType as InterfaceType).typeArguments.single;
+      w.ArrayType arrayType = translator.arrayTypeForDartType(elementType);
+      Expression array = node.receiver;
+      codeGen.wrap(array, w.RefType.def(arrayType, nullable: true));
+      b.array_len(arrayType);
+      b.i64_extend_i32_u();
+      return w.NumType.i64;
+    }
+
+    // int.bitlength
+    if (node.interfaceTarget.enclosingClass == translator.coreTypes.intClass &&
+        name == 'bitLength') {
+      w.Local temp = codeGen.function.addLocal(w.NumType.i64);
+      b.i64_const(64);
+      codeGen.wrap(node.receiver, w.NumType.i64);
+      b.local_tee(temp);
+      b.local_get(temp);
+      b.i64_const(63);
+      b.i64_shr_s();
+      b.i64_xor();
+      b.i64_clz();
+      b.i64_sub();
+      return w.NumType.i64;
+    }
+
+    return null;
+  }
+
+  w.ValueType? generateInstanceIntrinsic(InstanceInvocation node) {
+    Expression receiver = node.receiver;
+    DartType receiverType = dartTypeOf(receiver);
+    String name = node.name.text;
+    Procedure target = node.interfaceTarget;
+
+    // _TypedListBase._setRange
+    if (target.enclosingClass == translator.typedListBaseClass &&
+        name == "_setRange") {
+      // Always fall back to alternative implementation.
+      b.i32_const(0);
+      return w.NumType.i32;
+    }
+
+    // _TypedList._(get|set)(Int|Uint|Float)(8|16|32|64)
+    if (node.interfaceTarget.enclosingClass == translator.typedListClass) {
+      Match? match = RegExp("^_(get|set)(Int|Uint|Float)(8|16|32|64)\$")
+          .matchAsPrefix(name);
+      if (match != null) {
+        bool setter = match.group(1) == "set";
+        bool signed = match.group(2) == "Int";
+        bool float = match.group(2) == "Float";
+        int bytes = int.parse(match.group(3)!) ~/ 8;
+        bool wide = bytes == 8;
+
+        ClassInfo typedListInfo =
+            translator.classInfo[translator.typedListClass]!;
+        w.RefType arrayType = typedListInfo.struct
+            .fields[FieldIndex.typedListArray].type.unpacked as w.RefType;
+        w.ArrayType arrayHeapType = arrayType.heapType as w.ArrayType;
+        w.ValueType valueType = float ? w.NumType.f64 : w.NumType.i64;
+        w.ValueType intType = wide ? w.NumType.i64 : w.NumType.i32;
+
+        // Prepare array and offset
+        w.Local array = codeGen.addLocal(arrayType);
+        w.Local offset = codeGen.addLocal(w.NumType.i32);
+        codeGen.wrap(receiver, typedListInfo.nullableType);
+        b.struct_get(typedListInfo.struct, FieldIndex.typedListArray);
+        b.local_set(array);
+        codeGen.wrap(node.arguments.positional[0], w.NumType.i64);
+        b.i32_wrap_i64();
+        b.local_set(offset);
+
+        if (setter) {
+          // Setter
+          w.Local value = codeGen.addLocal(intType);
+          codeGen.wrap(node.arguments.positional[1], valueType);
+          if (wide) {
+            if (float) {
+              b.i64_reinterpret_f64();
+            }
+          } else {
+            if (float) {
+              b.f32_demote_f64();
+              b.i32_reinterpret_f32();
+            } else {
+              b.i32_wrap_i64();
+            }
+          }
+          b.local_set(value);
+
+          for (int i = 0; i < bytes; i++) {
+            b.local_get(array);
+            b.local_get(offset);
+            if (i > 0) {
+              b.i32_const(i);
+              b.i32_add();
+            }
+            b.local_get(value);
+            if (i > 0) {
+              if (wide) {
+                b.i64_const(i * 8);
+                b.i64_shr_u();
+              } else {
+                b.i32_const(i * 8);
+                b.i32_shr_u();
+              }
+            }
+            if (wide) {
+              b.i32_wrap_i64();
+            }
+            b.array_set(arrayHeapType);
+          }
+          return translator.voidMarker;
+        } else {
+          // Getter
+          for (int i = 0; i < bytes; i++) {
+            b.local_get(array);
+            b.local_get(offset);
+            if (i > 0) {
+              b.i32_const(i);
+              b.i32_add();
+            }
+            if (signed && i == bytes - 1) {
+              b.array_get_s(arrayHeapType);
+            } else {
+              b.array_get_u(arrayHeapType);
+            }
+            if (wide) {
+              if (signed) {
+                b.i64_extend_i32_s();
+              } else {
+                b.i64_extend_i32_u();
+              }
+            }
+            if (i > 0) {
+              if (wide) {
+                b.i64_const(i * 8);
+                b.i64_shl();
+                b.i64_or();
+              } else {
+                b.i32_const(i * 8);
+                b.i32_shl();
+                b.i32_or();
+              }
+            }
+          }
+
+          if (wide) {
+            if (float) {
+              b.f64_reinterpret_i64();
+            }
+          } else {
+            if (float) {
+              b.f32_reinterpret_i32();
+              b.f64_promote_f32();
+            } else {
+              if (signed) {
+                b.i64_extend_i32_s();
+              } else {
+                b.i64_extend_i32_u();
+              }
+            }
+          }
+          return valueType;
+        }
+      }
+    }
+
+    // WasmIntArray.(readSigned|readUnsigned|write)
+    // WasmFloatArray.(read|write)
+    // WasmObjectArray.(read|write)
+    if (node.interfaceTarget.enclosingClass?.superclass ==
+        translator.wasmArrayBaseClass) {
+      DartType elementType =
+          (receiverType as InterfaceType).typeArguments.single;
+      w.ArrayType arrayType = translator.arrayTypeForDartType(elementType);
+      w.StorageType wasmType = arrayType.elementType.type;
+      bool innerExtend =
+          wasmType == w.PackedType.i8 || wasmType == w.PackedType.i16;
+      bool outerExtend =
+          wasmType.unpacked == w.NumType.i32 || wasmType == w.NumType.f32;
+      switch (name) {
+        case 'read':
+        case 'readSigned':
+        case 'readUnsigned':
+          bool unsigned = name == 'readUnsigned';
+          Expression array = receiver;
+          Expression index = node.arguments.positional.single;
+          codeGen.wrap(array, w.RefType.def(arrayType, nullable: true));
+          codeGen.wrap(index, w.NumType.i64);
+          b.i32_wrap_i64();
+          if (innerExtend) {
+            if (unsigned) {
+              b.array_get_u(arrayType);
+            } else {
+              b.array_get_s(arrayType);
+            }
+          } else {
+            b.array_get(arrayType);
+          }
+          if (outerExtend) {
+            if (wasmType == w.NumType.f32) {
+              b.f64_promote_f32();
+              return w.NumType.f64;
+            } else {
+              if (unsigned) {
+                b.i64_extend_i32_u();
+              } else {
+                b.i64_extend_i32_s();
+              }
+              return w.NumType.i64;
+            }
+          }
+          return wasmType.unpacked;
+        case 'write':
+          Expression array = receiver;
+          Expression index = node.arguments.positional[0];
+          Expression value = node.arguments.positional[1];
+          codeGen.wrap(array, w.RefType.def(arrayType, nullable: true));
+          codeGen.wrap(index, w.NumType.i64);
+          b.i32_wrap_i64();
+          codeGen.wrap(value, typeOfExp(value));
+          if (outerExtend) {
+            if (wasmType == w.NumType.f32) {
+              b.f32_demote_f64();
+            } else {
+              b.i32_wrap_i64();
+            }
+          }
+          b.array_set(arrayType);
+          return codeGen.voidMarker;
+        default:
+          throw "Unsupported array method: $name";
+      }
+    }
+
+    // List.[] on list constants
+    if (receiver is ConstantExpression &&
+        receiver.constant is ListConstant &&
+        name == '[]') {
+      ClassInfo info = translator.classInfo[translator.listBaseClass]!;
+      w.RefType listType = info.nullableType;
+      Field arrayField = translator.listBaseClass.fields
+          .firstWhere((f) => f.name.text == '_data');
+      int arrayFieldIndex = translator.fieldIndex[arrayField]!;
+      w.ArrayType arrayType =
+          (info.struct.fields[arrayFieldIndex].type as w.RefType).heapType
+              as w.ArrayType;
+      codeGen.wrap(receiver, listType);
+      b.struct_get(info.struct, arrayFieldIndex);
+      codeGen.wrap(node.arguments.positional.single, w.NumType.i64);
+      b.i32_wrap_i64();
+      b.array_get(arrayType);
+      return translator.topInfo.nullableType;
+    }
+
+    if (node.arguments.positional.length == 1) {
+      // Binary operator
+      Expression left = node.receiver;
+      Expression right = node.arguments.positional.single;
+      DartType argType = dartTypeOf(right);
+      if (argType is VoidType) return null;
+      w.ValueType leftType = translator.translateType(receiverType);
+      w.ValueType rightType = translator.translateType(argType);
+      var code = binaryOperatorMap[leftType]?[rightType]?[name];
+      if (code != null) {
+        w.ValueType outType = isComparison(name) ? w.NumType.i32 : leftType;
+        codeGen.wrap(left, leftType);
+        codeGen.wrap(right, rightType);
+        code(b);
+        return outType;
+      }
+    } else if (node.arguments.positional.length == 0) {
+      // Unary operator
+      Expression operand = node.receiver;
+      w.ValueType opType = translator.translateType(receiverType);
+      var code = unaryOperatorMap[opType]?[name];
+      if (code != null) {
+        codeGen.wrap(operand, opType);
+        code(b);
+        return unaryResultMap[name] ?? opType;
+      }
+    }
+
+    return null;
+  }
+
+  w.ValueType? generateEqualsIntrinsic(EqualsCall node) {
+    w.ValueType leftType = typeOfExp(node.left);
+    w.ValueType rightType = typeOfExp(node.right);
+
+    if (leftType == boolType && rightType == boolType) {
+      codeGen.wrap(node.left, w.NumType.i32);
+      codeGen.wrap(node.right, w.NumType.i32);
+      b.i32_eq();
+      return w.NumType.i32;
+    }
+
+    if (leftType == intType && rightType == intType) {
+      codeGen.wrap(node.left, w.NumType.i64);
+      codeGen.wrap(node.right, w.NumType.i64);
+      b.i64_eq();
+      return w.NumType.i32;
+    }
+
+    if (leftType == doubleType && rightType == doubleType) {
+      codeGen.wrap(node.left, w.NumType.f64);
+      codeGen.wrap(node.right, w.NumType.f64);
+      b.f64_eq();
+      return w.NumType.i32;
+    }
+
+    return null;
+  }
+
+  w.ValueType? generateStaticGetterIntrinsic(StaticGet node) {
+    Member target = node.target;
+
+    // ClassID getters
+    String? className = translator.getPragma(target, "wasm:class-id");
+    if (className != null) {
+      List<String> libAndClass = className.split("#");
+      Class cls = translator.libraries
+          .firstWhere((l) => l.name == libAndClass[0])
+          .classes
+          .firstWhere((c) => c.name == libAndClass[1]);
+      int classId = translator.classInfo[cls]!.classId;
+      b.i64_const(classId);
+      return w.NumType.i64;
+    }
+
+    return null;
+  }
+
+  w.ValueType? generateStaticIntrinsic(StaticInvocation node) {
+    String name = node.name.text;
+
+    // dart:core static functions
+    if (node.target.enclosingLibrary == translator.coreTypes.coreLibrary) {
+      switch (name) {
+        case "identical":
+          Expression first = node.arguments.positional[0];
+          Expression second = node.arguments.positional[1];
+          DartType boolType = translator.coreTypes.boolNonNullableRawType;
+          InterfaceType intType = translator.coreTypes.intNonNullableRawType;
+          DartType doubleType = translator.coreTypes.doubleNonNullableRawType;
+          List<DartType> types = [dartTypeOf(first), dartTypeOf(second)];
+          if (types.every((t) => t == intType)) {
+            codeGen.wrap(first, w.NumType.i64);
+            codeGen.wrap(second, w.NumType.i64);
+            b.i64_eq();
+            return w.NumType.i32;
+          }
+          if (types.any((t) =>
+              t is InterfaceType &&
+              t != boolType &&
+              t != doubleType &&
+              !translator.hierarchy
+                  .isSubtypeOf(intType.classNode, t.classNode))) {
+            codeGen.wrap(first, w.RefType.eq(nullable: true));
+            codeGen.wrap(second, w.RefType.eq(nullable: true));
+            b.ref_eq();
+            return w.NumType.i32;
+          }
+          break;
+        case "_getHash":
+          Expression arg = node.arguments.positional[0];
+          w.ValueType objectType = translator.objectInfo.nullableType;
+          codeGen.wrap(arg, objectType);
+          b.struct_get(translator.objectInfo.struct, FieldIndex.identityHash);
+          b.i64_extend_i32_u();
+          return w.NumType.i64;
+        case "_setHash":
+          Expression arg = node.arguments.positional[0];
+          Expression hash = node.arguments.positional[1];
+          w.ValueType objectType = translator.objectInfo.nullableType;
+          codeGen.wrap(arg, objectType);
+          codeGen.wrap(hash, w.NumType.i64);
+          b.i32_wrap_i64();
+          b.struct_set(translator.objectInfo.struct, FieldIndex.identityHash);
+          return codeGen.voidMarker;
+      }
+    }
+
+    // dart:_internal static functions
+    if (node.target.enclosingLibrary.name == "dart._internal") {
+      switch (name) {
+        case "unsafeCast":
+          w.ValueType targetType =
+              translator.translateType(node.arguments.types.single);
+          Expression operand = node.arguments.positional.single;
+          return codeGen.wrap(operand, targetType);
+        case "allocateOneByteString":
+          ClassInfo info = translator.classInfo[translator.oneByteStringClass]!;
+          translator.functions.allocateClass(info.classId);
+          w.ArrayType arrayType =
+              translator.wasmArrayType(w.PackedType.i8, "WasmI8");
+          Expression length = node.arguments.positional[0];
+          b.i32_const(info.classId);
+          b.i32_const(initialIdentityHash);
+          codeGen.wrap(length, w.NumType.i64);
+          b.i32_wrap_i64();
+          translator.array_new_default(b, arrayType);
+          translator.struct_new(b, info);
+          return info.nonNullableType;
+        case "writeIntoOneByteString":
+          ClassInfo info = translator.classInfo[translator.oneByteStringClass]!;
+          w.ArrayType arrayType =
+              translator.wasmArrayType(w.PackedType.i8, "WasmI8");
+          Field arrayField = translator.oneByteStringClass.fields
+              .firstWhere((f) => f.name.text == '_array');
+          int arrayFieldIndex = translator.fieldIndex[arrayField]!;
+          Expression string = node.arguments.positional[0];
+          Expression index = node.arguments.positional[1];
+          Expression codePoint = node.arguments.positional[2];
+          codeGen.wrap(string, info.nonNullableType);
+          b.struct_get(info.struct, arrayFieldIndex);
+          codeGen.wrap(index, w.NumType.i64);
+          b.i32_wrap_i64();
+          codeGen.wrap(codePoint, w.NumType.i64);
+          b.i32_wrap_i64();
+          b.array_set(arrayType);
+          return codeGen.voidMarker;
+        case "allocateTwoByteString":
+          ClassInfo info = translator.classInfo[translator.twoByteStringClass]!;
+          translator.functions.allocateClass(info.classId);
+          w.ArrayType arrayType =
+              translator.wasmArrayType(w.PackedType.i16, "WasmI16");
+          Expression length = node.arguments.positional[0];
+          b.i32_const(info.classId);
+          b.i32_const(initialIdentityHash);
+          codeGen.wrap(length, w.NumType.i64);
+          b.i32_wrap_i64();
+          translator.array_new_default(b, arrayType);
+          translator.struct_new(b, info);
+          return info.nonNullableType;
+        case "writeIntoTwoByteString":
+          ClassInfo info = translator.classInfo[translator.twoByteStringClass]!;
+          w.ArrayType arrayType =
+              translator.wasmArrayType(w.PackedType.i16, "WasmI16");
+          Field arrayField = translator.oneByteStringClass.fields
+              .firstWhere((f) => f.name.text == '_array');
+          int arrayFieldIndex = translator.fieldIndex[arrayField]!;
+          Expression string = node.arguments.positional[0];
+          Expression index = node.arguments.positional[1];
+          Expression codePoint = node.arguments.positional[2];
+          codeGen.wrap(string, info.nonNullableType);
+          b.struct_get(info.struct, arrayFieldIndex);
+          codeGen.wrap(index, w.NumType.i64);
+          b.i32_wrap_i64();
+          codeGen.wrap(codePoint, w.NumType.i64);
+          b.i32_wrap_i64();
+          b.array_set(arrayType);
+          return codeGen.voidMarker;
+        case "floatToIntBits":
+          codeGen.wrap(node.arguments.positional.single, w.NumType.f64);
+          b.f32_demote_f64();
+          b.i32_reinterpret_f32();
+          b.i64_extend_i32_u();
+          return w.NumType.i64;
+        case "intBitsToFloat":
+          codeGen.wrap(node.arguments.positional.single, w.NumType.i64);
+          b.i32_wrap_i64();
+          b.f32_reinterpret_i32();
+          b.f64_promote_f32();
+          return w.NumType.f64;
+        case "doubleToIntBits":
+          codeGen.wrap(node.arguments.positional.single, w.NumType.f64);
+          b.i64_reinterpret_f64();
+          return w.NumType.i64;
+        case "intBitsToDouble":
+          codeGen.wrap(node.arguments.positional.single, w.NumType.i64);
+          b.f64_reinterpret_i64();
+          return w.NumType.f64;
+        case "getID":
+          assert(node.target.enclosingClass?.name == "ClassID");
+          ClassInfo info = translator.topInfo;
+          codeGen.wrap(node.arguments.positional.single, info.nullableType);
+          b.struct_get(info.struct, FieldIndex.classId);
+          b.i64_extend_i32_u();
+          return w.NumType.i64;
+      }
+    }
+
+    // Wasm(Int|Float|Object)Array constructors
+    if (node.target.enclosingClass?.superclass ==
+        translator.wasmArrayBaseClass) {
+      Expression length = node.arguments.positional[0];
+      w.ArrayType arrayType =
+          translator.arrayTypeForDartType(node.arguments.types.single);
+      codeGen.wrap(length, w.NumType.i64);
+      b.i32_wrap_i64();
+      translator.array_new_default(b, arrayType);
+      return w.RefType.def(arrayType, nullable: false);
+    }
+
+    return null;
+  }
+
+  bool generateMemberIntrinsic(Reference target, w.DefinedFunction function,
+      List<w.Local> paramLocals, w.Label? returnLabel) {
+    Member member = target.asMember;
+    if (member is! Procedure) return false;
+    String name = member.name.text;
+    FunctionNode functionNode = member.function;
+
+    // Object.==
+    if (member == translator.coreTypes.objectEquals) {
+      b.local_get(paramLocals[0]);
+      b.local_get(paramLocals[1]);
+      b.ref_eq();
+      return true;
+    }
+
+    // Object.runtimeType
+    if (member.enclosingClass == translator.coreTypes.objectClass &&
+        name == "runtimeType") {
+      w.Local receiver = paramLocals[0];
+      ClassInfo info = translator.classInfo[translator.typeClass]!;
+      translator.functions.allocateClass(info.classId);
+      w.ValueType typeListExpectedType = info.struct.fields[3].type.unpacked;
+
+      b.i32_const(info.classId);
+      b.i32_const(initialIdentityHash);
+      b.local_get(receiver);
+      b.struct_get(translator.topInfo.struct, FieldIndex.classId);
+      b.i64_extend_i32_u();
+      // TODO(askesc): Type arguments
+      b.global_get(translator.constants.emptyTypeList);
+      translator.convertType(function,
+          translator.constants.emptyTypeList.type.type, typeListExpectedType);
+      translator.struct_new(b, info);
+
+      return true;
+    }
+
+    // identical
+    if (member == translator.coreTypes.identicalProcedure) {
+      w.Local first = paramLocals[0];
+      w.Local second = paramLocals[1];
+      ClassInfo boolInfo = translator.classInfo[translator.boxedBoolClass]!;
+      ClassInfo intInfo = translator.classInfo[translator.boxedIntClass]!;
+      ClassInfo doubleInfo = translator.classInfo[translator.boxedDoubleClass]!;
+      w.Local cid = function.addLocal(w.NumType.i32);
+      w.Label ref_eq = b.block();
+      b.local_get(first);
+      b.br_on_null(ref_eq);
+      b.struct_get(translator.topInfo.struct, FieldIndex.classId);
+      b.local_tee(cid);
+
+      // Both bool?
+      b.i32_const(boolInfo.classId);
+      b.i32_eq();
+      b.if_();
+      b.local_get(first);
+      translator.ref_cast(b, boolInfo);
+      b.struct_get(boolInfo.struct, FieldIndex.boxValue);
+      w.Label bothBool = b.block(const [], [boolInfo.nullableType]);
+      b.local_get(second);
+      translator.br_on_cast(b, bothBool, boolInfo);
+      b.i32_const(0);
+      b.return_();
+      b.end();
+      b.struct_get(boolInfo.struct, FieldIndex.boxValue);
+      b.i32_eq();
+      b.return_();
+      b.end();
+
+      // Both int?
+      b.local_get(cid);
+      b.i32_const(intInfo.classId);
+      b.i32_eq();
+      b.if_();
+      b.local_get(first);
+      translator.ref_cast(b, intInfo);
+      b.struct_get(intInfo.struct, FieldIndex.boxValue);
+      w.Label bothInt = b.block(const [], [intInfo.nullableType]);
+      b.local_get(second);
+      translator.br_on_cast(b, bothInt, intInfo);
+      b.i32_const(0);
+      b.return_();
+      b.end();
+      b.struct_get(intInfo.struct, FieldIndex.boxValue);
+      b.i64_eq();
+      b.return_();
+      b.end();
+
+      // Both double?
+      b.local_get(cid);
+      b.i32_const(doubleInfo.classId);
+      b.i32_eq();
+      b.if_();
+      b.local_get(first);
+      translator.ref_cast(b, doubleInfo);
+      b.struct_get(doubleInfo.struct, FieldIndex.boxValue);
+      b.i64_reinterpret_f64();
+      w.Label bothDouble = b.block(const [], [doubleInfo.nullableType]);
+      b.local_get(second);
+      translator.br_on_cast(b, bothDouble, doubleInfo);
+      b.i32_const(0);
+      b.return_();
+      b.end();
+      b.struct_get(doubleInfo.struct, FieldIndex.boxValue);
+      b.i64_reinterpret_f64();
+      b.i64_eq();
+      b.return_();
+      b.end();
+
+      // Compare as references
+      b.end();
+      b.local_get(first);
+      b.local_get(second);
+      b.ref_eq();
+
+      return true;
+    }
+
+    // (Int|Uint|Float)(8|16|32|64)(Clamped)?(List|ArrayView) constructors
+    if (member.isExternal &&
+        member.enclosingLibrary.name == "dart.typed_data") {
+      if (member.isFactory) {
+        String className = member.enclosingClass!.name;
+
+        Match? match = RegExp("^(Int|Uint|Float)(8|16|32|64)(Clamped)?List\$")
+            .matchAsPrefix(className);
+        if (match != null) {
+          int shift = int.parse(match.group(2)!).bitLength - 4;
+          Class cls = member.enclosingLibrary.classes
+              .firstWhere((c) => c.name == "_$className");
+          ClassInfo info = translator.classInfo[cls]!;
+          translator.functions.allocateClass(info.classId);
+          w.ArrayType arrayType =
+              translator.wasmArrayType(w.PackedType.i8, "i8");
+
+          w.Local length = paramLocals[0];
+          b.i32_const(info.classId);
+          b.i32_const(initialIdentityHash);
+          b.local_get(length);
+          b.i32_wrap_i64();
+          b.local_get(length);
+          if (shift > 0) {
+            b.i64_const(shift);
+            b.i64_shl();
+          }
+          b.i32_wrap_i64();
+          translator.array_new_default(b, arrayType);
+          translator.struct_new(b, info);
+          return true;
+        }
+
+        match = RegExp("^_(Int|Uint|Float)(8|16|32|64)(Clamped)?ArrayView\$")
+            .matchAsPrefix(className);
+        if (match != null ||
+            member.enclosingClass == translator.byteDataViewClass) {
+          ClassInfo info = translator.classInfo[member.enclosingClass]!;
+          translator.functions.allocateClass(info.classId);
+
+          w.Local buffer = paramLocals[0];
+          w.Local offsetInBytes = paramLocals[1];
+          w.Local length = paramLocals[2];
+          b.i32_const(info.classId);
+          b.i32_const(initialIdentityHash);
+          b.local_get(length);
+          b.i32_wrap_i64();
+          b.local_get(buffer);
+          b.local_get(offsetInBytes);
+          b.i32_wrap_i64();
+          translator.struct_new(b, info);
+          return true;
+        }
+      }
+
+      // _TypedListBase.length
+      // _TypedListView.offsetInBytes
+      // _TypedListView._typedData
+      // _ByteDataView.length
+      // _ByteDataView.offsetInBytes
+      // _ByteDataView._typedData
+      if (member.isGetter) {
+        Class cls = member.enclosingClass!;
+        ClassInfo info = translator.classInfo[cls]!;
+        b.local_get(paramLocals[0]);
+        translator.ref_cast(b, info);
+        switch (name) {
+          case "length":
+            assert(cls == translator.typedListBaseClass ||
+                cls == translator.byteDataViewClass);
+            if (cls == translator.typedListBaseClass) {
+              b.struct_get(info.struct, FieldIndex.typedListBaseLength);
+            } else {
+              b.struct_get(info.struct, FieldIndex.byteDataViewLength);
+            }
+            b.i64_extend_i32_u();
+            return true;
+          case "offsetInBytes":
+            assert(cls == translator.typedListViewClass ||
+                cls == translator.byteDataViewClass);
+            if (cls == translator.typedListViewClass) {
+              b.struct_get(info.struct, FieldIndex.typedListViewOffsetInBytes);
+            } else {
+              b.struct_get(info.struct, FieldIndex.byteDataViewOffsetInBytes);
+            }
+            b.i64_extend_i32_u();
+            return true;
+          case "_typedData":
+            assert(cls == translator.typedListViewClass ||
+                cls == translator.byteDataViewClass);
+            if (cls == translator.typedListViewClass) {
+              b.struct_get(info.struct, FieldIndex.typedListViewTypedData);
+            } else {
+              b.struct_get(info.struct, FieldIndex.byteDataViewTypedData);
+            }
+            return true;
+        }
+        throw "Unrecognized typed data getter: ${cls.name}.$name";
+      }
+    }
+
+    // int members
+    if (member.enclosingClass == translator.boxedIntClass &&
+        member.function.body == null) {
+      String op = member.name.text;
+      if (functionNode.requiredParameterCount == 0) {
+        CodeGenCallback? code = unaryOperatorMap[intType]![op];
+        if (code != null) {
+          w.ValueType resultType = unaryResultMap[op] ?? intType;
+          w.ValueType inputType = function.type.inputs.single;
+          w.ValueType outputType = function.type.outputs.single;
+          b.local_get(function.locals[0]);
+          translator.convertType(function, inputType, intType);
+          code(b);
+          translator.convertType(function, resultType, outputType);
+          return true;
+        }
+      } else if (functionNode.requiredParameterCount == 1) {
+        CodeGenCallback? code = binaryOperatorMap[intType]![intType]![op];
+        if (code != null) {
+          w.ValueType leftType = function.type.inputs[0];
+          w.ValueType rightType = function.type.inputs[1];
+          w.ValueType outputType = function.type.outputs.single;
+          if (rightType == intType) {
+            // int parameter
+            b.local_get(function.locals[0]);
+            translator.convertType(function, leftType, intType);
+            b.local_get(function.locals[1]);
+            code(b);
+            if (!isComparison(op)) {
+              translator.convertType(function, intType, outputType);
+            }
+            return true;
+          }
+          // num parameter
+          ClassInfo intInfo = translator.classInfo[translator.boxedIntClass]!;
+          w.Label intArg = b.block(const [], [intInfo.nonNullableType]);
+          b.local_get(function.locals[1]);
+          translator.br_on_cast(b, intArg, intInfo);
+          // double argument
+          b.drop();
+          b.local_get(function.locals[0]);
+          translator.convertType(function, leftType, intType);
+          b.f64_convert_i64_s();
+          b.local_get(function.locals[1]);
+          translator.convertType(function, rightType, doubleType);
+          // Inline double op
+          CodeGenCallback doubleCode =
+              binaryOperatorMap[doubleType]![doubleType]![op]!;
+          doubleCode(b);
+          if (!isComparison(op)) {
+            translator.convertType(function, doubleType, outputType);
+          }
+          b.return_();
+          b.end();
+          // int argument
+          translator.convertType(function, intInfo.nonNullableType, intType);
+          w.Local rightTemp = function.addLocal(intType);
+          b.local_set(rightTemp);
+          b.local_get(function.locals[0]);
+          translator.convertType(function, leftType, intType);
+          b.local_get(rightTemp);
+          code(b);
+          if (!isComparison(op)) {
+            translator.convertType(function, intType, outputType);
+          }
+          return true;
+        }
+      }
+    }
+
+    // double unary members
+    if (member.enclosingClass == translator.boxedDoubleClass &&
+        member.function.body == null) {
+      String op = member.name.text;
+      if (functionNode.requiredParameterCount == 0) {
+        CodeGenCallback? code = unaryOperatorMap[doubleType]![op];
+        if (code != null) {
+          w.ValueType resultType = unaryResultMap[op] ?? doubleType;
+          w.ValueType inputType = function.type.inputs.single;
+          w.ValueType outputType = function.type.outputs.single;
+          b.local_get(function.locals[0]);
+          translator.convertType(function, inputType, doubleType);
+          code(b);
+          translator.convertType(function, resultType, outputType);
+          return true;
+        }
+      }
+    }
+
+    return false;
+  }
+}
diff --git a/pkg/dart2wasm/lib/param_info.dart b/pkg/dart2wasm/lib/param_info.dart
new file mode 100644
index 0000000..182af47
--- /dev/null
+++ b/pkg/dart2wasm/lib/param_info.dart
@@ -0,0 +1,93 @@
+// 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.
+
+import 'package:dart2wasm/reference_extensions.dart';
+
+import 'package:kernel/ast.dart';
+
+/// Information about optional parameters and their default values for a
+/// member or a set of members belonging to the same override group.
+class ParameterInfo {
+  final Member member;
+  int typeParamCount = 0;
+  late final List<Constant?> positional;
+  late final Map<String, Constant?> named;
+
+  // Do not access these until the info is complete.
+  late final List<String> names = named.keys.toList()..sort();
+  late final Map<String, int> nameIndex = {
+    for (int i = 0; i < names.length; i++) names[i]: positional.length + i
+  };
+
+  int get paramCount => positional.length + named.length;
+
+  static Constant? defaultValue(VariableDeclaration param) {
+    Expression? initializer = param.initializer;
+    if (initializer is ConstantExpression) {
+      return initializer.constant;
+    } else if (initializer == null) {
+      return null;
+    } else {
+      throw "Non-constant default value";
+    }
+  }
+
+  ParameterInfo.fromMember(Reference target) : member = target.asMember {
+    FunctionNode? function = member.function;
+    if (target.isTearOffReference) {
+      positional = [];
+      named = {};
+    } else if (function != null) {
+      typeParamCount = (member is Constructor
+              ? member.enclosingClass!.typeParameters
+              : function.typeParameters)
+          .length;
+      positional = List.generate(function.positionalParameters.length, (i) {
+        // A required parameter has no default value.
+        if (i < function.requiredParameterCount) return null;
+        return defaultValue(function.positionalParameters[i]);
+      });
+      named = {
+        for (VariableDeclaration param in function.namedParameters)
+          param.name!: defaultValue(param)
+      };
+    } else {
+      // A setter parameter has no default value.
+      positional = [if (target.isSetter) null];
+      named = {};
+    }
+  }
+
+  void merge(ParameterInfo other) {
+    assert(typeParamCount == other.typeParamCount);
+    for (int i = 0; i < other.positional.length; i++) {
+      if (i >= positional.length) {
+        positional.add(other.positional[i]);
+      } else {
+        if (positional[i] == null) {
+          positional[i] = other.positional[i];
+        } else if (other.positional[i] != null) {
+          if (positional[i] != other.positional[i]) {
+            print("Mismatching default value for parameter $i: "
+                "${member}: ${positional[i]} vs "
+                "${other.member}: ${other.positional[i]}");
+          }
+        }
+      }
+    }
+    for (String name in other.named.keys) {
+      Constant? value = named[name];
+      Constant? otherValue = other.named[name];
+      if (value == null) {
+        named[name] = otherValue;
+      } else if (otherValue != null) {
+        if (value != otherValue) {
+          print("Mismatching default value for parameter '$name': "
+              "${member}: ${value} vs "
+              "${other.member}: ${otherValue}");
+        }
+      }
+    }
+  }
+}
diff --git a/pkg/dart2wasm/lib/reference_extensions.dart b/pkg/dart2wasm/lib/reference_extensions.dart
new file mode 100644
index 0000000..ba9a68c
--- /dev/null
+++ b/pkg/dart2wasm/lib/reference_extensions.dart
@@ -0,0 +1,62 @@
+// 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.
+
+import 'package:kernel/ast.dart';
+
+// Extend references with flags to more easily identify getters and setters.
+
+extension GetterSetterReference on Reference {
+  bool get isImplicitGetter {
+    Member member = asMember;
+    return member is Field && member.getterReference == this;
+  }
+
+  bool get isImplicitSetter {
+    Member member = asMember;
+    return member is Field && member.setterReference == this;
+  }
+
+  bool get isGetter {
+    Member member = asMember;
+    return member is Procedure && member.isGetter || isImplicitGetter;
+  }
+
+  bool get isSetter {
+    Member member = asMember;
+    return member is Procedure && member.isSetter || isImplicitSetter;
+  }
+}
+
+// Extend procedures with a tearOffReference that refers to the tear-off
+// implementation for that procedure. This enables a Reference to refer to any
+// implementation relating to a member, including its tear-off, which it can't
+// do in plain kernel.
+
+extension TearOffReference on Procedure {
+  // Use an Expando to avoid keeping the procedure alive.
+  static final Expando<Reference> _tearOffReference = Expando();
+
+  Reference get tearOffReference =>
+      _tearOffReference[this] ??= Reference()..node = this;
+}
+
+extension IsTearOffReference on Reference {
+  bool get isTearOffReference {
+    Member member = asMember;
+    return member is Procedure && member.tearOffReference == this;
+  }
+}
+
+extension ReferenceAs on Member {
+  Reference referenceAs({required bool getter, required bool setter}) {
+    Member member = this;
+    return member is Field
+        ? setter
+            ? member.setterReference!
+            : member.getterReference
+        : getter && member is Procedure && member.kind == ProcedureKind.Method
+            ? member.tearOffReference
+            : member.reference;
+  }
+}
diff --git a/pkg/dart2wasm/lib/target.dart b/pkg/dart2wasm/lib/target.dart
new file mode 100644
index 0000000..a7827e1
--- /dev/null
+++ b/pkg/dart2wasm/lib/target.dart
@@ -0,0 +1,191 @@
+// 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.
+
+import 'package:kernel/ast.dart';
+import 'package:kernel/class_hierarchy.dart';
+import 'package:kernel/clone.dart';
+import 'package:kernel/core_types.dart';
+import 'package:kernel/reference_from_index.dart';
+import 'package:kernel/target/changed_structure_notifier.dart';
+import 'package:kernel/target/targets.dart';
+import 'package:kernel/transformations/mixin_full_resolution.dart'
+    as transformMixins show transformLibraries;
+
+import 'package:dart2wasm/constants_backend.dart';
+import 'package:dart2wasm/transformers.dart' as wasmTrans;
+
+class WasmTarget extends Target {
+  Class? _growableList;
+  Class? _immutableList;
+  Class? _immutableMap;
+  Class? _unmodifiableSet;
+  Class? _compactLinkedCustomHashMap;
+  Class? _compactLinkedHashSet;
+  Class? _oneByteString;
+  Class? _twoByteString;
+
+  @override
+  late final ConstantsBackend constantsBackend;
+
+  @override
+  String get name => 'wasm';
+
+  @override
+  TargetFlags get flags => TargetFlags(enableNullSafety: true);
+
+  @override
+  List<String> get extraIndexedLibraries => const <String>[
+        "dart:collection",
+        "dart:typed_data",
+      ];
+
+  void _patchHostEndian(CoreTypes coreTypes) {
+    // Fix Endian.host to be a const field equal to Endian.little instead of
+    // a final field. Wasm is a little-endian platform.
+    // Can't use normal patching process for this because CFE does not
+    // support patching fields.
+    // See http://dartbug.com/32836 for the background.
+    final Field host =
+        coreTypes.index.getField('dart:typed_data', 'Endian', 'host');
+    final Field little =
+        coreTypes.index.getField('dart:typed_data', 'Endian', 'little');
+    host.isConst = true;
+    host.initializer = new CloneVisitorNotMembers().clone(little.initializer!)
+      ..parent = host;
+  }
+
+  @override
+  void performPreConstantEvaluationTransformations(
+      Component component,
+      CoreTypes coreTypes,
+      List<Library> libraries,
+      DiagnosticReporter diagnosticReporter,
+      {void Function(String msg)? logger,
+      ChangedStructureNotifier? changedStructureNotifier}) {
+    constantsBackend = WasmConstantsBackend(coreTypes);
+    _patchHostEndian(coreTypes);
+  }
+
+  @override
+  void performModularTransformationsOnLibraries(
+      Component component,
+      CoreTypes coreTypes,
+      ClassHierarchy hierarchy,
+      List<Library> libraries,
+      Map<String, String>? environmentDefines,
+      DiagnosticReporter diagnosticReporter,
+      ReferenceFromIndex? referenceFromIndex,
+      {void logger(String msg)?,
+      ChangedStructureNotifier? changedStructureNotifier}) {
+    transformMixins.transformLibraries(
+        this, coreTypes, hierarchy, libraries, referenceFromIndex);
+    logger?.call("Transformed mixin applications");
+
+    wasmTrans.transformLibraries(libraries, coreTypes, hierarchy);
+  }
+
+  @override
+  void performTransformationsOnProcedure(
+      CoreTypes coreTypes,
+      ClassHierarchy hierarchy,
+      Procedure procedure,
+      Map<String, String>? environmentDefines,
+      {void logger(String msg)?}) {
+    wasmTrans.transformProcedure(procedure, coreTypes, hierarchy);
+  }
+
+  @override
+  Expression instantiateInvocation(CoreTypes coreTypes, Expression receiver,
+      String name, Arguments arguments, int offset, bool isSuper) {
+    throw "Unsupported: instantiateInvocation";
+  }
+
+  Expression instantiateNoSuchMethodError(CoreTypes coreTypes,
+      Expression receiver, String name, Arguments arguments, int offset,
+      {bool isMethod: false,
+      bool isGetter: false,
+      bool isSetter: false,
+      bool isField: false,
+      bool isLocalVariable: false,
+      bool isDynamic: false,
+      bool isSuper: false,
+      bool isStatic: false,
+      bool isConstructor: false,
+      bool isTopLevel: false}) {
+    throw "Unsupported: instantiateNoSuchMethodError";
+  }
+
+  @override
+  bool get supportsSetLiterals => false;
+
+  @override
+  int get enabledLateLowerings => LateLowering.all;
+
+  @override
+  int get enabledConstructorTearOffLowerings => ConstructorTearOffLowering.all;
+
+  @override
+  bool get supportsExplicitGetterCalls => true;
+
+  @override
+  bool get supportsLateLoweringSentinel => false;
+
+  @override
+  bool get useStaticFieldLowering => false;
+
+  @override
+  bool enableNative(Uri uri) => true;
+
+  @override
+  Class concreteListLiteralClass(CoreTypes coreTypes) {
+    return _growableList ??=
+        coreTypes.index.getClass('dart:core', '_GrowableList');
+  }
+
+  @override
+  Class concreteConstListLiteralClass(CoreTypes coreTypes) {
+    return _immutableList ??=
+        coreTypes.index.getClass('dart:core', '_ImmutableList');
+  }
+
+  @override
+  Class concreteMapLiteralClass(CoreTypes coreTypes) {
+    return _compactLinkedCustomHashMap ??= coreTypes.index
+        .getClass('dart:collection', '_CompactLinkedCustomHashMap');
+  }
+
+  @override
+  Class concreteConstMapLiteralClass(CoreTypes coreTypes) {
+    return _immutableMap ??=
+        coreTypes.index.getClass('dart:collection', '_ImmutableMap');
+  }
+
+  @override
+  Class concreteSetLiteralClass(CoreTypes coreTypes) {
+    return _compactLinkedHashSet ??=
+        coreTypes.index.getClass('dart:collection', '_CompactLinkedHashSet');
+  }
+
+  @override
+  Class concreteConstSetLiteralClass(CoreTypes coreTypes) {
+    return _unmodifiableSet ??=
+        coreTypes.index.getClass('dart:collection', '_UnmodifiableSet');
+  }
+
+  @override
+  Class concreteStringLiteralClass(CoreTypes coreTypes, String value) {
+    const int maxLatin1 = 0xff;
+    for (int i = 0; i < value.length; ++i) {
+      if (value.codeUnitAt(i) > maxLatin1) {
+        return _twoByteString ??=
+            coreTypes.index.getClass('dart:core', '_TwoByteString');
+      }
+    }
+    return _oneByteString ??=
+        coreTypes.index.getClass('dart:core', '_OneByteString');
+  }
+
+  @override
+  bool isSupportedPragma(String pragmaName) => pragmaName.startsWith("wasm:");
+}
diff --git a/pkg/dart2wasm/lib/transformers.dart b/pkg/dart2wasm/lib/transformers.dart
new file mode 100644
index 0000000..83793e3
--- /dev/null
+++ b/pkg/dart2wasm/lib/transformers.dart
@@ -0,0 +1,108 @@
+// 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.
+
+import 'package:kernel/ast.dart';
+import 'package:kernel/class_hierarchy.dart';
+import 'package:kernel/core_types.dart';
+import 'package:kernel/type_environment.dart';
+
+void transformLibraries(
+    List<Library> libraries, CoreTypes coreTypes, ClassHierarchy hierarchy) {
+  final transformer = _WasmTransformer(coreTypes, hierarchy);
+  libraries.forEach(transformer.visitLibrary);
+}
+
+void transformProcedure(
+    Procedure procedure, CoreTypes coreTypes, ClassHierarchy hierarchy) {
+  final transformer = _WasmTransformer(coreTypes, hierarchy);
+  procedure.accept(transformer);
+}
+
+class _WasmTransformer extends Transformer {
+  final TypeEnvironment env;
+
+  Member? _currentMember;
+  StaticTypeContext? _cachedTypeContext;
+
+  StaticTypeContext get typeContext =>
+      _cachedTypeContext ??= StaticTypeContext(_currentMember!, env);
+
+  _WasmTransformer(CoreTypes coreTypes, ClassHierarchy hierarchy)
+      : env = TypeEnvironment(coreTypes, hierarchy);
+
+  @override
+  defaultMember(Member node) {
+    _currentMember = node;
+    _cachedTypeContext = null;
+
+    final result = super.defaultMember(node);
+
+    _currentMember = null;
+    _cachedTypeContext = null;
+    return result;
+  }
+
+  @override
+  TreeNode visitForInStatement(ForInStatement stmt) {
+    // Transform
+    //
+    //   for ({var/final} T <variable> in <iterable>) { ... }
+    //
+    // Into
+    //
+    //  {
+    //    final Iterator<T> #forIterator = <iterable>.iterator;
+    //    for (; #forIterator.moveNext() ;) {
+    //        {var/final} T variable = #forIterator.current;
+    //        ...
+    //      }
+    //    }
+    //  }
+    final CoreTypes coreTypes = typeContext.typeEnvironment.coreTypes;
+
+    // The CFE might invoke this transformation despite the program having
+    // compile-time errors. So we will not transform this [stmt] if the
+    // `stmt.iterable` is an invalid expression or has an invalid type and
+    // instead eliminate the entire for-in and replace it with a invalid
+    // expression statement.
+    final iterable = stmt.iterable;
+    final iterableType = iterable.getStaticType(typeContext);
+    if (iterableType is InvalidType) {
+      return ExpressionStatement(
+          InvalidExpression('Invalid iterable type in for-in'));
+    }
+
+    final DartType elementType = stmt.getElementType(typeContext);
+    final iteratorType = InterfaceType(
+        coreTypes.iteratorClass, Nullability.nonNullable, [elementType]);
+
+    final iterator = VariableDeclaration("#forIterator",
+        initializer: InstanceGet(
+            InstanceAccessKind.Instance, iterable, Name('iterator'),
+            interfaceTarget: coreTypes.iterableGetIterator,
+            resultType: coreTypes.iterableGetIterator.function.returnType)
+          ..fileOffset = iterable.fileOffset,
+        type: iteratorType)
+      ..fileOffset = iterable.fileOffset;
+
+    final condition = InstanceInvocation(InstanceAccessKind.Instance,
+        VariableGet(iterator), Name('moveNext'), Arguments(const []),
+        interfaceTarget: coreTypes.iteratorMoveNext,
+        functionType: coreTypes.iteratorMoveNext.function
+            .computeFunctionType(Nullability.nonNullable))
+      ..fileOffset = iterable.fileOffset;
+
+    final variable = stmt.variable
+      ..initializer = (InstanceGet(
+          InstanceAccessKind.Instance, VariableGet(iterator), Name('current'),
+          interfaceTarget: coreTypes.iteratorGetCurrent,
+          resultType: coreTypes.iteratorGetCurrent.function.returnType)
+        ..fileOffset = stmt.bodyOffset);
+
+    final Block body = Block([variable, stmt.body]);
+
+    return Block([iterator, ForStatement(const [], condition, const [], body)])
+        .accept<TreeNode>(this);
+  }
+}
diff --git a/pkg/dart2wasm/lib/translator.dart b/pkg/dart2wasm/lib/translator.dart
new file mode 100644
index 0000000..7a33ce4
--- /dev/null
+++ b/pkg/dart2wasm/lib/translator.dart
@@ -0,0 +1,819 @@
+// 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.
+
+import 'dart:typed_data';
+
+import 'package:dart2wasm/class_info.dart';
+import 'package:dart2wasm/closures.dart';
+import 'package:dart2wasm/code_generator.dart';
+import 'package:dart2wasm/constants.dart';
+import 'package:dart2wasm/dispatch_table.dart';
+import 'package:dart2wasm/functions.dart';
+import 'package:dart2wasm/globals.dart';
+import 'package:dart2wasm/param_info.dart';
+import 'package:dart2wasm/reference_extensions.dart';
+
+import 'package:kernel/ast.dart';
+import 'package:kernel/class_hierarchy.dart'
+    show ClassHierarchy, ClassHierarchySubtypes, ClosedWorldClassHierarchy;
+import 'package:kernel/core_types.dart';
+import 'package:kernel/src/printer.dart';
+import 'package:kernel/type_environment.dart';
+import 'package:vm/metadata/direct_call.dart';
+
+import 'package:wasm_builder/wasm_builder.dart' as w;
+
+/// Options controlling the translation.
+class TranslatorOptions {
+  bool exportAll = false;
+  bool inlining = false;
+  int inliningLimit = 3;
+  bool lazyConstants = false;
+  bool localNullability = false;
+  bool nameSection = true;
+  bool nominalTypes = false;
+  bool parameterNullability = true;
+  bool polymorphicSpecialization = false;
+  bool printKernel = false;
+  bool printWasm = false;
+  bool runtimeTypes = true;
+  bool stringDataSegments = false;
+  List<int>? watchPoints = null;
+
+  bool get useRttGlobals => runtimeTypes && !nominalTypes;
+}
+
+typedef CodeGenCallback = void Function(w.Instructions);
+
+/// The main entry point for the translation from kernel to Wasm and the hub for
+/// all global state in the compiler.
+///
+/// This class also contains utility methods for types and code generation used
+/// throughout the compiler.
+class Translator {
+  // Options for the translation.
+  final TranslatorOptions options;
+
+  // Kernel input and context.
+  final Component component;
+  final List<Library> libraries;
+  final CoreTypes coreTypes;
+  final TypeEnvironment typeEnvironment;
+  final ClosedWorldClassHierarchy hierarchy;
+  late final ClassHierarchySubtypes subtypes;
+
+  // Classes and members referenced specifically by the compiler.
+  late final Class wasmTypesBaseClass;
+  late final Class wasmArrayBaseClass;
+  late final Class wasmAnyRefClass;
+  late final Class wasmEqRefClass;
+  late final Class wasmDataRefClass;
+  late final Class boxedBoolClass;
+  late final Class boxedIntClass;
+  late final Class boxedDoubleClass;
+  late final Class functionClass;
+  late final Class listBaseClass;
+  late final Class fixedLengthListClass;
+  late final Class growableListClass;
+  late final Class immutableListClass;
+  late final Class stringBaseClass;
+  late final Class oneByteStringClass;
+  late final Class twoByteStringClass;
+  late final Class typeClass;
+  late final Class typedListBaseClass;
+  late final Class typedListClass;
+  late final Class typedListViewClass;
+  late final Class byteDataViewClass;
+  late final Procedure stringEquals;
+  late final Procedure stringInterpolate;
+  late final Procedure mapFactory;
+  late final Procedure mapPut;
+  late final Map<Class, w.StorageType> builtinTypes;
+  late final Map<w.ValueType, Class> boxedClasses;
+
+  // Other parts of the global compiler state.
+  late final ClassInfoCollector classInfoCollector;
+  late final DispatchTable dispatchTable;
+  late final Globals globals;
+  late final Constants constants;
+  late final FunctionCollector functions;
+
+  // Information about the program used and updated by the various phases.
+  final List<ClassInfo> classes = [];
+  final Map<Class, ClassInfo> classInfo = {};
+  final Map<w.HeapType, ClassInfo> classForHeapType = {};
+  final Map<Field, int> fieldIndex = {};
+  final Map<TypeParameter, int> typeParameterIndex = {};
+  final Map<Reference, ParameterInfo> staticParamInfo = {};
+  late Procedure mainFunction;
+  late final w.Module m;
+  late final w.DefinedFunction initFunction;
+  late final w.ValueType voidMarker;
+
+  // Caches for when identical source constructs need a common representation.
+  final Map<w.StorageType, w.ArrayType> arrayTypeCache = {};
+  final Map<int, w.StructType> functionTypeCache = {};
+  final Map<w.StructType, int> functionTypeParameterCount = {};
+  final Map<int, w.DefinedGlobal> functionTypeRtt = {};
+  final Map<w.DefinedFunction, w.DefinedGlobal> functionRefCache = {};
+  final Map<Procedure, w.DefinedFunction> tearOffFunctionCache = {};
+
+  ClassInfo get topInfo => classes[0];
+  ClassInfo get objectInfo => classInfo[coreTypes.objectClass]!;
+
+  Translator(this.component, this.coreTypes, this.typeEnvironment, this.options)
+      : libraries = component.libraries,
+        hierarchy =
+            ClassHierarchy(component, coreTypes) as ClosedWorldClassHierarchy {
+    subtypes = hierarchy.computeSubtypesInformation();
+    classInfoCollector = ClassInfoCollector(this);
+    dispatchTable = DispatchTable(this);
+    functions = FunctionCollector(this);
+
+    Library coreLibrary =
+        component.libraries.firstWhere((l) => l.name == "dart.core");
+    Class lookupCore(String name) {
+      return coreLibrary.classes.firstWhere((c) => c.name == name);
+    }
+
+    Library collectionLibrary =
+        component.libraries.firstWhere((l) => l.name == "dart.collection");
+    Class lookupCollection(String name) {
+      return collectionLibrary.classes.firstWhere((c) => c.name == name);
+    }
+
+    Library typedDataLibrary =
+        component.libraries.firstWhere((l) => l.name == "dart.typed_data");
+    Class lookupTypedData(String name) {
+      return typedDataLibrary.classes.firstWhere((c) => c.name == name);
+    }
+
+    Library wasmLibrary =
+        component.libraries.firstWhere((l) => l.name == "dart.wasm");
+    Class lookupWasm(String name) {
+      return wasmLibrary.classes.firstWhere((c) => c.name == name);
+    }
+
+    wasmTypesBaseClass = lookupWasm("_WasmBase");
+    wasmArrayBaseClass = lookupWasm("_WasmArray");
+    wasmAnyRefClass = lookupWasm("WasmAnyRef");
+    wasmEqRefClass = lookupWasm("WasmEqRef");
+    wasmDataRefClass = lookupWasm("WasmDataRef");
+    boxedBoolClass = lookupCore("_BoxedBool");
+    boxedIntClass = lookupCore("_BoxedInt");
+    boxedDoubleClass = lookupCore("_BoxedDouble");
+    functionClass = lookupCore("_Function");
+    fixedLengthListClass = lookupCore("_List");
+    listBaseClass = lookupCore("_ListBase");
+    growableListClass = lookupCore("_GrowableList");
+    immutableListClass = lookupCore("_ImmutableList");
+    stringBaseClass = lookupCore("_StringBase");
+    oneByteStringClass = lookupCore("_OneByteString");
+    twoByteStringClass = lookupCore("_TwoByteString");
+    typeClass = lookupCore("_Type");
+    typedListBaseClass = lookupTypedData("_TypedListBase");
+    typedListClass = lookupTypedData("_TypedList");
+    typedListViewClass = lookupTypedData("_TypedListView");
+    byteDataViewClass = lookupTypedData("_ByteDataView");
+    stringEquals =
+        stringBaseClass.procedures.firstWhere((p) => p.name.text == "==");
+    stringInterpolate = stringBaseClass.procedures
+        .firstWhere((p) => p.name.text == "_interpolate");
+    mapFactory = lookupCollection("LinkedHashMap").procedures.firstWhere(
+        (p) => p.kind == ProcedureKind.Factory && p.name.text == "_default");
+    mapPut = lookupCollection("_CompactLinkedCustomHashMap")
+        .superclass! // _HashBase
+        .superclass! // _LinkedHashMapMixin<K, V>
+        .procedures
+        .firstWhere((p) => p.name.text == "[]=");
+    builtinTypes = {
+      coreTypes.boolClass: w.NumType.i32,
+      coreTypes.intClass: w.NumType.i64,
+      coreTypes.doubleClass: w.NumType.f64,
+      wasmAnyRefClass: w.RefType.any(nullable: false),
+      wasmEqRefClass: w.RefType.eq(nullable: false),
+      wasmDataRefClass: w.RefType.data(nullable: false),
+      boxedBoolClass: w.NumType.i32,
+      boxedIntClass: w.NumType.i64,
+      boxedDoubleClass: w.NumType.f64,
+      lookupWasm("WasmI8"): w.PackedType.i8,
+      lookupWasm("WasmI16"): w.PackedType.i16,
+      lookupWasm("WasmI32"): w.NumType.i32,
+      lookupWasm("WasmI64"): w.NumType.i64,
+      lookupWasm("WasmF32"): w.NumType.f32,
+      lookupWasm("WasmF64"): w.NumType.f64,
+    };
+    boxedClasses = {
+      w.NumType.i32: boxedBoolClass,
+      w.NumType.i64: boxedIntClass,
+      w.NumType.f64: boxedDoubleClass,
+    };
+  }
+
+  Uint8List translate() {
+    m = w.Module(watchPoints: options.watchPoints);
+    voidMarker = w.RefType.def(w.StructType("void"), nullable: true);
+
+    classInfoCollector.collect();
+
+    functions.collectImportsAndExports();
+    mainFunction =
+        libraries.first.procedures.firstWhere((p) => p.name.text == "main");
+    functions.addExport(mainFunction.reference, "main");
+
+    initFunction = m.addFunction(functionType(const [], const []), "#init");
+    m.startFunction = initFunction;
+
+    globals = Globals(this);
+    constants = Constants(this);
+
+    dispatchTable.build();
+
+    functions.initialize();
+    while (functions.worklist.isNotEmpty) {
+      Reference reference = functions.worklist.removeLast();
+      Member member = reference.asMember;
+      var function =
+          functions.getExistingFunction(reference) as w.DefinedFunction;
+
+      String canonicalName = "$member";
+      if (reference.isSetter) {
+        canonicalName = "$canonicalName=";
+      } else if (reference.isGetter || reference.isTearOffReference) {
+        int dot = canonicalName.indexOf('.');
+        canonicalName = canonicalName.substring(0, dot + 1) +
+            '=' +
+            canonicalName.substring(dot + 1);
+      }
+      canonicalName = member.enclosingLibrary == libraries.first
+          ? canonicalName
+          : "${member.enclosingLibrary.importUri} $canonicalName";
+
+      String? exportName = functions.exports[reference];
+
+      if (options.printKernel || options.printWasm) {
+        if (exportName != null) {
+          print("#${function.index}: $canonicalName (exported as $exportName)");
+        } else {
+          print("#${function.index}: $canonicalName");
+        }
+        print(member.function
+            ?.computeFunctionType(Nullability.nonNullable)
+            .toStringInternal());
+      }
+      if (options.printKernel) {
+        if (member is Constructor) {
+          Class cls = member.enclosingClass;
+          for (Field field in cls.fields) {
+            if (field.isInstanceMember && field.initializer != null) {
+              print("${field.name}: ${field.initializer}");
+            }
+          }
+          for (Initializer initializer in member.initializers) {
+            print(initializer);
+          }
+        }
+        Statement? body = member.function?.body;
+        if (body != null) {
+          print(body);
+        }
+        if (!options.printWasm) print("");
+      }
+
+      if (exportName != null) {
+        m.exportFunction(exportName, function);
+      } else if (options.exportAll) {
+        m.exportFunction(canonicalName, function);
+      }
+      var codeGen = CodeGenerator(this, function, reference);
+      codeGen.generate();
+
+      if (options.printWasm) {
+        print(function.type);
+        print(function.body.trace);
+      }
+
+      for (Lambda lambda in codeGen.closures.lambdas.values) {
+        CodeGenerator(this, lambda.function, reference)
+            .generateLambda(lambda, codeGen.closures);
+        _printFunction(lambda.function, "$canonicalName (closure)");
+      }
+    }
+
+    dispatchTable.output();
+    constants.finalize();
+    initFunction.body.end();
+
+    for (ConstantInfo info in constants.constantInfo.values) {
+      w.DefinedFunction? function = info.function;
+      if (function != null) {
+        _printFunction(function, info.constant);
+      } else {
+        if (options.printWasm) {
+          print("Global #${info.global.index}: ${info.constant}");
+          print(info.global.initializer.trace);
+        }
+      }
+    }
+    if (options.lazyConstants) {
+      _printFunction(constants.oneByteStringFunction, "makeOneByteString");
+      _printFunction(constants.twoByteStringFunction, "makeTwoByteString");
+    }
+    _printFunction(initFunction, "init");
+
+    return m.encode(emitNameSection: options.nameSection);
+  }
+
+  void _printFunction(w.DefinedFunction function, Object name) {
+    if (options.printWasm) {
+      print("#${function.index}: $name");
+      print(function.body.trace);
+    }
+  }
+
+  Class classForType(DartType type) {
+    return type is InterfaceType
+        ? type.classNode
+        : type is TypeParameterType
+            ? classForType(type.bound)
+            : coreTypes.objectClass;
+  }
+
+  w.ValueType translateType(DartType type) {
+    w.StorageType wasmType = translateStorageType(type);
+    if (wasmType is w.ValueType) return wasmType;
+    throw "Packed types are only allowed in arrays and fields";
+  }
+
+  bool isWasmType(Class cls) {
+    while (cls.superclass != null) {
+      cls = cls.superclass!;
+      if (cls == wasmTypesBaseClass) return true;
+    }
+    return false;
+  }
+
+  w.StorageType typeForInfo(ClassInfo info, bool nullable) {
+    Class? cls = info.cls;
+    if (cls != null) {
+      w.StorageType? builtin = builtinTypes[cls];
+      if (builtin != null) {
+        if (!nullable) return builtin;
+        if (isWasmType(cls)) {
+          if (builtin.isPrimitive) throw "Wasm numeric types can't be nullable";
+          return (builtin as w.RefType).withNullability(true);
+        }
+        Class? boxedClass = boxedClasses[builtin];
+        if (boxedClass != null) {
+          info = classInfo[boxedClass]!;
+        }
+      }
+    }
+    return w.RefType.def(info.repr.struct,
+        nullable: !options.parameterNullability || nullable);
+  }
+
+  w.StorageType translateStorageType(DartType type) {
+    if (type is InterfaceType) {
+      if (type.classNode.superclass == wasmArrayBaseClass) {
+        DartType elementType = type.typeArguments.single;
+        return w.RefType.def(arrayTypeForDartType(elementType),
+            nullable: false);
+      }
+      return typeForInfo(
+          classInfo[type.classNode]!, type.isPotentiallyNullable);
+    }
+    if (type is DynamicType) {
+      return topInfo.nullableType;
+    }
+    if (type is NullType) {
+      return topInfo.nullableType;
+    }
+    if (type is NeverType) {
+      return topInfo.nullableType;
+    }
+    if (type is VoidType) {
+      return voidMarker;
+    }
+    if (type is TypeParameterType) {
+      return translateStorageType(type.isPotentiallyNullable
+          ? type.bound.withDeclaredNullability(type.nullability)
+          : type.bound);
+    }
+    if (type is FutureOrType) {
+      return topInfo.nullableType;
+    }
+    if (type is FunctionType) {
+      if (type.requiredParameterCount != type.positionalParameters.length ||
+          type.namedParameters.isNotEmpty) {
+        throw "Function types with optional parameters not supported: $type";
+      }
+      return w.RefType.def(closureStructType(type.requiredParameterCount),
+          nullable:
+              !options.parameterNullability || type.isPotentiallyNullable);
+    }
+    throw "Unsupported type ${type.runtimeType}";
+  }
+
+  w.ArrayType arrayTypeForDartType(DartType type) {
+    while (type is TypeParameterType) type = type.bound;
+    return wasmArrayType(
+        translateStorageType(type), type.toText(defaultAstTextStrategy));
+  }
+
+  w.ArrayType wasmArrayType(w.StorageType type, String name) {
+    return arrayTypeCache.putIfAbsent(
+        type, () => arrayType("Array<$name>", elementType: w.FieldType(type)));
+  }
+
+  w.StructType closureStructType(int parameterCount) {
+    return functionTypeCache.putIfAbsent(parameterCount, () {
+      ClassInfo info = classInfo[functionClass]!;
+      w.StructType struct = structType("Function$parameterCount",
+          fields: info.struct.fields, superType: info.struct);
+      assert(struct.fields.length == FieldIndex.closureFunction);
+      struct.fields.add(w.FieldType(
+          w.RefType.def(closureFunctionType(parameterCount), nullable: false),
+          mutable: false));
+      if (options.useRttGlobals) {
+        functionTypeRtt[parameterCount] =
+            classInfoCollector.makeRtt(struct, info);
+      }
+      functionTypeParameterCount[struct] = parameterCount;
+      return struct;
+    });
+  }
+
+  w.FunctionType closureFunctionType(int parameterCount) {
+    return functionType([
+      w.RefType.data(),
+      ...List<w.ValueType>.filled(parameterCount, topInfo.nullableType)
+    ], [
+      topInfo.nullableType
+    ]);
+  }
+
+  int parameterCountForFunctionStruct(w.HeapType heapType) {
+    return functionTypeParameterCount[heapType]!;
+  }
+
+  w.DefinedGlobal makeFunctionRef(w.DefinedFunction f) {
+    return functionRefCache.putIfAbsent(f, () {
+      w.DefinedGlobal global = m.addGlobal(
+          w.GlobalType(w.RefType.def(f.type, nullable: false), mutable: false));
+      global.initializer.ref_func(f);
+      global.initializer.end();
+      return global;
+    });
+  }
+
+  w.DefinedFunction getTearOffFunction(Procedure member) {
+    return tearOffFunctionCache.putIfAbsent(member, () {
+      assert(member.kind == ProcedureKind.Method);
+      FunctionNode functionNode = member.function;
+      int parameterCount = functionNode.requiredParameterCount;
+      if (functionNode.positionalParameters.length != parameterCount ||
+          functionNode.namedParameters.isNotEmpty) {
+        throw "Not supported: Tear-off with optional parameters"
+            " at ${member.location}";
+      }
+      if (functionNode.typeParameters.isNotEmpty) {
+        throw "Not supported: Tear-off with type parameters"
+            " at ${member.location}";
+      }
+      w.FunctionType memberSignature = signatureFor(member.reference);
+      w.FunctionType closureSignature = closureFunctionType(parameterCount);
+      int signatureOffset = member.isInstanceMember ? 1 : 0;
+      assert(memberSignature.inputs.length == signatureOffset + parameterCount);
+      assert(closureSignature.inputs.length == 1 + parameterCount);
+      w.DefinedFunction function =
+          m.addFunction(closureSignature, "$member (tear-off)");
+      w.BaseFunction target = functions.getFunction(member.reference);
+      w.Instructions b = function.body;
+      for (int i = 0; i < memberSignature.inputs.length; i++) {
+        w.Local paramLocal = function.locals[(1 - signatureOffset) + i];
+        b.local_get(paramLocal);
+        convertType(function, paramLocal.type, memberSignature.inputs[i]);
+      }
+      b.call(target);
+      convertType(function, outputOrVoid(target.type.outputs),
+          outputOrVoid(closureSignature.outputs));
+      b.end();
+      return function;
+    });
+  }
+
+  w.ValueType ensureBoxed(w.ValueType type) {
+    // Box receiver if it's primitive
+    if (type is w.RefType) return type;
+    return w.RefType.def(classInfo[boxedClasses[type]!]!.struct,
+        nullable: false);
+  }
+
+  w.ValueType typeForLocal(w.ValueType type) {
+    return options.localNullability ? type : type.withNullability(true);
+  }
+
+  w.ValueType outputOrVoid(List<w.ValueType> outputs) {
+    return outputs.isEmpty ? voidMarker : outputs.single;
+  }
+
+  bool needsConversion(w.ValueType from, w.ValueType to) {
+    return (from == voidMarker) ^ (to == voidMarker) || !from.isSubtypeOf(to);
+  }
+
+  void convertType(
+      w.DefinedFunction function, w.ValueType from, w.ValueType to) {
+    w.Instructions b = function.body;
+    if (from == voidMarker || to == voidMarker) {
+      if (from != voidMarker) {
+        b.drop();
+        return;
+      }
+      if (to != voidMarker) {
+        if (to is w.RefType && to.nullable) {
+          // This can happen when a void method has its return type overridden to
+          // return a value, in which case the selector signature will have a
+          // non-void return type to encompass all possible return values.
+          b.ref_null(to.heapType);
+        } else {
+          // This only happens in invalid but unreachable code produced by the
+          // TFA dead-code elimination.
+          b.comment("Non-nullable void conversion");
+          b.unreachable();
+        }
+        return;
+      }
+    }
+    if (!from.isSubtypeOf(to)) {
+      if (from is! w.RefType && to is w.RefType) {
+        // Boxing
+        ClassInfo info = classInfo[boxedClasses[from]!]!;
+        assert(info.struct.isSubtypeOf(to.heapType));
+        w.Local temp = function.addLocal(from);
+        b.local_set(temp);
+        b.i32_const(info.classId);
+        b.local_get(temp);
+        struct_new(b, info);
+      } else if (from is w.RefType && to is! w.RefType) {
+        // Unboxing
+        ClassInfo info = classInfo[boxedClasses[to]!]!;
+        if (!from.heapType.isSubtypeOf(info.struct)) {
+          // Cast to box type
+          if (!from.heapType.isSubtypeOf(w.HeapType.data)) {
+            b.ref_as_data();
+          }
+          ref_cast(b, info);
+        }
+        b.struct_get(info.struct, FieldIndex.boxValue);
+      } else if (from.withNullability(false).isSubtypeOf(to)) {
+        // Null check
+        b.ref_as_non_null();
+      } else {
+        // Downcast
+        var heapType = (to as w.RefType).heapType;
+        ClassInfo? info = classForHeapType[heapType];
+        if (from.nullable && !to.nullable) {
+          b.ref_as_non_null();
+        }
+        if (!(from as w.RefType).heapType.isSubtypeOf(w.HeapType.data)) {
+          b.ref_as_data();
+        }
+        ref_cast(
+            b,
+            info ??
+                (heapType.isSubtypeOf(classInfo[functionClass]!.struct)
+                    ? parameterCountForFunctionStruct(heapType)
+                    : heapType));
+      }
+    }
+  }
+
+  w.FunctionType signatureFor(Reference target) {
+    Member member = target.asMember;
+    if (member.isInstanceMember) {
+      return dispatchTable.selectorForTarget(target).signature;
+    } else {
+      return functions.getFunction(target).type;
+    }
+  }
+
+  ParameterInfo paramInfoFor(Reference target) {
+    Member member = target.asMember;
+    if (member.isInstanceMember) {
+      return dispatchTable.selectorForTarget(target).paramInfo;
+    } else {
+      return staticParamInfo.putIfAbsent(
+          target, () => ParameterInfo.fromMember(target));
+    }
+  }
+
+  Member? singleTarget(TreeNode node) {
+    DirectCallMetadataRepository metadata =
+        component.metadata[DirectCallMetadataRepository.repositoryTag]
+            as DirectCallMetadataRepository;
+    return metadata.mapping[node]?.target;
+  }
+
+  bool shouldInline(Reference target) {
+    if (!options.inlining) return false;
+    Member member = target.asMember;
+    if (member is Field) return true;
+    Statement? body = member.function!.body;
+    return body != null &&
+        NodeCounter().countNodes(body) <= options.inliningLimit;
+  }
+
+  T? getPragma<T>(Annotatable node, String name, [T? defaultvalue]) {
+    for (Expression annotation in node.annotations) {
+      if (annotation is ConstantExpression) {
+        Constant constant = annotation.constant;
+        if (constant is InstanceConstant) {
+          if (constant.classNode == coreTypes.pragmaClass) {
+            Constant? nameConstant =
+                constant.fieldValues[coreTypes.pragmaName.fieldReference];
+            if (nameConstant is StringConstant && nameConstant.value == name) {
+              Object? value =
+                  constant.fieldValues[coreTypes.pragmaOptions.fieldReference];
+              if (value is PrimitiveConstant<T>) {
+                return value.value;
+              }
+              return value as T? ?? defaultvalue;
+            }
+          }
+        }
+      }
+    }
+    return null;
+  }
+
+  // Wrappers for type creation to abstract over equi-recursive versus nominal
+  // typing. The given supertype is ignored when nominal types are disabled,
+  // and a suitable default is inserted when nominal types are enabled.
+
+  w.FunctionType functionType(
+      Iterable<w.ValueType> inputs, Iterable<w.ValueType> outputs,
+      {w.HeapType? superType}) {
+    return m.addFunctionType(inputs, outputs,
+        superType: options.nominalTypes ? superType ?? w.HeapType.func : null);
+  }
+
+  w.StructType structType(String name,
+      {Iterable<w.FieldType>? fields, w.HeapType? superType}) {
+    return m.addStructType(name,
+        fields: fields,
+        superType: options.nominalTypes ? superType ?? w.HeapType.data : null);
+  }
+
+  w.ArrayType arrayType(String name,
+      {w.FieldType? elementType, w.HeapType? superType}) {
+    return m.addArrayType(name,
+        elementType: elementType,
+        superType: options.nominalTypes ? superType ?? w.HeapType.data : null);
+  }
+
+  // Wrappers for object allocation and cast instructions to abstract over
+  // RTT-based and static versions of the instructions.
+  // The [type] parameter taken by the methods is either a [ClassInfo] (to use
+  // the RTT for the class), an [int] (to use the RTT for the closure struct
+  // corresponding to functions with that number of parameters) or a
+  // [w.DataType] (to use the canonical RTT for the type).
+
+  void struct_new(w.Instructions b, Object type) {
+    if (options.runtimeTypes) {
+      final struct = _emitRtt(b, type) as w.StructType;
+      b.struct_new_with_rtt(struct);
+    } else {
+      b.struct_new(_targetType(type) as w.StructType);
+    }
+  }
+
+  void struct_new_default(w.Instructions b, Object type) {
+    if (options.runtimeTypes) {
+      final struct = _emitRtt(b, type) as w.StructType;
+      b.struct_new_default_with_rtt(struct);
+    } else {
+      b.struct_new_default(_targetType(type) as w.StructType);
+    }
+  }
+
+  void array_new(w.Instructions b, w.ArrayType type) {
+    if (options.runtimeTypes) {
+      b.rtt_canon(type);
+      b.array_new_with_rtt(type);
+    } else {
+      b.array_new(type);
+    }
+  }
+
+  void array_new_default(w.Instructions b, w.ArrayType type) {
+    if (options.runtimeTypes) {
+      b.rtt_canon(type);
+      b.array_new_default_with_rtt(type);
+    } else {
+      b.array_new_default(type);
+    }
+  }
+
+  void array_init(w.Instructions b, w.ArrayType type, int length) {
+    if (options.runtimeTypes) {
+      b.rtt_canon(type);
+      b.array_init(type, length);
+    } else {
+      b.array_init_static(type, length);
+    }
+  }
+
+  void array_init_from_data(
+      w.Instructions b, w.ArrayType type, w.DataSegment data) {
+    if (options.runtimeTypes) {
+      b.rtt_canon(type);
+      b.array_init_from_data(type, data);
+    } else {
+      b.array_init_from_data_static(type, data);
+    }
+  }
+
+  void ref_test(w.Instructions b, Object type) {
+    if (options.runtimeTypes) {
+      _emitRtt(b, type);
+      b.ref_test();
+    } else {
+      b.ref_test_static(_targetType(type));
+    }
+  }
+
+  void ref_cast(w.Instructions b, Object type) {
+    if (options.runtimeTypes) {
+      _emitRtt(b, type);
+      b.ref_cast();
+    } else {
+      b.ref_cast_static(_targetType(type));
+    }
+  }
+
+  void br_on_cast(w.Instructions b, w.Label label, Object type) {
+    if (options.runtimeTypes) {
+      _emitRtt(b, type);
+      b.br_on_cast(label);
+    } else {
+      b.br_on_cast_static(label, _targetType(type));
+    }
+  }
+
+  void br_on_cast_fail(w.Instructions b, w.Label label, Object type) {
+    if (options.runtimeTypes) {
+      _emitRtt(b, type);
+      b.br_on_cast_fail(label);
+    } else {
+      b.br_on_cast_static_fail(label, _targetType(type));
+    }
+  }
+
+  w.DefType _emitRtt(w.Instructions b, Object type) {
+    if (type is ClassInfo) {
+      if (options.nominalTypes) {
+        b.rtt_canon(type.struct);
+      } else {
+        b.global_get(type.rtt);
+      }
+      return type.struct;
+    } else if (type is int) {
+      int parameterCount = type;
+      w.StructType struct = closureStructType(parameterCount);
+      if (options.nominalTypes) {
+        b.rtt_canon(struct);
+      } else {
+        w.DefinedGlobal rtt = functionTypeRtt[parameterCount]!;
+        b.global_get(rtt);
+      }
+      return struct;
+    } else {
+      b.rtt_canon(type as w.DataType);
+      return type;
+    }
+  }
+
+  w.DefType _targetType(Object type) => type is ClassInfo
+      ? type.struct
+      : type is int
+          ? closureStructType(type)
+          : type as w.DefType;
+}
+
+class NodeCounter extends Visitor<void> with VisitorVoidMixin {
+  int count = 0;
+
+  int countNodes(Node node) {
+    count = 0;
+    node.accept(this);
+    return count;
+  }
+
+  @override
+  void defaultNode(Node node) {
+    count++;
+    node.visitChildren(this);
+  }
+}
diff --git a/pkg/dart2wasm/pubspec.yaml b/pkg/dart2wasm/pubspec.yaml
new file mode 100644
index 0000000..dd0b8de
--- /dev/null
+++ b/pkg/dart2wasm/pubspec.yaml
@@ -0,0 +1,26 @@
+name: dart2wasm
+# This package is not intended for consumption on pub.dev. DO NOT publish.
+publish_to: none
+environment:
+  sdk: '>=2.12.0'
+
+dependencies:
+  front_end:
+    path: ../front_end
+  kernel:
+    path: ../kernel
+  vm:
+    path: ../vm
+  wasm_builder:
+    path: ../wasm_builder
+
+dependency_overrides:
+  # Packages with source in the SDK
+  front_end:
+    path: ../front_end
+  kernel:
+    path: ../kernel
+  vm:
+    path: ../vm
+  wasm_builder:
+    path: ../wasm_builder
diff --git a/pkg/front_end/lib/src/fasta/kernel/kernel_target.dart b/pkg/front_end/lib/src/fasta/kernel/kernel_target.dart
index 7952b4b..755208c 100644
--- a/pkg/front_end/lib/src/fasta/kernel/kernel_target.dart
+++ b/pkg/front_end/lib/src/fasta/kernel/kernel_target.dart
@@ -422,6 +422,17 @@
     }, () => loader.currentUriForCrashReporting);
   }
 
+  /// Builds [augmentationLibraries] to the state expected after applying phase
+  /// 1 macros.
+  Future<void> _buildForPhase1(
+      Iterable<SourceLibraryBuilder> augmentationLibraries) async {
+    await loader.buildOutlines();
+    loader.computeLibraryScopes(augmentationLibraries);
+    // TODO(johnniwinther): Support computation of macro applications in
+    // augmentation libraries?
+    loader.resolveTypes(augmentationLibraries);
+  }
+
   Future<BuildResult> buildOutlines({CanonicalName? nameRoot}) async {
     if (loader.first == null) return new BuildResult();
     return withCrashReporting<BuildResult>(() async {
@@ -441,7 +452,7 @@
       }
 
       benchmarker?.enterPhase(BenchmarkPhases.outline_computeLibraryScopes);
-      loader.computeLibraryScopes();
+      loader.computeLibraryScopes(loader.libraryBuilders);
 
       benchmarker?.enterPhase(BenchmarkPhases.outline_computeMacroApplications);
       MacroApplications? macroApplications =
@@ -451,10 +462,10 @@
       setupTopAndBottomTypes();
 
       benchmarker?.enterPhase(BenchmarkPhases.outline_resolveTypes);
-      loader.resolveTypes();
+      loader.resolveTypes(loader.sourceLibraryBuilders);
 
       benchmarker?.enterPhase(BenchmarkPhases.outline_computeVariances);
-      loader.computeVariances();
+      loader.computeVariances(loader.sourceLibraryBuilders);
 
       benchmarker?.enterPhase(BenchmarkPhases.outline_computeDefaultTypes);
       loader.computeDefaultTypes(
@@ -462,7 +473,9 @@
 
       if (macroApplications != null) {
         benchmarker?.enterPhase(BenchmarkPhases.outline_applyTypeMacros);
-        await macroApplications.applyTypeMacros();
+        List<SourceLibraryBuilder> augmentationLibraries =
+            await macroApplications.applyTypeMacros();
+        await _buildForPhase1(augmentationLibraries);
       }
 
       benchmarker?.enterPhase(BenchmarkPhases.outline_checkSemantics);
diff --git a/pkg/front_end/lib/src/fasta/kernel/macro.dart b/pkg/front_end/lib/src/fasta/kernel/macro.dart
index c14a5cd..6f6abea 100644
--- a/pkg/front_end/lib/src/fasta/kernel/macro.dart
+++ b/pkg/front_end/lib/src/fasta/kernel/macro.dart
@@ -78,12 +78,14 @@
 
 class MacroApplicationDataForTesting {
   Map<SourceLibraryBuilder, LibraryMacroApplicationData> libraryData = {};
-  Map<SourceClassBuilder, String> classTypesResults = {};
+  Map<SourceLibraryBuilder, String> libraryTypesResult = {};
+  Map<SourceClassBuilder, List<macro.MacroExecutionResult>> classTypesResults =
+      {};
   Map<SourceClassBuilder, List<macro.MacroExecutionResult>>
       classDeclarationsResults = {};
   Map<SourceClassBuilder, List<macro.MacroExecutionResult>>
       classDefinitionsResults = {};
-  Map<MemberBuilder, String> memberTypesResults = {};
+  Map<MemberBuilder, List<macro.MacroExecutionResult>> memberTypesResults = {};
   Map<MemberBuilder, List<macro.MacroExecutionResult>>
       memberDeclarationsResults = {};
   Map<MemberBuilder, List<macro.MacroExecutionResult>>
@@ -235,6 +237,8 @@
           uri = typeDeclarationBuilder.library.importUri;
         } else if (typeDeclarationBuilder is TypeAliasBuilder) {
           uri = typeDeclarationBuilder.library.importUri;
+        } else if (identifier.name == 'dynamic') {
+          uri = Uri.parse('dart:core');
         }
         return new macro.ResolvedIdentifier(
             kind: macro.IdentifierKind.topLevelMember,
@@ -252,19 +256,33 @@
       }
     } else {
       // TODO(johnniwinther): Use [_IdentifierImpl] for all identifiers.
-      return new macro.ResolvedIdentifier(
-          kind: macro.IdentifierKind.topLevelMember,
-          name: identifier.name,
-          staticScope: null,
-          uri: null);
+      if (identical(identifier, dynamicIdentifier)) {
+        return new macro.ResolvedIdentifier(
+            kind: macro.IdentifierKind.topLevelMember,
+            name: identifier.name,
+            staticScope: null,
+            uri: Uri.parse('dart:core'));
+      } else {
+        return new macro.ResolvedIdentifier(
+            kind: macro.IdentifierKind.topLevelMember,
+            name: identifier.name,
+            staticScope: null,
+            uri: null);
+      }
     }
   }
 
-  Future<void> _applyMacros(
-      Future<void> Function(Builder, macro.Declaration, List<MacroApplication>)
-          applyMacros) async {
+  Future<Map<SourceLibraryBuilder, List<macro.MacroExecutionResult>>>
+      _applyMacros(
+          Future<List<macro.MacroExecutionResult>> Function(
+                  Builder, macro.Declaration, List<MacroApplication>)
+              applyMacros) async {
+    Map<SourceLibraryBuilder, List<macro.MacroExecutionResult>> libraryResults =
+        {};
     for (MapEntry<SourceLibraryBuilder,
         LibraryMacroApplicationData> libraryEntry in libraryData.entries) {
+      SourceLibraryBuilder libraryBuilder = libraryEntry.key;
+      List<macro.MacroExecutionResult> results = [];
       LibraryMacroApplicationData libraryMacroApplicationData =
           libraryEntry.value;
       for (MapEntry<MemberBuilder, List<MacroApplication>> memberEntry
@@ -272,7 +290,8 @@
         MemberBuilder memberBuilder = memberEntry.key;
         macro.Declaration? declaration = _getMemberDeclaration(memberBuilder);
         if (declaration != null) {
-          await applyMacros(memberBuilder, declaration, memberEntry.value);
+          results.addAll(
+              await applyMacros(memberBuilder, declaration, memberEntry.value));
         }
       }
       for (MapEntry<SourceClassBuilder, ClassMacroApplicationData> classEntry
@@ -283,18 +302,22 @@
         if (classApplications != null) {
           macro.ClassDeclaration classDeclaration =
               _getClassDeclaration(classBuilder);
-          await applyMacros(classBuilder, classDeclaration, classApplications);
+          results.addAll(await applyMacros(
+              classBuilder, classDeclaration, classApplications));
         }
         for (MapEntry<MemberBuilder, List<MacroApplication>> memberEntry
             in classData.memberApplications.entries) {
           MemberBuilder memberBuilder = memberEntry.key;
           macro.Declaration? declaration = _getMemberDeclaration(memberBuilder);
           if (declaration != null) {
-            await applyMacros(memberBuilder, declaration, memberEntry.value);
+            results.addAll(await applyMacros(
+                memberBuilder, declaration, memberEntry.value));
           }
         }
       }
+      libraryResults[libraryBuilder] = results;
     }
+    return libraryResults;
   }
 
   Future<List<macro.MacroExecutionResult>> _applyTypeMacros(
@@ -311,20 +334,33 @@
         results.add(result);
       }
     }
-    String result =
-        _macroExecutor.buildAugmentationLibrary(results, _resolveIdentifier);
+
     if (retainDataForTesting) {
       if (builder is SourceClassBuilder) {
-        dataForTesting?.classTypesResults[builder] = result;
+        dataForTesting?.classTypesResults[builder] = results;
       } else {
-        dataForTesting?.memberTypesResults[builder as MemberBuilder] = result;
+        dataForTesting?.memberTypesResults[builder as MemberBuilder] = results;
       }
     }
     return results;
   }
 
-  Future<void> applyTypeMacros() async {
-    await _applyMacros(_applyTypeMacros);
+  Future<List<SourceLibraryBuilder>> applyTypeMacros() async {
+    List<SourceLibraryBuilder> augmentationLibraries = [];
+    Map<SourceLibraryBuilder, List<macro.MacroExecutionResult>> results =
+        await _applyMacros(_applyTypeMacros);
+    for (MapEntry<SourceLibraryBuilder, List<macro.MacroExecutionResult>> entry
+        in results.entries) {
+      SourceLibraryBuilder sourceLibraryBuilder = entry.key;
+      String result = _macroExecutor.buildAugmentationLibrary(
+          entry.value, _resolveIdentifier);
+      if (retainDataForTesting) {
+        dataForTesting?.libraryTypesResult[sourceLibraryBuilder] = result;
+      }
+      augmentationLibraries
+          .add(await sourceLibraryBuilder.createAugmentationLibrary(result));
+    }
+    return augmentationLibraries;
   }
 
   Future<List<macro.MacroExecutionResult>> _applyDeclarationsMacros(
diff --git a/pkg/front_end/lib/src/fasta/source/source_library_builder.dart b/pkg/front_end/lib/src/fasta/source/source_library_builder.dart
index a5a4d57..70e9378 100644
--- a/pkg/front_end/lib/src/fasta/source/source_library_builder.dart
+++ b/pkg/front_end/lib/src/fasta/source/source_library_builder.dart
@@ -504,6 +504,28 @@
     (_patchLibraries ??= []).add(patchLibrary);
   }
 
+  /// Creates a synthesized augmentation library for the [source] code and
+  /// attach it as a patch library of this library.
+  ///
+  /// To support the parser of the [source], the library is registered as an
+  /// unparsed library on the [loader].
+  SourceLibraryBuilder createAugmentationLibrary(String source) {
+    int index = _patchLibraries?.length ?? 0;
+    Uri uri = new Uri(
+        scheme: 'org-dartlang-augmentation', path: '${fileUri.path}-$index');
+    SourceLibraryBuilder augmentationLibrary = new SourceLibraryBuilder(
+        fileUri: uri,
+        importUri: uri,
+        packageLanguageVersion: packageLanguageVersion,
+        loader: loader,
+        isUnsupported: false,
+        target: library,
+        origin: this);
+    addPatchLibrary(augmentationLibrary);
+    loader.registerUnparsedLibrarySource(augmentationLibrary, source);
+    return augmentationLibrary;
+  }
+
   List<NamedTypeBuilder> get unresolvedNamedTypes =>
       _libraryTypeParameterScopeBuilder.unresolvedNamedTypes;
 
diff --git a/pkg/front_end/lib/src/fasta/source/source_loader.dart b/pkg/front_end/lib/src/fasta/source/source_loader.dart
index 3130c75..a429ef0 100644
--- a/pkg/front_end/lib/src/fasta/source/source_loader.dart
+++ b/pkg/front_end/lib/src/fasta/source/source_loader.dart
@@ -160,7 +160,8 @@
 
   List<SourceLibraryBuilder>? _sourceLibraryBuilders;
 
-  final Queue<LibraryBuilder> _unparsedLibraries = new Queue<LibraryBuilder>();
+  final Queue<SourceLibraryBuilder> _unparsedLibraries =
+      new Queue<SourceLibraryBuilder>();
 
   final List<Library> libraries = <Library>[];
 
@@ -964,12 +965,25 @@
     _typeInferenceEngine!.typeDependencies[member] = typeDependency;
   }
 
+  /// Registers the [library] as unparsed with the given [source] code.
+  ///
+  /// This is used for creating synthesized augmentation libraries.
+  void registerUnparsedLibrarySource(
+      SourceLibraryBuilder library, String source) {
+    List<int> codeUnits = source.codeUnits;
+    Uint8List bytes = new Uint8List(codeUnits.length + 1);
+    bytes.setRange(0, codeUnits.length, codeUnits);
+    sourceBytes[library.fileUri] = bytes;
+    _unparsedLibraries.addLast(library);
+  }
+
+  /// Runs the [OutlineBuilder] on the source of all [_unparsedLibraries].
   Future<void> buildOutlines() async {
     _ensureCoreLibrary();
     while (_unparsedLibraries.isNotEmpty) {
-      LibraryBuilder library = _unparsedLibraries.removeFirst();
+      SourceLibraryBuilder library = _unparsedLibraries.removeFirst();
       currentUriForCrashReporting = library.importUri;
-      await buildOutline(library as SourceLibraryBuilder);
+      await buildOutline(library);
     }
     currentUriForCrashReporting = null;
     logSummary(outlineSummaryTemplate);
@@ -1256,7 +1270,8 @@
     ticker.logMs("Applied patches");
   }
 
-  void computeLibraryScopes() {
+  /// Compute library scopes for [libraryBuilders].
+  void computeLibraryScopes(Iterable<LibraryBuilder> libraryBuilders) {
     Set<LibraryBuilder> exporters = new Set<LibraryBuilder>();
     Set<LibraryBuilder> exportees = new Set<LibraryBuilder>();
     for (LibraryBuilder library in libraryBuilders) {
@@ -1347,9 +1362,10 @@
     });
   }
 
-  void resolveTypes() {
+  /// Resolve [NamedTypeBuilder]s in [libraryBuilders].
+  void resolveTypes(Iterable<SourceLibraryBuilder> libraryBuilders) {
     int typeCount = 0;
-    for (SourceLibraryBuilder library in sourceLibraryBuilders) {
+    for (SourceLibraryBuilder library in libraryBuilders) {
       typeCount += library.resolveTypes();
     }
     ticker.logMs("Resolved $typeCount types");
@@ -1697,9 +1713,10 @@
     ticker.logMs("Resolved $count type-variable bounds");
   }
 
-  void computeVariances() {
+  /// Computes variances of type parameters on typedefs in [libraryBuilders].
+  void computeVariances(Iterable<SourceLibraryBuilder> libraryBuilders) {
     int count = 0;
-    for (SourceLibraryBuilder library in sourceLibraryBuilders) {
+    for (SourceLibraryBuilder library in libraryBuilders) {
       count += library.computeVariances();
     }
     ticker.logMs("Computed variances of $count type variables");
diff --git a/pkg/front_end/lib/src/testing/compiler_common.dart b/pkg/front_end/lib/src/testing/compiler_common.dart
index 6c2620b..407f814 100644
--- a/pkg/front_end/lib/src/testing/compiler_common.dart
+++ b/pkg/front_end/lib/src/testing/compiler_common.dart
@@ -114,9 +114,13 @@
   }
 }
 
+const String _testUriScheme = 'org-dartlang-test';
+
+bool isTestUri(Uri uri) => uri.isScheme(_testUriScheme);
+
 /// A fake absolute directory used as the root of a memory-file system in the
 /// helpers above.
-Uri _defaultDir = Uri.parse('org-dartlang-test:///a/b/c/');
+Uri _defaultDir = Uri.parse('${_testUriScheme}:///a/b/c/');
 
 /// Convert relative file paths into an absolute Uri as expected by the test
 /// helpers above.
diff --git a/pkg/front_end/lib/src/testing/id_testing_helper.dart b/pkg/front_end/lib/src/testing/id_testing_helper.dart
index 48fe099..be478b3 100644
--- a/pkg/front_end/lib/src/testing/id_testing_helper.dart
+++ b/pkg/front_end/lib/src/testing/id_testing_helper.dart
@@ -85,6 +85,10 @@
   dynamic customizeCompilerOptions(
           CompilerOptions options, TestData testData) =>
       null;
+
+  /// Called after running test on [testData] with the resulting
+  /// [testResultData].
+  void onCompilationResult(TestData testData, TestResultData testResultData) {}
 }
 
 abstract class DataComputer<T> {
@@ -369,6 +373,7 @@
 
   TestResultData testResultData =
       new TestResultData(config, customData, compilerResult);
+  config.onCompilationResult(testData, testResultData);
 
   Component component = compilerResult.component!;
   Map<Uri, Map<Id, ActualData<T>>> actualMaps = <Uri, Map<Id, ActualData<T>>>{};
diff --git a/pkg/front_end/test/macro_application/data/pkgs/macro/lib/macro.dart b/pkg/front_end/test/macro_application/data/pkgs/macro/lib/macro.dart
index a394202..5f4db94 100644
--- a/pkg/front_end/test/macro_application/data/pkgs/macro/lib/macro.dart
+++ b/pkg/front_end/test/macro_application/data/pkgs/macro/lib/macro.dart
@@ -48,8 +48,10 @@
       TypeBuilder builder) {
     var name = '${function.identifier.name}GeneratedClass';
     builder.declareType(
-        name, new DeclarationCode.fromParts(['class $name<T extends ',
-      function.returnType.code, '> {}']));
+        name, new DeclarationCode.fromParts(['''
+class $name {
+  external ''', function.returnType.code, ''' method();
+}''']));
   }
 }
 
diff --git a/pkg/front_end/test/macro_application/data/tests/declarations.dart b/pkg/front_end/test/macro_application/data/tests/declarations.dart
index 0f95ed9..dfed26d 100644
--- a/pkg/front_end/test/macro_application/data/tests/declarations.dart
+++ b/pkg/front_end/test/macro_application/data/tests/declarations.dart
@@ -2,66 +2,62 @@
 // 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.
 
+/*library: 
+
+
+*/
+
 import 'package:macro/macro.dart';
 
 /*member: topLevelFunction1:
-
 void topLevelFunction1GeneratedMethod_() {}
 */
 @FunctionDeclarationsMacro1()
 void topLevelFunction1() {}
 
 /*member: topLevelFunction2:
-
 void topLevelFunction2GeneratedMethod_e() {}
 */
 @FunctionDeclarationsMacro1()
 external void topLevelFunction2();
 
 /*member: topLevelField1:
-
 void topLevelField1GeneratedMethod_() {}
 */
 @VariableDeclarationsMacro1()
 int? topLevelField1;
 
 /*member: topLevelField2:
-
 void topLevelField2GeneratedMethod_e() {}
 */
 @VariableDeclarationsMacro1()
 external int? topLevelField2;
 
 /*member: topLevelField3:
-
 void topLevelField3GeneratedMethod_f() {}
 */
 @VariableDeclarationsMacro1()
 final int? topLevelField3 = null;
 
 /*member: topLevelField4:
-
 void topLevelField4GeneratedMethod_l() {}
 */
 @VariableDeclarationsMacro1()
 late int? topLevelField4;
 
 /*member: topLevelGetter1:
-
 void topLevelGetter1GeneratedMethod_g() {}
 */
 @FunctionDeclarationsMacro1()
 int? get topLevelGetter1 => null;
 
 /*member: topLevelSetter1=:
-
 void topLevelSetter1GeneratedMethod_s() {}
 */
 @FunctionDeclarationsMacro1()
 void set topLevelSetter1(int? value) {}
 
 /*class: Class1:
-
 void Class1GeneratedMethod_() {}
 
 void Class1Introspection() {
@@ -74,7 +70,6 @@
 @ClassDeclarationsMacro2()
 class Class1 {
   /*member: Class1.:
-
 augment class Class1 {
 void Class1_GeneratedMethod_() {}
 
@@ -83,7 +78,6 @@
   Class1();
 
   /*member: Class1.redirect:
-
 augment class Class1 {
 void Class1_redirectGeneratedMethod_f() {}
 
@@ -92,7 +86,6 @@
   factory Class1.redirect() = Class1;
 
   /*member: Class1.fact:
-
 augment class Class1 {
 void Class1_factGeneratedMethod_f() {}
 
@@ -101,49 +94,42 @@
   factory Class1.fact() => new Class1();
 
   /*member: Class1.instanceMethod1:
-
 void Class1_instanceMethod1GeneratedMethod_() {}
 */
   @MethodDeclarationsMacro1()
   void instanceMethod1() {}
 
   /*member: Class1.instanceGetter1:
-
 void Class1_instanceGetter1GeneratedMethod_g() {}
 */
   @MethodDeclarationsMacro1()
   int? get instanceGetter1 => null;
 
   /*member: Class1.instanceSetter1=:
-
 void Class1_instanceSetter1GeneratedMethod_s() {}
 */
   @MethodDeclarationsMacro1()
   void set instanceSetter1(int? value) {}
 
   /*member: Class1.[]:
-
 void Class1_[]GeneratedMethod_o() {}
 */
   @MethodDeclarationsMacro1()
   int operator [](int i) => i;
 
   /*member: Class1.instanceField1:
-
 void Class1_instanceField1GeneratedMethod_() {}
 */
   @FieldDeclarationsMacro1()
   int? instanceField1;
 
   /*member: Class1.instanceField2:
-
 void Class1_instanceField2GeneratedMethod_f() {}
 */
   @FieldDeclarationsMacro1()
   final int? instanceField2 = null;
 
   /*member: Class1.instanceField3:
-
 void Class1_instanceField3GeneratedMethod_fl() {}
 */
   @FieldDeclarationsMacro1()
@@ -151,7 +137,6 @@
 }
 
 /*class: Class2:
-
 void Class2GeneratedMethod_a() {}
 
 void Class2Introspection() {
@@ -164,14 +149,12 @@
 @ClassDeclarationsMacro2()
 abstract class Class2 {
   /*member: Class2.instanceMethod1:
-
 void Class2_instanceMethod1GeneratedMethod_a() {}
 */
   @MethodDeclarationsMacro1()
   void instanceMethod1();
 
   /*member: Class2.instanceField1:
-
 void Class2_instanceField1GeneratedMethod_() {}
 */
   @FieldDeclarationsMacro1()
diff --git a/pkg/front_end/test/macro_application/data/tests/declarations.dart.expect b/pkg/front_end/test/macro_application/data/tests/declarations.dart.expect
new file mode 100644
index 0000000..786028f
--- /dev/null
+++ b/pkg/front_end/test/macro_application/data/tests/declarations.dart.expect
@@ -0,0 +1,81 @@
+library /*isNonNullableByDefault*/;
+import self as self;
+import "package:macro/macro.dart" as mac;
+import "dart:core" as core;
+
+import "package:macro/macro.dart";
+
+@#C1
+@#C2
+class Class1 extends core::Object {
+  @#C3
+  field core::int? instanceField1 = null;
+  @#C3
+  final field core::int? instanceField2 = null;
+  @#C3
+  late final field core::int? instanceField3 = null;
+  static final field dynamic _redirecting# = <dynamic>[#C4]/*isLegacy*/;
+  @#C5
+  constructor •() → self::Class1
+    : super core::Object::•()
+    ;
+  @#C5
+  static factory redirect() → self::Class1
+    return new self::Class1::•();
+  @#C5
+  static factory fact() → self::Class1
+    return new self::Class1::•();
+  @#C6
+  method instanceMethod1() → void {}
+  @#C6
+  get instanceGetter1() → core::int?
+    return null;
+  @#C6
+  set instanceSetter1(core::int? value) → void {}
+  @#C6
+  operator [](core::int i) → core::int
+    return i;
+}
+@#C1
+@#C2
+abstract class Class2 extends core::Object {
+  synthetic constructor •() → self::Class2
+    : super core::Object::•()
+    ;
+  @#C6
+  abstract method instanceMethod1() → void;
+  @#C3
+  abstract get instanceField1() → core::int?;
+  @#C3
+  abstract set instanceField1(core::int? #externalFieldValue) → void;
+}
+@#C7
+static field core::int? topLevelField1;
+@#C7
+static final field core::int? topLevelField3 = null;
+@#C7
+late static field core::int? topLevelField4;
+@#C8
+static method topLevelFunction1() → void {}
+@#C8
+external static method topLevelFunction2() → void;
+@#C7
+external static get topLevelField2() → core::int?;
+@#C7
+external static set topLevelField2(core::int? #externalFieldValue) → void;
+@#C8
+static get topLevelGetter1() → core::int?
+  return null;
+@#C8
+static set topLevelSetter1(core::int? value) → void {}
+
+constants  {
+  #C1 = mac::ClassDeclarationsMacro1 {}
+  #C2 = mac::ClassDeclarationsMacro2 {}
+  #C3 = mac::FieldDeclarationsMacro1 {}
+  #C4 = constructor-tearoff self::Class1::redirect
+  #C5 = mac::ConstructorDeclarationsMacro1 {}
+  #C6 = mac::MethodDeclarationsMacro1 {}
+  #C7 = mac::VariableDeclarationsMacro1 {}
+  #C8 = mac::FunctionDeclarationsMacro1 {}
+}
diff --git a/pkg/front_end/test/macro_application/data/tests/parameters.dart b/pkg/front_end/test/macro_application/data/tests/parameters.dart
index 132979a..89fa87e 100644
--- a/pkg/front_end/test/macro_application/data/tests/parameters.dart
+++ b/pkg/front_end/test/macro_application/data/tests/parameters.dart
@@ -2,10 +2,14 @@
 // 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.
 
+/*library: 
+
+
+*/
+
 import 'package:macro/macro.dart';
 
 /*member: topLevelFunction1:
-
 augment void topLevelFunction1(int a, ) {
   return 42;
 }*/
@@ -13,7 +17,6 @@
 external void topLevelFunction1(int a);
 
 /*member: topLevelFunction2:
-
 augment void topLevelFunction2(int a, int b, ) {
   return 42;
 }*/
@@ -21,7 +24,6 @@
 external void topLevelFunction2(int a, int b);
 
 /*member: topLevelFunction3:
-
 augment void topLevelFunction3(int a, [int? b, ]) {
   return 42;
 }*/
@@ -29,7 +31,6 @@
 external void topLevelFunction3(int a, [int? b]);
 
 /*member: topLevelFunction4:
-
 augment void topLevelFunction4(int a, {int? b, int? c, }) {
   return 42;
 }*/
diff --git a/pkg/front_end/test/macro_application/data/tests/parameters.dart.expect b/pkg/front_end/test/macro_application/data/tests/parameters.dart.expect
new file mode 100644
index 0000000..8f916e5
--- /dev/null
+++ b/pkg/front_end/test/macro_application/data/tests/parameters.dart.expect
@@ -0,0 +1,20 @@
+library /*isNonNullableByDefault*/;
+import self as self;
+import "package:macro/macro.dart" as mac;
+import "dart:core" as core;
+
+import "package:macro/macro.dart";
+
+@#C1
+external static method topLevelFunction1(core::int a) → void;
+@#C1
+external static method topLevelFunction2(core::int a, core::int b) → void;
+@#C1
+external static method topLevelFunction3(core::int a, [core::int? b = #C2]) → void;
+@#C1
+external static method topLevelFunction4(core::int a, {core::int? b = #C2, core::int? c = #C2}) → void;
+
+constants  {
+  #C1 = mac::FunctionDefinitionMacro1 {}
+  #C2 = null
+}
diff --git a/pkg/front_end/test/macro_application/data/tests/subtypes.dart b/pkg/front_end/test/macro_application/data/tests/subtypes.dart
index aae13edd..ffdd9f7 100644
--- a/pkg/front_end/test/macro_application/data/tests/subtypes.dart
+++ b/pkg/front_end/test/macro_application/data/tests/subtypes.dart
@@ -2,6 +2,11 @@
 // 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.
 
+/*library: 
+
+
+*/
+
 import 'package:macro/macro.dart';
 
 class A {}
@@ -19,7 +24,6 @@
 class D2 {}
 
 /*member: topLevelFunction1:
-
 void topLevelFunction1GeneratedMethod_es() {}
 
 augment A topLevelFunction1(A a, ) {
@@ -31,7 +35,6 @@
 external A topLevelFunction1(A a);
 
 /*member: topLevelFunction2:
-
 void topLevelFunction2GeneratedMethod_s() {}
 
 augment B2 topLevelFunction2(B1 a, ) {
@@ -43,7 +46,6 @@
 external B2 topLevelFunction2(B1 a);
 
 /*member: topLevelFunction3:
-
 void topLevelFunction3GeneratedMethod_() {}
 
 augment C2 topLevelFunction3(C1 a, ) {
@@ -55,7 +57,6 @@
 external C2 topLevelFunction3(C1 a);
 
 /*member: topLevelFunction4:
-
 void topLevelFunction4GeneratedMethod_() {}
 
 augment D2 topLevelFunction4(D1 a, ) {
diff --git a/pkg/front_end/test/macro_application/data/tests/subtypes.dart.expect b/pkg/front_end/test/macro_application/data/tests/subtypes.dart.expect
new file mode 100644
index 0000000..d428183
--- /dev/null
+++ b/pkg/front_end/test/macro_application/data/tests/subtypes.dart.expect
@@ -0,0 +1,59 @@
+library /*isNonNullableByDefault*/;
+import self as self;
+import "dart:core" as core;
+import "package:macro/macro.dart" as mac;
+
+import "package:macro/macro.dart";
+
+class A extends core::Object {
+  synthetic constructor •() → self::A
+    : super core::Object::•()
+    ;
+}
+class B1 extends core::Object {
+  synthetic constructor •() → self::B1
+    : super core::Object::•()
+    ;
+}
+class B2 extends self::B1 {
+  synthetic constructor •() → self::B2
+    : super self::B1::•()
+    ;
+}
+class C1 extends self::C2 {
+  synthetic constructor •() → self::C1
+    : super self::C2::•()
+    ;
+}
+class C2 extends core::Object {
+  synthetic constructor •() → self::C2
+    : super core::Object::•()
+    ;
+}
+class D1 extends core::Object {
+  synthetic constructor •() → self::D1
+    : super core::Object::•()
+    ;
+}
+class D2 extends core::Object {
+  synthetic constructor •() → self::D2
+    : super core::Object::•()
+    ;
+}
+@#C1
+@#C2
+external static method topLevelFunction1(self::A a) → self::A;
+@#C1
+@#C2
+external static method topLevelFunction2(self::B1 a) → self::B2;
+@#C1
+@#C2
+external static method topLevelFunction3(self::C1 a) → self::C2;
+@#C1
+@#C2
+external static method topLevelFunction4(self::D1 a) → self::D2;
+
+constants  {
+  #C1 = mac::FunctionDeclarationsMacro2 {}
+  #C2 = mac::FunctionDefinitionMacro2 {}
+}
diff --git a/pkg/front_end/test/macro_application/data/tests/type_annotations.dart b/pkg/front_end/test/macro_application/data/tests/type_annotations.dart
index 35867d9..dfee364 100644
--- a/pkg/front_end/test/macro_application/data/tests/type_annotations.dart
+++ b/pkg/front_end/test/macro_application/data/tests/type_annotations.dart
@@ -2,12 +2,16 @@
 // 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.
 
+/*library: 
+
+
+*/
+
 import 'dart:math' as math;
 
 import 'package:macro/macro.dart';
 
 /*member: topLevelFunction1:
-
 augment void topLevelFunction1() {
   return 42;
 }*/
@@ -15,7 +19,6 @@
 external void topLevelFunction1();
 
 /*member: topLevelFunction2:
-
 augment dynamic topLevelFunction2() {
   return 42;
 }*/
@@ -23,7 +26,6 @@
 external dynamic topLevelFunction2();
 
 /*member: topLevelFunction3:
-
 augment int topLevelFunction3() {
   return 42;
 }*/
@@ -31,7 +33,6 @@
 external int topLevelFunction3();
 
 /*member: topLevelFunction4:
-
 augment dynamic topLevelFunction4() {
   return 42;
 }*/
@@ -39,7 +40,6 @@
 external topLevelFunction4();
 
 /*member: topLevelFunction5:
-
 augment math.Random topLevelFunction5() {
   return 42;
 }*/
@@ -47,7 +47,6 @@
 external math.Random topLevelFunction5();
 
 /*member: topLevelFunction6:
-
 augment List<int> topLevelFunction6() {
   return 42;
 }*/
@@ -55,7 +54,6 @@
 external List<int> topLevelFunction6();
 
 /*member: topLevelFunction7:
-
 augment Map<math.Random, List<int>> topLevelFunction7() {
   return 42;
 }*/
@@ -63,7 +61,6 @@
 external Map<math.Random, List<int>> topLevelFunction7();
 
 /*member: topLevelFunction8:
-
 augment Map<int?, String>? topLevelFunction8() {
   return 42;
 }*/
diff --git a/pkg/front_end/test/macro_application/data/tests/type_annotations.dart.expect b/pkg/front_end/test/macro_application/data/tests/type_annotations.dart.expect
new file mode 100644
index 0000000..219150c
--- /dev/null
+++ b/pkg/front_end/test/macro_application/data/tests/type_annotations.dart.expect
@@ -0,0 +1,29 @@
+library /*isNonNullableByDefault*/;
+import self as self;
+import "package:macro/macro.dart" as mac;
+import "dart:core" as core;
+import "dart:math" as math;
+
+import "dart:math" as math;
+import "package:macro/macro.dart";
+
+@#C1
+external static method topLevelFunction1() → void;
+@#C1
+external static method topLevelFunction2() → dynamic;
+@#C1
+external static method topLevelFunction3() → core::int;
+@#C1
+external static method topLevelFunction4() → dynamic;
+@#C1
+external static method topLevelFunction5() → math::Random;
+@#C1
+external static method topLevelFunction6() → core::List<core::int>;
+@#C1
+external static method topLevelFunction7() → core::Map<math::Random, core::List<core::int>>;
+@#C1
+external static method topLevelFunction8() → core::Map<core::int?, core::String>?;
+
+constants  {
+  #C1 = mac::FunctionDefinitionMacro1 {}
+}
diff --git a/pkg/front_end/test/macro_application/data/tests/types.dart b/pkg/front_end/test/macro_application/data/tests/types.dart
index 2dbb05d..883e037 100644
--- a/pkg/front_end/test/macro_application/data/tests/types.dart
+++ b/pkg/front_end/test/macro_application/data/tests/types.dart
@@ -2,35 +2,61 @@
 // 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.
 
+/*library: 
+import 'dart:core' as i0;
+import 'package:macro/macro.dart' as i1;
+
+
+class topLevelFunction1GeneratedClass {
+  external void method();
+}
+class topLevelFunction2GeneratedClass {
+  external i0.dynamic method();
+}
+class topLevelFunction3GeneratedClass {
+  external i0.int method();
+}
+class topLevelFunction4GeneratedClass {
+  external i1.FunctionTypesMacro1? method();
+}
+class topLevelFunction5GeneratedClass {
+  external i0.dynamic method();
+}
+*/
+
 import 'package:macro/macro.dart';
 
 /*member: topLevelFunction1:
-class topLevelFunction1GeneratedClass<T extends void> {}*/
+class topLevelFunction1GeneratedClass {
+  external void method();
+}*/
 @FunctionTypesMacro1()
 void topLevelFunction1() {}
 
 /*member: topLevelFunction2:
-class topLevelFunction2GeneratedClass<T extends dynamic> {}*/
+class topLevelFunction2GeneratedClass {
+  external dynamic method();
+}*/
 @FunctionTypesMacro1()
 dynamic topLevelFunction2() {}
 
 /*member: topLevelFunction3:
-import 'dart:core' as i0;
-
-
-class topLevelFunction3GeneratedClass<T extends i0.int> {}*/
+class topLevelFunction3GeneratedClass {
+  external int method();
+}*/
 @FunctionTypesMacro1()
 int topLevelFunction3() => 0;
 
 /*member: topLevelFunction4:
-import 'package:macro/macro.dart' as i0;
-
-
-class topLevelFunction4GeneratedClass<T extends i0.FunctionTypesMacro1?> {}*/
+class topLevelFunction4GeneratedClass {
+  external FunctionTypesMacro1? method();
+}*/
 @FunctionTypesMacro1()
 FunctionTypesMacro1? topLevelFunction4() => null;
 
 /*member: topLevelFunction5:
-class topLevelFunction5GeneratedClass<T extends dynamic> {}*/
+class topLevelFunction5GeneratedClass {
+  external dynamic method();
+}*/
 @FunctionTypesMacro1()
 topLevelFunction5() {}
diff --git a/pkg/front_end/test/macro_application/data/tests/types.dart.expect b/pkg/front_end/test/macro_application/data/tests/types.dart.expect
new file mode 100644
index 0000000..e36ef4f
--- /dev/null
+++ b/pkg/front_end/test/macro_application/data/tests/types.dart.expect
@@ -0,0 +1,55 @@
+library /*isNonNullableByDefault*/;
+import self as self;
+import "dart:core" as core;
+import "package:macro/macro.dart" as mac;
+
+import "dart:core" as i0;
+import "package:macro/macro.dart" as i1;
+import "package:macro/macro.dart";
+
+class topLevelFunction1GeneratedClass extends core::Object { // from org-dartlang-augmentation:/a/b/c/main.dart-0
+  synthetic constructor •() → self::topLevelFunction1GeneratedClass
+    : super core::Object::•()
+    ;
+  external method method() → void;
+}
+class topLevelFunction2GeneratedClass extends core::Object { // from org-dartlang-augmentation:/a/b/c/main.dart-0
+  synthetic constructor •() → self::topLevelFunction2GeneratedClass
+    : super core::Object::•()
+    ;
+  external method method() → dynamic;
+}
+class topLevelFunction3GeneratedClass extends core::Object { // from org-dartlang-augmentation:/a/b/c/main.dart-0
+  synthetic constructor •() → self::topLevelFunction3GeneratedClass
+    : super core::Object::•()
+    ;
+  external method method() → core::int;
+}
+class topLevelFunction4GeneratedClass extends core::Object { // from org-dartlang-augmentation:/a/b/c/main.dart-0
+  synthetic constructor •() → self::topLevelFunction4GeneratedClass
+    : super core::Object::•()
+    ;
+  external method method() → mac::FunctionTypesMacro1?;
+}
+class topLevelFunction5GeneratedClass extends core::Object { // from org-dartlang-augmentation:/a/b/c/main.dart-0
+  synthetic constructor •() → self::topLevelFunction5GeneratedClass
+    : super core::Object::•()
+    ;
+  external method method() → dynamic;
+}
+@#C1
+static method topLevelFunction1() → void {}
+@#C1
+static method topLevelFunction2() → dynamic {}
+@#C1
+static method topLevelFunction3() → core::int
+  return 0;
+@#C1
+static method topLevelFunction4() → mac::FunctionTypesMacro1?
+  return null;
+@#C1
+static method topLevelFunction5() → dynamic {}
+
+constants  {
+  #C1 = mac::FunctionTypesMacro1 {}
+}
diff --git a/pkg/front_end/test/macro_application/macro_application_test.dart b/pkg/front_end/test/macro_application/macro_application_test.dart
index 18e8fdf..9282bac 100644
--- a/pkg/front_end/test/macro_application/macro_application_test.dart
+++ b/pkg/front_end/test/macro_application/macro_application_test.dart
@@ -2,7 +2,7 @@
 // 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:io' show Directory, Platform;
+import 'dart:io' show Directory, File, Platform;
 
 import 'package:_fe_analyzer_shared/src/macros/api.dart';
 import 'package:_fe_analyzer_shared/src/macros/executor.dart';
@@ -10,7 +10,7 @@
 import 'package:_fe_analyzer_shared/src/macros/isolated_executor/isolated_executor.dart'
     as isolatedExecutor;
 import 'package:_fe_analyzer_shared/src/testing/id.dart'
-    show ActualData, ClassId, Id;
+    show ActualData, ClassId, Id, LibraryId;
 import 'package:_fe_analyzer_shared/src/testing/id_testing.dart';
 import 'package:front_end/src/api_prototype/compiler_options.dart';
 import 'package:front_end/src/api_prototype/experimental_flags.dart';
@@ -19,14 +19,20 @@
 import 'package:front_end/src/fasta/kernel/macro.dart';
 import 'package:front_end/src/fasta/kernel/utils.dart';
 import 'package:front_end/src/fasta/source/source_class_builder.dart';
+import 'package:front_end/src/fasta/source/source_library_builder.dart';
+import 'package:front_end/src/testing/compiler_common.dart';
 import 'package:front_end/src/testing/id_extractor.dart';
 import 'package:front_end/src/testing/id_testing_helper.dart';
 import 'package:kernel/ast.dart' hide Arguments;
 import 'package:kernel/kernel.dart';
 import 'package:kernel/target/targets.dart';
+import 'package:kernel/text/ast_to_text.dart';
 import 'package:vm/target/vm.dart';
 
+import '../utils/kernel_chain.dart';
+
 Future<void> main(List<String> args) async {
+  bool generateExpectations = args.contains('-g');
   enableMacros = true;
 
   Directory tempDirectory =
@@ -38,8 +44,10 @@
         args: args,
         createUriForFileName: createUriForFileName,
         onFailure: onFailure,
-        runTest: runTestFor(
-            const MacroDataComputer(), [new MacroTestConfig(tempDirectory)]),
+        runTest: runTestFor(const MacroDataComputer(), [
+          new MacroTestConfig(dataDir, tempDirectory,
+              generateExpectations: generateExpectations)
+        ]),
         preserveWhitespaceInAnnotations: true);
   } finally {
     await tempDirectory.delete(recursive: true);
@@ -47,11 +55,14 @@
 }
 
 class MacroTestConfig extends TestConfig {
+  final Directory dataDir;
   final Directory tempDirectory;
+  final bool generateExpectations;
   int precompiledCount = 0;
   final Map<MacroClass, Uri> precompiledMacroUris = {};
 
-  MacroTestConfig(this.tempDirectory)
+  MacroTestConfig(this.dataDir, this.tempDirectory,
+      {required this.generateExpectations})
       : super(cfeMarker, 'cfe',
             explicitExperimentalFlags: {ExperimentalFlag.macros: true},
             packageConfigUri:
@@ -71,6 +82,42 @@
       return uri;
     };
   }
+
+  @override
+  Future<void> onCompilationResult(
+      TestData testData, TestResultData testResultData) async {
+    Component component = testResultData.compilerResult.component!;
+    StringBuffer buffer = new StringBuffer();
+    Printer printer = new Printer(buffer)
+      ..writeProblemsAsJson("Problems in component", component.problemsAsJson);
+    component.libraries.forEach((Library library) {
+      if (isTestUri(library.importUri)) {
+        printer.writeLibraryFile(library);
+        printer.endLine();
+      }
+    });
+    printer.writeConstantTable(component);
+    String actual = buffer.toString();
+    String expectationFileName = '${testData.name}.expect';
+    Uri expectedUri = dataDir.uri.resolve(expectationFileName);
+    File file = new File.fromUri(expectedUri);
+    if (file.existsSync()) {
+      String expected = file.readAsStringSync();
+      if (expected != actual) {
+        if (generateExpectations) {
+          file.writeAsStringSync(actual);
+        } else {
+          String diff = await runDiff(expectedUri, actual);
+          throw "${testData.name} don't match ${expectedUri}\n$diff";
+        }
+      }
+    } else if (generateExpectations) {
+      file.writeAsStringSync(actual);
+    } else {
+      throw 'Please use -g option to create file ${expectedUri} with this '
+          'content:\n$actual';
+    }
+  }
 }
 
 bool _isMember(MemberBuilder memberBuilder, Member member) {
@@ -93,6 +140,34 @@
   DataInterpreter<String> get dataValidator => const StringDataInterpreter();
 
   @override
+  void computeLibraryData(TestResultData testResultData, Library library,
+      Map<Id, ActualData<String>> actualMap,
+      {bool? verbose}) {
+    CfeDataRegistry<String> registry =
+        new CfeDataRegistry(testResultData.compilerResult, actualMap);
+    MacroApplicationDataForTesting macroApplicationData = testResultData
+        .compilerResult
+        .kernelTargetForTesting!
+        .loader
+        .dataForTesting!
+        .macroApplicationData;
+    StringBuffer sb = new StringBuffer();
+    for (SourceLibraryBuilder sourceLibraryBuilder
+        in macroApplicationData.libraryTypesResult.keys) {
+      if (sourceLibraryBuilder.library == library) {
+        String source =
+            macroApplicationData.libraryTypesResult[sourceLibraryBuilder]!;
+        sb.write('\n${source}');
+      }
+    }
+    if (sb.isNotEmpty) {
+      Id id = new LibraryId(library.fileUri);
+      registry.registerValue(
+          library.fileUri, library.fileOffset, id, sb.toString(), library);
+    }
+  }
+
+  @override
   void computeClassData(TestResultData testResultData, Class cls,
       Map<Id, ActualData<String>> actualMap,
       {bool? verbose}) {
@@ -105,10 +180,12 @@
         .dataForTesting!
         .macroApplicationData;
     StringBuffer sb = new StringBuffer();
-    for (MapEntry<SourceClassBuilder, String> entry
+    for (MapEntry<SourceClassBuilder, List<MacroExecutionResult>> entry
         in macroApplicationData.classTypesResults.entries) {
       if (entry.key.cls == cls) {
-        sb.write('\n${entry.value.trim()}');
+        for (MacroExecutionResult result in entry.value) {
+          sb.write('\n${codeToString(result.augmentations.first)}');
+        }
       }
     }
     for (MapEntry<SourceClassBuilder, List<MacroExecutionResult>> entry
@@ -147,10 +224,12 @@
         .dataForTesting!
         .macroApplicationData;
     StringBuffer sb = new StringBuffer();
-    for (MapEntry<MemberBuilder, String> entry
+    for (MapEntry<MemberBuilder, List<MacroExecutionResult>> entry
         in macroApplicationData.memberTypesResults.entries) {
       if (_isMember(entry.key, member)) {
-        sb.write('\n${entry.value.trim()}');
+        for (MacroExecutionResult result in entry.value) {
+          sb.write('\n${codeToString(result.augmentations.first)}');
+        }
       }
     }
     for (MapEntry<MemberBuilder, List<MacroExecutionResult>> entry
diff --git a/pkg/smith/lib/configuration.dart b/pkg/smith/lib/configuration.dart
index bac7ac7..9284ec3 100644
--- a/pkg/smith/lib/configuration.dart
+++ b/pkg/smith/lib/configuration.dart
@@ -624,6 +624,7 @@
   static const none = Compiler._('none');
   static const dart2js = Compiler._('dart2js');
   static const dart2analyzer = Compiler._('dart2analyzer');
+  static const dart2wasm = Compiler._('dart2wasm');
   static const compareAnalyzerCfe = Compiler._('compare_analyzer_cfe');
   static const dartdevc = Compiler._('dartdevc');
   static const dartdevk = Compiler._('dartdevk');
@@ -639,6 +640,7 @@
     none,
     dart2js,
     dart2analyzer,
+    dart2wasm,
     compareAnalyzerCfe,
     dartdevc,
     dartdevk,
@@ -691,6 +693,12 @@
           Runtime.safari,
         ];
 
+      case Compiler.dart2wasm:
+        return const [
+          Runtime.none,
+          Runtime.d8,
+          Runtime.chrome,
+        ];
       case Compiler.dart2analyzer:
       case Compiler.compareAnalyzerCfe:
         return const [Runtime.none];
@@ -716,6 +724,8 @@
     switch (this) {
       case Compiler.dart2js:
         return Runtime.d8;
+      case Compiler.dart2wasm:
+        return Runtime.d8;
       case Compiler.dartdevc:
       case Compiler.dartdevk:
         return Runtime.chrome;
@@ -742,6 +752,7 @@
       case Compiler.dart2analyzer:
       case Compiler.compareAnalyzerCfe:
       case Compiler.dart2js:
+      case Compiler.dart2wasm:
       case Compiler.dartdevc:
       case Compiler.dartdevk:
       case Compiler.fasta:
diff --git a/pkg/test_runner/lib/src/command.dart b/pkg/test_runner/lib/src/command.dart
index 45fa5d2..7ba599f 100644
--- a/pkg/test_runner/lib/src/command.dart
+++ b/pkg/test_runner/lib/src/command.dart
@@ -186,6 +186,9 @@
     if (displayName == 'precompiler' || displayName == 'app_jit') {
       return VMCommandOutput(
           this, exitCode, timedOut, stdout, stderr, time, pid);
+    } else if (displayName == 'dart2wasm') {
+      return Dart2WasmCompilerCommandOutput(
+          this, exitCode, timedOut, stdout, stderr, time, compilationSkipped);
     }
 
     return CompilationCommandOutput(
diff --git a/pkg/test_runner/lib/src/command_output.dart b/pkg/test_runner/lib/src/command_output.dart
index cc65097..03db24c 100644
--- a/pkg/test_runner/lib/src/command_output.dart
+++ b/pkg/test_runner/lib/src/command_output.dart
@@ -1060,6 +1060,45 @@
   }
 }
 
+class Dart2WasmCompilerCommandOutput extends CompilationCommandOutput
+    with _StaticErrorOutput {
+  static void parseErrors(String stdout, List<StaticError> errors) {
+    _StaticErrorOutput._parseCfeErrors(
+        ErrorSource.web, _errorRegexp, stdout, errors);
+  }
+
+  /// Matches the location and message of a dart2wasm error message, which looks
+  /// like:
+  ///
+  ///     tests/language_2/some_test.dart:9:3: Error: Some message.
+  ///       BadThing();
+  ///       ^
+  ///
+  /// The test runner only validates the main error message, and not the
+  /// suggested fixes, so we only parse the first line.
+  // TODO(rnystrom): Support validating context messages.
+  static final _errorRegexp =
+      RegExp(r"^([^:]+):(\d+):(\d+): (Error): (.*)$", multiLine: true);
+
+  Dart2WasmCompilerCommandOutput(
+      Command command,
+      int exitCode,
+      bool timedOut,
+      List<int> stdout,
+      List<int> stderr,
+      Duration time,
+      bool compilationSkipped)
+      : super(command, exitCode, timedOut, stdout, stderr, time,
+            compilationSkipped);
+
+  @override
+  void _parseErrors() {
+    var errors = <StaticError>[];
+    parseErrors(decodeUtf8(stdout), errors);
+    errors.forEach(addError);
+  }
+}
+
 class DevCompilerCommandOutput extends CommandOutput with _StaticErrorOutput {
   /// Matches the first line of a DDC error message. DDC prints errors to
   /// stdout that look like:
diff --git a/pkg/test_runner/lib/src/compiler_configuration.dart b/pkg/test_runner/lib/src/compiler_configuration.dart
index 6d72e22..b287bcc 100644
--- a/pkg/test_runner/lib/src/compiler_configuration.dart
+++ b/pkg/test_runner/lib/src/compiler_configuration.dart
@@ -86,6 +86,9 @@
       case Compiler.dart2js:
         return Dart2jsCompilerConfiguration(configuration);
 
+      case Compiler.dart2wasm:
+        return Dart2WasmCompilerConfiguration(configuration);
+
       case Compiler.dartdevc:
         return DevCompilerConfiguration(configuration);
 
@@ -499,6 +502,83 @@
   }
 }
 
+/// Common configuration for dart2wasm-based tools, such as dart2wasm.
+class Dart2WasmCompilerConfiguration extends CompilerConfiguration {
+  Dart2WasmCompilerConfiguration(TestConfiguration configuration)
+      : super._subclass(configuration);
+
+  String computeCompilerPath() {
+    var prefix = 'sdk/bin';
+    if (_isHostChecked) {
+      if (_useSdk) {
+        throw "--host-checked and --use-sdk cannot be used together";
+      }
+      // The script dart2wasm_developer is not included in the
+      // shipped SDK, that is the script is not installed in
+      // "$buildDir/dart-sdk/bin/"
+      return '$prefix/dart2wasm_developer$shellScriptExtension';
+    }
+    if (_useSdk) {
+      prefix = '${_configuration.buildDirectory}/dart-sdk/bin';
+    }
+    return '$prefix/dart2wasm$shellScriptExtension';
+  }
+
+  List<String> computeCompilerArguments(
+      TestFile testFile, List<String> vmOptions, List<String> args) {
+    return [
+      // The file being compiled is the last argument.
+      args.last
+    ];
+  }
+
+  Command computeCompilationCommand(String outputFileName,
+      List<String> arguments, Map<String, String> environmentOverrides) {
+    arguments = arguments.toList();
+    arguments.add('$outputFileName');
+
+    return CompilationCommand(
+        'dart2wasm',
+        outputFileName,
+        bootstrapDependencies(),
+        computeCompilerPath(),
+        arguments,
+        environmentOverrides,
+        alwaysCompile: !_useSdk);
+  }
+
+  CommandArtifact computeCompilationArtifact(String tempDir,
+      List<String> arguments, Map<String, String> environmentOverrides) {
+    var compilerArguments = [
+      ...arguments,
+    ];
+
+    var inputFile = arguments.last;
+    var inputFilename = Uri.file(inputFile).pathSegments.last;
+    var out = "$tempDir/${inputFilename.replaceAll('.dart', '.wasm')}";
+    var commands = [
+      computeCompilationCommand(out, compilerArguments, environmentOverrides),
+    ];
+
+    return CommandArtifact(commands, out, 'application/wasm');
+  }
+
+  List<String> computeRuntimeArguments(
+      RuntimeConfiguration runtimeConfiguration,
+      TestFile testFile,
+      List<String> vmOptions,
+      List<String> originalArguments,
+      CommandArtifact artifact) {
+    return [
+      '--experimental-wasm-gc',
+      '--wasm-gc-js-interop',
+      'pkg/dart2wasm/bin/run_wasm.js',
+      '--',
+      artifact.filename,
+    ];
+  }
+}
+
 /// Configuration for `dartdevc` and `dartdevk` (DDC with Kernel)
 class DevCompilerConfiguration extends CompilerConfiguration {
   DevCompilerConfiguration(TestConfiguration configuration)
diff --git a/pkg/test_runner/lib/src/configuration.dart b/pkg/test_runner/lib/src/configuration.dart
index 198c01a..d98b8b8 100644
--- a/pkg/test_runner/lib/src/configuration.dart
+++ b/pkg/test_runner/lib/src/configuration.dart
@@ -203,6 +203,7 @@
       Compiler.dartkp,
       Compiler.fasta,
       Compiler.dart2js,
+      Compiler.dart2wasm,
     ];
     return fastaCompilers.contains(compiler);
   }
diff --git a/pkg/test_runner/lib/src/runtime_configuration.dart b/pkg/test_runner/lib/src/runtime_configuration.dart
index 2341361..4cba518 100644
--- a/pkg/test_runner/lib/src/runtime_configuration.dart
+++ b/pkg/test_runner/lib/src/runtime_configuration.dart
@@ -168,7 +168,7 @@
 
   void checkArtifact(CommandArtifact artifact) {
     var type = artifact.mimeType;
-    if (type != 'application/javascript') {
+    if (type != 'application/javascript' && type != 'application/wasm') {
       throw "Runtime '$moniker' cannot run files of type '$type'.";
     }
   }
diff --git a/pkg/test_runner/lib/src/test_suite.dart b/pkg/test_runner/lib/src/test_suite.dart
index 5aeaf54..faf6d57 100644
--- a/pkg/test_runner/lib/src/test_suite.dart
+++ b/pkg/test_runner/lib/src/test_suite.dart
@@ -597,6 +597,7 @@
       '$directory/${name}_analyzer.status',
       '$directory/${name}_analyzer2.status',
       '$directory/${name}_dart2js.status',
+      '$directory/${name}_dart2wasm.status',
       '$directory/${name}_dartdevc.status',
       '$directory/${name}_kernel.status',
       '$directory/${name}_precompiled.status',
@@ -956,6 +957,7 @@
     var commands = <Command>[];
     const supportedCompilers = {
       Compiler.dart2js,
+      Compiler.dart2wasm,
       Compiler.dartdevc,
       Compiler.dartdevk
     };
diff --git a/pkg/wasm_builder/LICENSE b/pkg/wasm_builder/LICENSE
new file mode 100644
index 0000000..9566a27
--- /dev/null
+++ b/pkg/wasm_builder/LICENSE
@@ -0,0 +1,26 @@
+Copyright 2022, the Dart project authors. All rights reserved.
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+    * Redistributions of source code must retain the above copyright
+      notice, this list of conditions and the following disclaimer.
+    * Redistributions in binary form must reproduce the above
+      copyright notice, this list of conditions and the following
+      disclaimer in the documentation and/or other materials provided
+      with the distribution.
+    * Neither the name of Google Inc. nor the names of its
+      contributors may be used to endorse or promote products derived
+      from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/pkg/wasm_builder/lib/src/instructions.dart b/pkg/wasm_builder/lib/src/instructions.dart
new file mode 100644
index 0000000..24a9556
--- /dev/null
+++ b/pkg/wasm_builder/lib/src/instructions.dart
@@ -0,0 +1,2283 @@
+// 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.
+
+import 'module.dart';
+import 'serialize.dart';
+import 'types.dart';
+
+/// Thrown when Wasm bytecode validation fails.
+class ValidationError {
+  final String trace;
+  final String error;
+
+  ValidationError(this.trace, this.error);
+
+  @override
+  String toString() => "$trace\n$error";
+}
+
+/// Label to use as target for branch instructions.
+abstract class Label {
+  final List<ValueType> inputs;
+  final List<ValueType> outputs;
+
+  late final int? ordinal;
+  late final int depth;
+  late final int baseStackHeight;
+  late final bool reachable;
+
+  Label._(this.inputs, this.outputs);
+
+  List<ValueType> get targetTypes;
+
+  bool get hasOrdinal => ordinal != null;
+
+  @override
+  String toString() => "L$ordinal";
+}
+
+class Expression extends Label {
+  Expression(List<ValueType> inputs, List<ValueType> outputs)
+      : super._(inputs, outputs) {
+    ordinal = null;
+    depth = 0;
+    baseStackHeight = 0;
+    reachable = true;
+  }
+
+  List<ValueType> get targetTypes => outputs;
+}
+
+class Block extends Label {
+  Block(List<ValueType> inputs, List<ValueType> outputs)
+      : super._(inputs, outputs);
+
+  List<ValueType> get targetTypes => outputs;
+}
+
+class Loop extends Label {
+  Loop(List<ValueType> inputs, List<ValueType> outputs)
+      : super._(inputs, outputs);
+
+  List<ValueType> get targetTypes => inputs;
+}
+
+class If extends Label {
+  bool hasElse = false;
+
+  If(List<ValueType> inputs, List<ValueType> outputs)
+      : super._(inputs, outputs);
+
+  List<ValueType> get targetTypes => outputs;
+}
+
+/// A sequence of Wasm instructions.
+///
+/// Instructions can be added to the sequence by calling the corresponding
+/// instruction methods.
+///
+/// If asserts are enabled, the instruction methods will perform on-the-fly
+/// validation and throw a [ValidationError] if validation fails.
+class Instructions with SerializerMixin {
+  /// The module containing these instructions.
+  final Module module;
+
+  /// Locals declared in this body, including parameters.
+  final List<Local> locals;
+
+  /// Is this the initializer of a global variable?
+  final bool isGlobalInitializer;
+
+  /// Whether a textual trace of the instruction stream should be recorded when
+  /// emitting instructions (provided asserts are enabled).
+  ///
+  /// This trace can be accessed via the [trace] property and will be part of
+  /// the exception text if a validation error occurs.
+  bool traceEnabled = true;
+
+  /// Whether to print a byte offset for each instruction in the textual trace.
+  bool byteOffsetEnabled = false;
+
+  /// Column width for the instruction byte offset.
+  int byteOffsetWidth = 7;
+
+  /// Column width for the instructions.
+  int instructionColumnWidth = 50;
+
+  int _indent = 1;
+  final List<String> _traceLines = [];
+
+  int _labelCount = 0;
+  final List<Label> _labelStack = [];
+  final List<ValueType> _stackTypes = [];
+  bool _reachable = true;
+
+  /// Create a new instruction sequence.
+  Instructions(this.module, List<ValueType> outputs,
+      {this.locals = const [], this.isGlobalInitializer = false}) {
+    _labelStack.add(Expression(const [], outputs));
+  }
+
+  /// Whether the current point in the instruction stream is reachable.
+  bool get reachable => _reachable;
+
+  /// Whether the instruction sequence has been completed by the final `end`.
+  bool get isComplete => _labelStack.isEmpty;
+
+  /// Textual trace of the instructions.
+  String get trace => _traceLines.join();
+
+  bool _debugTrace(List<Object>? trace,
+      {required bool reachableAfter,
+      int indentBefore = 0,
+      int indentAfter = 0}) {
+    if (traceEnabled && trace != null) {
+      _indent += indentBefore;
+      String byteOffset =
+          byteOffsetEnabled ? "${data.length}".padLeft(byteOffsetWidth) : "";
+      String instr = "  " * _indent + " " + trace.join(" ");
+      instr = instr.length > instructionColumnWidth - 2
+          ? instr.substring(0, instructionColumnWidth - 4) + "... "
+          : instr.padRight(instructionColumnWidth);
+      final String stack = reachableAfter ? _stackTypes.join(', ') : "-";
+      final String line = "$byteOffset$instr$stack\n";
+      _indent += indentAfter;
+
+      _traceLines.add(line);
+    }
+    return true;
+  }
+
+  bool _comment(String text) {
+    if (traceEnabled) {
+      final String line = " " * (byteOffsetEnabled ? byteOffsetWidth : 0) +
+          "  " * _indent +
+          " ;; $text\n";
+      _traceLines.add(line);
+    }
+    return true;
+  }
+
+  Never _reportError(String error) {
+    throw ValidationError(trace, error);
+  }
+
+  ValueType get _topOfStack {
+    if (!reachable) return RefType.any();
+    if (_stackTypes.isEmpty) _reportError("Stack underflow");
+    return _stackTypes.last;
+  }
+
+  Label get _topOfLabelStack {
+    if (_labelStack.isEmpty) _reportError("Label stack underflow");
+    return _labelStack.last;
+  }
+
+  List<ValueType> _stack(int n) {
+    if (_stackTypes.length < n) _reportError("Stack underflow");
+    return _stackTypes.sublist(_stackTypes.length - n);
+  }
+
+  List<ValueType> _checkStackTypes(List<ValueType> inputs,
+      [List<ValueType>? stack]) {
+    stack ??= _stack(inputs.length);
+    bool typesMatch = true;
+    for (int i = 0; i < inputs.length; i++) {
+      if (!stack[i].isSubtypeOf(inputs[i])) {
+        typesMatch = false;
+        break;
+      }
+    }
+    if (!typesMatch) {
+      final String expected = inputs.join(', ');
+      final String got = stack.join(', ');
+      _reportError("Expected [$expected], but stack contained [$got]");
+    }
+    return stack;
+  }
+
+  bool _verifyTypes(List<ValueType> inputs, List<ValueType> outputs,
+      {List<Object>? trace, bool reachableAfter = true}) {
+    return _verifyTypesFun(inputs, (_) => outputs,
+        trace: trace, reachableAfter: reachableAfter);
+  }
+
+  bool _verifyTypesFun(List<ValueType> inputs,
+      List<ValueType> Function(List<ValueType>) outputsFun,
+      {List<Object>? trace, bool reachableAfter = true}) {
+    if (!reachable) {
+      return _debugTrace(trace, reachableAfter: false);
+    }
+    if (_stackTypes.length - inputs.length < _topOfLabelStack.baseStackHeight) {
+      _reportError("Underflowing base stack of innermost block");
+    }
+    final List<ValueType> stack = _checkStackTypes(inputs);
+    _stackTypes.length -= inputs.length;
+    _stackTypes.addAll(outputsFun(stack));
+    return _debugTrace(trace, reachableAfter: reachableAfter);
+  }
+
+  bool _verifyBranchTypes(Label label,
+      [int popped = 0, List<ValueType> pushed = const []]) {
+    if (!reachable) {
+      return true;
+    }
+    final List<ValueType> inputs = label.targetTypes;
+    if (_stackTypes.length - popped + pushed.length - inputs.length <
+        label.baseStackHeight) {
+      _reportError("Underflowing base stack of target label");
+    }
+    final List<ValueType> stack = inputs.length <= pushed.length
+        ? pushed.sublist(pushed.length - inputs.length)
+        : [
+            ..._stackTypes.sublist(
+                _stackTypes.length - popped + pushed.length - inputs.length,
+                _stackTypes.length - popped),
+            ...pushed
+          ];
+    _checkStackTypes(inputs, stack);
+    return true;
+  }
+
+  bool _verifyStartOfBlock(Label label, {required List<Object> trace}) {
+    return _debugTrace(
+        ["$label:", ...trace, FunctionType(label.inputs, label.outputs)],
+        reachableAfter: reachable, indentAfter: 1);
+  }
+
+  bool _verifyEndOfBlock(List<ValueType> outputs,
+      {required List<Object> trace,
+      required bool reachableAfter,
+      required bool reindent}) {
+    final Label label = _topOfLabelStack;
+    if (reachable) {
+      final int expectedHeight = label.baseStackHeight + label.outputs.length;
+      if (_stackTypes.length != expectedHeight) {
+        _reportError("Incorrect stack height at end of block"
+            " (expected $expectedHeight, actual ${_stackTypes.length})");
+      }
+      _checkStackTypes(label.outputs);
+    }
+    if (label.reachable) {
+      assert(_stackTypes.length >= label.baseStackHeight);
+      _stackTypes.length = label.baseStackHeight;
+      _stackTypes.addAll(outputs);
+    }
+    return _debugTrace([if (label.hasOrdinal) "$label:", ...trace],
+        reachableAfter: reachableAfter,
+        indentBefore: -1,
+        indentAfter: reindent ? 1 : 0);
+  }
+
+  // Meta
+
+  /// Emit a comment.
+  void comment(String text) {
+    assert(_comment(text));
+  }
+
+  // Control instructions
+
+  /// Emit an `unreachable` instruction.
+  void unreachable() {
+    assert(_verifyTypes(const [], const [],
+        trace: const ['unreachable'], reachableAfter: false));
+    _reachable = false;
+    writeByte(0x00);
+  }
+
+  /// Emit a `nop` instruction.
+  void nop() {
+    assert(_verifyTypes(const [], const [], trace: const ['nop']));
+    writeByte(0x01);
+  }
+
+  Label _beginBlock(int encoding, Label label, {required List<Object> trace}) {
+    assert(_verifyTypes(label.inputs, label.inputs));
+    label.ordinal = ++_labelCount;
+    label.depth = _labelStack.length;
+    label.baseStackHeight = _stackTypes.length - label.inputs.length;
+    label.reachable = reachable;
+    _labelStack.add(label);
+    assert(_verifyStartOfBlock(label, trace: trace));
+    writeByte(encoding);
+    if (label.inputs.isEmpty && label.outputs.isEmpty) {
+      writeByte(0x40);
+    } else if (label.inputs.isEmpty && label.outputs.length == 1) {
+      write(label.outputs.single);
+    } else {
+      final type = module.addFunctionType(label.inputs, label.outputs);
+      writeSigned(type.index);
+    }
+    return label;
+  }
+
+  /// Emit a `block` instruction.
+  /// Branching to the returned label will branch to the matching `end`.
+  Label block(
+      [List<ValueType> inputs = const [], List<ValueType> outputs = const []]) {
+    return _beginBlock(0x02, Block(inputs, outputs), trace: const ['block']);
+  }
+
+  /// Emit a `loop` instruction.
+  /// Branching to the returned label will branch to the `loop`.
+  Label loop(
+      [List<ValueType> inputs = const [], List<ValueType> outputs = const []]) {
+    return _beginBlock(0x03, Loop(inputs, outputs), trace: const ['loop']);
+  }
+
+  /// Emit an `if` instruction.
+  /// Branching to the returned label will branch to the matching `end`.
+  Label if_(
+      [List<ValueType> inputs = const [], List<ValueType> outputs = const []]) {
+    assert(_verifyTypes(const [NumType.i32], const []));
+    return _beginBlock(0x04, If(inputs, outputs), trace: const ['if']);
+  }
+
+  /// Emit an `else` instruction.
+  void else_() {
+    assert(_topOfLabelStack is If ||
+        _reportError("Unexpected 'else' (not in 'if' block)"));
+    final If label = _topOfLabelStack as If;
+    assert(!label.hasElse || _reportError("Duplicate 'else' in 'if' block"));
+    assert(_verifyEndOfBlock(label.inputs,
+        trace: const ['else'],
+        reachableAfter: _topOfLabelStack.reachable,
+        reindent: true));
+    label.hasElse = true;
+    _reachable = _topOfLabelStack.reachable;
+    writeByte(0x05);
+  }
+
+  /// Emit an `end` instruction.
+  void end() {
+    assert(_verifyEndOfBlock(_topOfLabelStack.outputs,
+        trace: const ['end'],
+        reachableAfter: _topOfLabelStack.reachable,
+        reindent: false));
+    _reachable = _topOfLabelStack.reachable;
+    _labelStack.removeLast();
+    writeByte(0x0B);
+  }
+
+  int _labelIndex(Label label) {
+    final int index = _labelStack.length - label.depth - 1;
+    assert(_labelStack[label.depth] == label);
+    return index;
+  }
+
+  void _writeLabel(Label label) {
+    writeUnsigned(_labelIndex(label));
+  }
+
+  /// Emit a `br` instruction.
+  void br(Label label) {
+    assert(_verifyTypes(const [], const [],
+        trace: ['br', label], reachableAfter: false));
+    assert(_verifyBranchTypes(label));
+    _reachable = false;
+    writeByte(0x0C);
+    _writeLabel(label);
+  }
+
+  /// Emit a `br_if` instruction.
+  void br_if(Label label) {
+    assert(
+        _verifyTypes(const [NumType.i32], const [], trace: ['br_if', label]));
+    assert(_verifyBranchTypes(label));
+    writeByte(0x0D);
+    _writeLabel(label);
+  }
+
+  /// Emit a `br_table` instruction.
+  void br_table(List<Label> labels, Label defaultLabel) {
+    assert(_verifyTypes(const [NumType.i32], const [],
+        trace: ['br_table', ...labels, defaultLabel], reachableAfter: false));
+    for (var label in labels) {
+      assert(_verifyBranchTypes(label));
+    }
+    assert(_verifyBranchTypes(defaultLabel));
+    _reachable = false;
+    writeByte(0x0E);
+    writeUnsigned(labels.length);
+    for (Label label in labels) {
+      _writeLabel(label);
+    }
+    _writeLabel(defaultLabel);
+  }
+
+  /// Emit a `return` instruction.
+  void return_() {
+    assert(_verifyTypes(_labelStack[0].outputs, const [],
+        trace: const ['return'], reachableAfter: false));
+    _reachable = false;
+    writeByte(0x0F);
+  }
+
+  /// Emit a `call` instruction.
+  void call(BaseFunction function) {
+    assert(_verifyTypes(function.type.inputs, function.type.outputs,
+        trace: ['call', function]));
+    writeByte(0x10);
+    writeUnsigned(function.index);
+  }
+
+  /// Emit a `call_indirect` instruction.
+  void call_indirect(FunctionType type, [Table? table]) {
+    assert(_verifyTypes([...type.inputs, NumType.i32], type.outputs,
+        trace: ['call_indirect', type, if (table != null) table.index]));
+    writeByte(0x11);
+    writeUnsigned(type.index);
+    writeUnsigned(table?.index ?? 0);
+  }
+
+  bool _verifyCallRef() {
+    if (!reachable) {
+      return _debugTrace(const ['call_ref'], reachableAfter: false);
+    }
+    ValueType fun = _topOfStack;
+    if (fun is RefType) {
+      var heapType = fun.heapType;
+      if (heapType is FunctionType) {
+        return _verifyTypes([...heapType.inputs, fun], heapType.outputs,
+            trace: const ['call_ref']);
+      }
+    }
+    _reportError("Expected function type, got $fun");
+  }
+
+  /// Emit a `call_ref` instruction.
+  void call_ref() {
+    assert(_verifyCallRef());
+    writeByte(0x14);
+  }
+
+  // Parametric instructions
+
+  /// Emit a `drop` instruction.
+  void drop() {
+    assert(_verifyTypes([_topOfStack], const [], trace: const ['drop']));
+    writeByte(0x1A);
+  }
+
+  /// Emit a `select` instruction.
+  void select(ValueType type) {
+    assert(_verifyTypes([type, type, NumType.i32], [type],
+        trace: ['select', type]));
+    if (type is NumType) {
+      writeByte(0x1B);
+    } else {
+      writeByte(0x1C);
+      writeUnsigned(1);
+      write(type);
+    }
+  }
+
+  // Variable instructions
+
+  /// Emit a `local.get` instruction.
+  void local_get(Local local) {
+    assert(locals[local.index] == local);
+    assert(_verifyTypes(const [], [local.type], trace: ['local.get', local]));
+    writeByte(0x20);
+    writeUnsigned(local.index);
+  }
+
+  /// Emit a `local.set` instruction.
+  void local_set(Local local) {
+    assert(locals[local.index] == local);
+    assert(_verifyTypes([local.type], const [], trace: ['local.set', local]));
+    writeByte(0x21);
+    writeUnsigned(local.index);
+  }
+
+  /// Emit a `local.tee` instruction.
+  void local_tee(Local local) {
+    assert(locals[local.index] == local);
+    assert(
+        _verifyTypes([local.type], [local.type], trace: ['local.tee', local]));
+    writeByte(0x22);
+    writeUnsigned(local.index);
+  }
+
+  /// Emit a `global.get` instruction.
+  void global_get(Global global) {
+    assert(_verifyTypes(const [], [global.type.type],
+        trace: ['global.get', global]));
+    writeByte(0x23);
+    writeUnsigned(global.index);
+  }
+
+  /// Emit a `global.set` instruction.
+  void global_set(Global global) {
+    assert(global.type.mutable);
+    assert(_verifyTypes([global.type.type], const [],
+        trace: ['global.set', global]));
+    writeByte(0x24);
+    writeUnsigned(global.index);
+  }
+
+  // Memory instructions
+
+  void _writeMemArg(Memory memory, int offset, int align) {
+    assert(align >= 0 && align < 64);
+    if (memory.index == 0) {
+      writeByte(align);
+      writeUnsigned(offset);
+    } else {
+      writeByte(64 + align);
+      writeUnsigned(offset);
+      writeUnsigned(memory.index);
+    }
+  }
+
+  /// Emit an `i32.load` instruction.
+  void i32_load(Memory memory, int offset, [int align = 2]) {
+    assert(align >= 0 && align <= 2);
+    assert(_verifyTypes(const [NumType.i32], const [NumType.i32],
+        trace: ['i32.load', memory.index, offset, align]));
+    writeByte(0x28);
+    _writeMemArg(memory, offset, align);
+  }
+
+  /// Emit an `i64.load` instruction.
+  void i64_load(Memory memory, int offset, [int align = 3]) {
+    assert(align >= 0 && align <= 3);
+    assert(_verifyTypes(const [NumType.i32], const [NumType.i64],
+        trace: ['i64.load', memory.index, offset, align]));
+    writeByte(0x29);
+    _writeMemArg(memory, offset, align);
+  }
+
+  /// Emit an `f32.load` instruction.
+  void f32_load(Memory memory, int offset, [int align = 2]) {
+    assert(align >= 0 && align <= 2);
+    assert(_verifyTypes(const [NumType.i32], const [NumType.f32],
+        trace: ['f32.load', memory.index, offset, align]));
+    writeByte(0x2A);
+    _writeMemArg(memory, offset, align);
+  }
+
+  /// Emit an `f64.load` instruction.
+  void f64_load(Memory memory, int offset, [int align = 3]) {
+    assert(align >= 0 && align <= 3);
+    assert(_verifyTypes(const [NumType.i32], const [NumType.f64],
+        trace: ['f64.load', memory.index, offset, align]));
+    writeByte(0x2B);
+    _writeMemArg(memory, offset, align);
+  }
+
+  /// Emit an `i32.load8_s` instruction.
+  void i32_load8_s(Memory memory, int offset, [int align = 0]) {
+    assert(align == 0);
+    assert(_verifyTypes(const [NumType.i32], const [NumType.i32],
+        trace: ['i32.load8_s', memory.index, offset, align]));
+    writeByte(0x2C);
+    _writeMemArg(memory, offset, align);
+  }
+
+  /// Emit an `i32.load8_u` instruction.
+  void i32_load8_u(Memory memory, int offset, [int align = 0]) {
+    assert(align == 0);
+    assert(_verifyTypes(const [NumType.i32], const [NumType.i32],
+        trace: ['i32.load8_u', memory.index, offset, align]));
+    writeByte(0x2D);
+    _writeMemArg(memory, offset, align);
+  }
+
+  /// Emit an `i32.load16_s` instruction.
+  void i32_load16_s(Memory memory, int offset, [int align = 1]) {
+    assert(align >= 0 && align <= 1);
+    assert(_verifyTypes(const [NumType.i32], const [NumType.i32],
+        trace: ['i32.load16_s', memory.index, offset, align]));
+    writeByte(0x2E);
+    _writeMemArg(memory, offset, align);
+  }
+
+  /// Emit an `i32.load16_u` instruction.
+  void i32_load16_u(Memory memory, int offset, [int align = 1]) {
+    assert(align >= 0 && align <= 1);
+    assert(_verifyTypes(const [NumType.i32], const [NumType.i32],
+        trace: ['i32.load16_u', memory.index, offset, align]));
+    writeByte(0x2F);
+    _writeMemArg(memory, offset, align);
+  }
+
+  /// Emit an `i64.load8_s` instruction.
+  void i64_load8_s(Memory memory, int offset, [int align = 0]) {
+    assert(align == 0);
+    assert(_verifyTypes(const [NumType.i32], const [NumType.i64],
+        trace: ['i64.load8_s', memory.index, offset, align]));
+    writeByte(0x30);
+    _writeMemArg(memory, offset, align);
+  }
+
+  /// Emit an `i64.load8_u` instruction.
+  void i64_load8_u(Memory memory, int offset, [int align = 0]) {
+    assert(align == 0);
+    assert(_verifyTypes(const [NumType.i32], const [NumType.i64],
+        trace: ['i64.load8_u', memory.index, offset, align]));
+    writeByte(0x31);
+    _writeMemArg(memory, offset, align);
+  }
+
+  /// Emit an `i64.load16_s` instruction.
+  void i64_load16_s(Memory memory, int offset, [int align = 1]) {
+    assert(align >= 0 && align <= 1);
+    assert(_verifyTypes(const [NumType.i32], const [NumType.i64],
+        trace: ['i64.load16_s', memory.index, offset, align]));
+    writeByte(0x32);
+    _writeMemArg(memory, offset, align);
+  }
+
+  /// Emit an `i64.load16_u` instruction.
+  void i64_load16_u(Memory memory, int offset, [int align = 1]) {
+    assert(align >= 0 && align <= 1);
+    assert(_verifyTypes(const [NumType.i32], const [NumType.i64],
+        trace: ['i64.load16_u', memory.index, offset, align]));
+    writeByte(0x33);
+    _writeMemArg(memory, offset, align);
+  }
+
+  /// Emit an `i64.load32_s` instruction.
+  void i64_load32_s(Memory memory, int offset, [int align = 2]) {
+    assert(align >= 0 && align <= 2);
+    assert(_verifyTypes(const [NumType.i32], const [NumType.i64],
+        trace: ['i64.load32_s', memory.index, offset, align]));
+    writeByte(0x34);
+    _writeMemArg(memory, offset, align);
+  }
+
+  /// Emit an `i64.load32_u` instruction.
+  void i64_load32_u(Memory memory, int offset, [int align = 2]) {
+    assert(align >= 0 && align <= 2);
+    assert(_verifyTypes(const [NumType.i32], const [NumType.i64],
+        trace: ['i64.load32_u', memory.index, offset, align]));
+    writeByte(0x35);
+    _writeMemArg(memory, offset, align);
+  }
+
+  /// Emit an `i32.store` instruction.
+  void i32_store(Memory memory, int offset, [int align = 2]) {
+    assert(align >= 0 && align <= 2);
+    assert(_verifyTypes(const [NumType.i32, NumType.i32], const [],
+        trace: ['i32.store', memory.index, offset, align]));
+    writeByte(0x36);
+    _writeMemArg(memory, offset, align);
+  }
+
+  /// Emit an `i64.store` instruction.
+  void i64_store(Memory memory, int offset, [int align = 3]) {
+    assert(align >= 0 && align <= 3);
+    assert(_verifyTypes(const [NumType.i32, NumType.i64], const [],
+        trace: ['i64.store', memory.index, offset, align]));
+    writeByte(0x37);
+    _writeMemArg(memory, offset, align);
+  }
+
+  /// Emit an `f32.store` instruction.
+  void f32_store(Memory memory, int offset, [int align = 2]) {
+    assert(align >= 0 && align <= 2);
+    assert(_verifyTypes(const [NumType.i32, NumType.f32], const [],
+        trace: ['f32.store', memory.index, offset, align]));
+    writeByte(0x38);
+    _writeMemArg(memory, offset, align);
+  }
+
+  /// Emit an `f64.store` instruction.
+  void f64_store(Memory memory, int offset, [int align = 3]) {
+    assert(align >= 0 && align <= 3);
+    assert(_verifyTypes(const [NumType.i32, NumType.f64], const [],
+        trace: ['f64.store', memory.index, offset, align]));
+    writeByte(0x39);
+    _writeMemArg(memory, offset, align);
+  }
+
+  /// Emit an `i32.store8` instruction.
+  void i32_store8(Memory memory, int offset, [int align = 0]) {
+    assert(align == 0);
+    assert(_verifyTypes(const [NumType.i32, NumType.i32], const [],
+        trace: ['i32.store8', memory.index, offset, align]));
+    writeByte(0x3A);
+    _writeMemArg(memory, offset, align);
+  }
+
+  /// Emit an `i32.store16` instruction.
+  void i32_store16(Memory memory, int offset, [int align = 1]) {
+    assert(align >= 0 && align <= 1);
+    assert(_verifyTypes(const [NumType.i32, NumType.i32], const [],
+        trace: ['i32.store16', memory.index, offset, align]));
+    writeByte(0x3B);
+    _writeMemArg(memory, offset, align);
+  }
+
+  /// Emit an `i64.store8` instruction.
+  void i64_store8(Memory memory, int offset, [int align = 0]) {
+    assert(align == 0);
+    assert(_verifyTypes(const [NumType.i32, NumType.i64], const [],
+        trace: ['i64.store8', memory.index, offset, align]));
+    writeByte(0x3C);
+    _writeMemArg(memory, offset, align);
+  }
+
+  /// Emit an `i64.store16` instruction.
+  void i64_store16(Memory memory, int offset, [int align = 1]) {
+    assert(align >= 0 && align <= 1);
+    assert(_verifyTypes(const [NumType.i32, NumType.i64], const [],
+        trace: ['i64.store16', memory.index, offset, align]));
+    writeByte(0x3D);
+    _writeMemArg(memory, offset, align);
+  }
+
+  /// Emit an `i64.store32` instruction.
+  void i64_store32(Memory memory, int offset, [int align = 2]) {
+    assert(align >= 0 && align <= 2);
+    assert(_verifyTypes(const [NumType.i32, NumType.i64], const [],
+        trace: ['i64.store32', memory.index, offset, align]));
+    writeByte(0x3E);
+    _writeMemArg(memory, offset, align);
+  }
+
+  /// Emit a `memory.size` instruction.
+  void memory_size(Memory memory) {
+    assert(_verifyTypes(const [], const [NumType.i32]));
+    writeByte(0x3F);
+    writeUnsigned(memory.index);
+  }
+
+  /// Emit a `memory.grow` instruction.
+  void memory_grow(Memory memory) {
+    assert(_verifyTypes(const [NumType.i32], const [NumType.i32]));
+    writeByte(0x40);
+    writeUnsigned(memory.index);
+  }
+
+  // Reference instructions
+
+  /// Emit a `ref.null` instruction.
+  void ref_null(HeapType heapType) {
+    assert(_verifyTypes(const [], [RefType(heapType, nullable: true)],
+        trace: ['ref.null', heapType]));
+    writeByte(0xD0);
+    write(heapType);
+  }
+
+  /// Emit a `ref.is_null` instruction.
+  void ref_is_null() {
+    assert(_verifyTypes(const [RefType.any()], const [NumType.i32],
+        trace: const ['ref.is_null']));
+    writeByte(0xD1);
+  }
+
+  /// Emit a `ref.func` instruction.
+  void ref_func(BaseFunction function) {
+    assert(_verifyTypes(const [], [RefType.def(function.type, nullable: false)],
+        trace: ['ref.func', function]));
+    writeByte(0xD2);
+    writeUnsigned(function.index);
+  }
+
+  /// Emit a `ref.as_non_null` instruction.
+  void ref_as_non_null() {
+    assert(_verifyTypes(
+        const [RefType.any()], [_topOfStack.withNullability(false)],
+        trace: const ['ref.as_non_null']));
+    writeByte(0xD3);
+  }
+
+  /// Emit a `br_on_null` instruction.
+  void br_on_null(Label label) {
+    assert(_verifyTypes(
+        const [RefType.any()], [_topOfStack.withNullability(false)],
+        trace: ['br_on_null', label]));
+    assert(_verifyBranchTypes(label, 1));
+    writeByte(0xD4);
+    _writeLabel(label);
+  }
+
+  /// Emit a `ref.eq` instruction.
+  void ref_eq() {
+    assert(_verifyTypes(const [RefType.eq(), RefType.eq()], const [NumType.i32],
+        trace: const ['ref.eq']));
+    writeByte(0xD5);
+  }
+
+  /// Emit a `br_on_non_null` instruction.
+  void br_on_non_null(Label label) {
+    assert(_verifyBranchTypes(label, 1, [_topOfStack.withNullability(false)]));
+    assert(_verifyTypes(const [RefType.any()], const [],
+        trace: ['br_on_non_null', label]));
+    writeByte(0xD6);
+    _writeLabel(label);
+  }
+
+  /// Emit a `struct.new_with_rtt` instruction.
+  void struct_new_with_rtt(StructType structType) {
+    assert(_verifyTypes(
+        [...structType.fields.map((f) => f.type.unpacked), Rtt(structType)],
+        [RefType.def(structType, nullable: false)],
+        trace: ['struct.new_with_rtt', structType]));
+    writeBytes(const [0xFB, 0x01]);
+    writeUnsigned(structType.index);
+  }
+
+  /// Emit a `struct.new_default_with_rtt` instruction.
+  void struct_new_default_with_rtt(StructType structType) {
+    assert(_verifyTypes(
+        [Rtt(structType)], [RefType.def(structType, nullable: false)],
+        trace: ['struct.new_default_with_rtt', structType]));
+    writeBytes(const [0xFB, 0x02]);
+    writeUnsigned(structType.index);
+  }
+
+  /// Emit a `struct.get` instruction.
+  void struct_get(StructType structType, int fieldIndex) {
+    assert(structType.fields[fieldIndex].type is ValueType);
+    assert(_verifyTypes([RefType.def(structType, nullable: true)],
+        [structType.fields[fieldIndex].type.unpacked],
+        trace: ['struct.get', structType, fieldIndex]));
+    writeBytes(const [0xFB, 0x03]);
+    writeUnsigned(structType.index);
+    writeUnsigned(fieldIndex);
+  }
+
+  /// Emit a `struct.get_s` instruction.
+  void struct_get_s(StructType structType, int fieldIndex) {
+    assert(structType.fields[fieldIndex].type is PackedType);
+    assert(_verifyTypes([RefType.def(structType, nullable: true)],
+        [structType.fields[fieldIndex].type.unpacked],
+        trace: ['struct.get_s', structType, fieldIndex]));
+    writeBytes(const [0xFB, 0x04]);
+    writeUnsigned(structType.index);
+    writeUnsigned(fieldIndex);
+  }
+
+  /// Emit a `struct.get_u` instruction.
+  void struct_get_u(StructType structType, int fieldIndex) {
+    assert(structType.fields[fieldIndex].type is PackedType);
+    assert(_verifyTypes([RefType.def(structType, nullable: true)],
+        [structType.fields[fieldIndex].type.unpacked],
+        trace: ['struct.get_u', structType, fieldIndex]));
+    writeBytes(const [0xFB, 0x05]);
+    writeUnsigned(structType.index);
+    writeUnsigned(fieldIndex);
+  }
+
+  /// Emit a `struct.set` instruction.
+  void struct_set(StructType structType, int fieldIndex) {
+    assert(_verifyTypes([
+      RefType.def(structType, nullable: true),
+      structType.fields[fieldIndex].type.unpacked
+    ], const [], trace: [
+      'struct.set',
+      structType,
+      fieldIndex
+    ]));
+    writeBytes(const [0xFB, 0x06]);
+    writeUnsigned(structType.index);
+    writeUnsigned(fieldIndex);
+  }
+
+  /// Emit a `struct.new` instruction.
+  void struct_new(StructType structType) {
+    assert(_verifyTypes([...structType.fields.map((f) => f.type.unpacked)],
+        [RefType.def(structType, nullable: false)],
+        trace: ['struct.new', structType]));
+    writeBytes(const [0xFB, 0x07]);
+    writeUnsigned(structType.index);
+  }
+
+  /// Emit a `struct.new_default` instruction.
+  void struct_new_default(StructType structType) {
+    assert(_verifyTypes(const [], [RefType.def(structType, nullable: false)],
+        trace: ['struct.new_default', structType]));
+    writeBytes(const [0xFB, 0x08]);
+    writeUnsigned(structType.index);
+  }
+
+  /// Emit an `array.new_with_rtt` instruction.
+  void array_new_with_rtt(ArrayType arrayType) {
+    assert(_verifyTypes(
+        [arrayType.elementType.type.unpacked, NumType.i32, Rtt(arrayType)],
+        [RefType.def(arrayType, nullable: false)],
+        trace: ['array.new_with_rtt', arrayType]));
+    writeBytes(const [0xFB, 0x11]);
+    writeUnsigned(arrayType.index);
+  }
+
+  /// Emit an `array.new_default_with_rtt` instruction.
+  void array_new_default_with_rtt(ArrayType arrayType) {
+    assert(_verifyTypes([NumType.i32, Rtt(arrayType)],
+        [RefType.def(arrayType, nullable: false)],
+        trace: ['array.new_default_with_rtt', arrayType]));
+    writeBytes(const [0xFB, 0x12]);
+    writeUnsigned(arrayType.index);
+  }
+
+  /// Emit an `array.get` instruction.
+  void array_get(ArrayType arrayType) {
+    assert(arrayType.elementType.type is ValueType);
+    assert(_verifyTypes([RefType.def(arrayType, nullable: true), NumType.i32],
+        [arrayType.elementType.type.unpacked],
+        trace: ['array.get', arrayType]));
+    writeBytes(const [0xFB, 0x13]);
+    writeUnsigned(arrayType.index);
+  }
+
+  /// Emit an `array.get_s` instruction.
+  void array_get_s(ArrayType arrayType) {
+    assert(arrayType.elementType.type is PackedType);
+    assert(_verifyTypes([RefType.def(arrayType, nullable: true), NumType.i32],
+        [arrayType.elementType.type.unpacked],
+        trace: ['array.get_s', arrayType]));
+    writeBytes(const [0xFB, 0x14]);
+    writeUnsigned(arrayType.index);
+  }
+
+  /// Emit an `array.get_u` instruction.
+  void array_get_u(ArrayType arrayType) {
+    assert(arrayType.elementType.type is PackedType);
+    assert(_verifyTypes([RefType.def(arrayType, nullable: true), NumType.i32],
+        [arrayType.elementType.type.unpacked],
+        trace: ['array.get_u', arrayType]));
+    writeBytes(const [0xFB, 0x15]);
+    writeUnsigned(arrayType.index);
+  }
+
+  /// Emit an `array.set` instruction.
+  void array_set(ArrayType arrayType) {
+    assert(_verifyTypes([
+      RefType.def(arrayType, nullable: true),
+      NumType.i32,
+      arrayType.elementType.type.unpacked
+    ], const [], trace: [
+      'array.set',
+      arrayType
+    ]));
+    writeBytes(const [0xFB, 0x16]);
+    writeUnsigned(arrayType.index);
+  }
+
+  /// Emit an `array.len` instruction.
+  void array_len(ArrayType arrayType) {
+    assert(_verifyTypes(
+        [RefType.def(arrayType, nullable: true)], const [NumType.i32],
+        trace: ['array.len', arrayType]));
+    writeBytes(const [0xFB, 0x17]);
+    writeUnsigned(arrayType.index);
+  }
+
+  /// Emit an `array.init` instruction.
+  void array_init(ArrayType arrayType, int length) {
+    ValueType elementType = arrayType.elementType.type.unpacked;
+    assert(_verifyTypes([...List.filled(length, elementType), Rtt(arrayType)],
+        [RefType.def(arrayType, nullable: false)],
+        trace: ['array.init', arrayType, length]));
+    writeBytes(const [0xFB, 0x19]);
+    writeUnsigned(arrayType.index);
+    writeUnsigned(length);
+  }
+
+  /// Emit an `array.init_static` instruction.
+  void array_init_static(ArrayType arrayType, int length) {
+    ValueType elementType = arrayType.elementType.type.unpacked;
+    assert(_verifyTypes([...List.filled(length, elementType)],
+        [RefType.def(arrayType, nullable: false)],
+        trace: ['array.init_static', arrayType, length]));
+    writeBytes(const [0xFB, 0x1a]);
+    writeUnsigned(arrayType.index);
+    writeUnsigned(length);
+  }
+
+  /// Emit an `array.new` instruction.
+  void array_new(ArrayType arrayType) {
+    assert(_verifyTypes([arrayType.elementType.type.unpacked, NumType.i32],
+        [RefType.def(arrayType, nullable: false)],
+        trace: ['array.new', arrayType]));
+    writeBytes(const [0xFB, 0x1b]);
+    writeUnsigned(arrayType.index);
+  }
+
+  /// Emit an `array.new_default` instruction.
+  void array_new_default(ArrayType arrayType) {
+    assert(_verifyTypes(
+        [NumType.i32], [RefType.def(arrayType, nullable: false)],
+        trace: ['array.new_default', arrayType]));
+    writeBytes(const [0xFB, 0x1c]);
+    writeUnsigned(arrayType.index);
+  }
+
+  /// Emit an `array.init_from_data_static` instruction.
+  void array_init_from_data_static(ArrayType arrayType, DataSegment data) {
+    assert(arrayType.elementType.type.isPrimitive);
+    assert(_verifyTypes(
+        [NumType.i32, NumType.i32], [RefType.def(arrayType, nullable: false)],
+        trace: ['array.init_from_data_static', arrayType, data.index]));
+    writeBytes(const [0xFB, 0x1d]);
+    writeUnsigned(arrayType.index);
+    writeUnsigned(data.index);
+    if (isGlobalInitializer) module.dataReferencedFromGlobalInitializer = true;
+  }
+
+  /// Emit an `array.init_from_data` instruction.
+  void array_init_from_data(ArrayType arrayType, DataSegment data) {
+    assert(arrayType.elementType.type.isPrimitive);
+    assert(_verifyTypes([NumType.i32, NumType.i32, Rtt(arrayType)],
+        [RefType.def(arrayType, nullable: false)],
+        trace: ['array.init_from_data', arrayType, data.index]));
+    writeBytes(const [0xFB, 0x1e]);
+    writeUnsigned(arrayType.index);
+    writeUnsigned(data.index);
+    if (isGlobalInitializer) module.dataReferencedFromGlobalInitializer = true;
+  }
+
+  /// Emit an `i31.new` instruction.
+  void i31_new() {
+    assert(_verifyTypes(const [NumType.i32], const [RefType.i31()],
+        trace: const ['i31.new']));
+    writeBytes(const [0xFB, 0x20]);
+  }
+
+  /// Emit an `i31.get_s` instruction.
+  void i31_get_s() {
+    assert(_verifyTypes(const [RefType.i31()], const [NumType.i32],
+        trace: const ['i31.get_s']));
+    writeBytes(const [0xFB, 0x21]);
+  }
+
+  /// Emit an `i31.get_u` instruction.
+  void i31_get_u() {
+    assert(_verifyTypes(const [RefType.i31()], const [NumType.i32],
+        trace: const ['i31.get_u']));
+    writeBytes(const [0xFB, 0x22]);
+  }
+
+  /// Emit an `rtt.canon` instruction.
+  void rtt_canon(DefType defType) {
+    assert(_verifyTypes(const [], [Rtt(defType, defType.depth)],
+        trace: ['rtt.canon', defType]));
+    writeBytes(const [0xFB, 0x30]);
+    writeSigned(defType.index);
+  }
+
+  bool _verifyRttSub(DefType subType) {
+    if (!reachable) {
+      return _debugTrace(['rtt.sub', subType], reachableAfter: false);
+    }
+    final ValueType input = _topOfStack;
+    if (input is! Rtt) _reportError("Expected rtt, but stack contained $input");
+    final int? depth = input.depth;
+    if (depth == null) _reportError("Expected rtt with known depth");
+    final DefType superType = input.defType;
+    if (!subType.isSubtypeOf(superType)) {
+      _reportError("Expected supertype of $subType, but got $superType");
+    }
+    return _verifyTypes([input], [Rtt(subType, depth + 1)],
+        trace: ['rtt.sub', subType]);
+  }
+
+  /// Emit an `rtt.sub` instruction.
+  void rtt_sub(DefType defType) {
+    assert(_verifyRttSub(defType));
+    writeBytes(const [0xFB, 0x31]);
+    writeSigned(defType.index);
+  }
+
+  bool _verifyCast(List<ValueType> Function(List<ValueType>) outputsFun,
+      {List<Object>? trace}) {
+    if (!reachable) {
+      return _debugTrace(trace, reachableAfter: false);
+    }
+    final stack = _stack(2);
+    final ValueType value = stack[0];
+    final ValueType rtt = stack[1];
+    if (rtt is! Rtt ||
+        !value.isSubtypeOf(const RefType.data(nullable: true)) &&
+            !value.isSubtypeOf(const RefType.func(nullable: true))) {
+      _reportError("Expected [data or func, rtt], but stack contained $stack");
+    }
+    return _verifyTypesFun(stack, outputsFun, trace: trace);
+  }
+
+  /// Emit a `ref.test` instruction.
+  void ref_test() {
+    assert(_verifyCast((_) => const [NumType.i32], trace: const ['ref.test']));
+    writeBytes(const [0xFB, 0x40]);
+  }
+
+  /// Emit a `ref.cast` instruction.
+  void ref_cast() {
+    assert(_verifyCast(
+        (inputs) => [
+              RefType.def((inputs[1] as Rtt).defType,
+                  nullable: inputs[0].nullable)
+            ],
+        trace: const ['ref.cast']));
+    writeBytes(const [0xFB, 0x41]);
+  }
+
+  /// Emit a `br_on_cast` instruction.
+  void br_on_cast(Label label) {
+    late final DefType targetType;
+    assert(_verifyCast((inputs) {
+      targetType = (inputs[1] as Rtt).defType;
+      return [inputs[0]];
+    }, trace: ['br_on_cast', label]));
+    assert(_verifyBranchTypes(
+        label, 1, [RefType.def(targetType, nullable: false)]));
+    writeBytes(const [0xFB, 0x42]);
+    _writeLabel(label);
+  }
+
+  /// Emit a `br_on_cast_fail` instruction.
+  void br_on_cast_fail(Label label) {
+    assert(_verifyBranchTypes(label, 1, [_topOfStack]));
+    assert(_verifyCast(
+        (inputs) => [RefType.def((inputs[1] as Rtt).defType, nullable: false)],
+        trace: ['br_on_cast_fail', label]));
+    writeBytes(const [0xFB, 0x43]);
+    _writeLabel(label);
+  }
+
+  bool _verifyCastStatic(List<ValueType> Function(List<ValueType>) outputsFun,
+      {List<Object>? trace}) {
+    if (!reachable) {
+      return _debugTrace(trace, reachableAfter: false);
+    }
+    final ValueType value = _topOfStack;
+    if (!value.isSubtypeOf(const RefType.data(nullable: true)) &&
+        !value.isSubtypeOf(const RefType.func(nullable: true))) {
+      _reportError("Expected [data or func], but stack contained [$value]");
+    }
+    return _verifyTypesFun([value], outputsFun, trace: trace);
+  }
+
+  /// Emit a `ref.test_static` instruction.
+  void ref_test_static(DefType targetType) {
+    assert(_verifyCastStatic((_) => const [NumType.i32],
+        trace: ['ref.test_static', targetType]));
+    writeBytes(const [0xFB, 0x44]);
+    writeSigned(targetType.index);
+  }
+
+  /// Emit a `ref.cast_static` instruction.
+  void ref_cast_static(DefType targetType) {
+    assert(_verifyCastStatic(
+        (inputs) => [RefType.def(targetType, nullable: inputs[0].nullable)],
+        trace: ['ref.cast_static', targetType]));
+    writeBytes(const [0xFB, 0x45]);
+    writeSigned(targetType.index);
+  }
+
+  /// Emit a `br_on_cast_static` instruction.
+  void br_on_cast_static(Label label, DefType targetType) {
+    assert(_verifyCastStatic((inputs) {
+      return [inputs[0]];
+    }, trace: ['br_on_cast_static', label, targetType]));
+    assert(_verifyBranchTypes(
+        label, 1, [RefType.def(targetType, nullable: false)]));
+    writeBytes(const [0xFB, 0x46]);
+    _writeLabel(label);
+    writeSigned(targetType.index);
+  }
+
+  /// Emit a `br_on_cast_static_fail` instruction.
+  void br_on_cast_static_fail(Label label, DefType targetType) {
+    assert(_verifyBranchTypes(label, 1, [_topOfStack]));
+    assert(_verifyCast((inputs) => [RefType.def(targetType, nullable: false)],
+        trace: ['br_on_cast_static_fail', label, targetType]));
+    writeBytes(const [0xFB, 0x47]);
+    _writeLabel(label);
+    writeSigned(targetType.index);
+  }
+
+  /// Emit a `ref.is_func` instruction.
+  void ref_is_func() {
+    assert(_verifyTypes(const [RefType.any()], const [NumType.i32],
+        trace: const ['ref.is_func']));
+    writeBytes(const [0xFB, 0x50]);
+  }
+
+  /// Emit a `ref.is_data` instruction.
+  void ref_is_data() {
+    assert(_verifyTypes(const [RefType.any()], const [NumType.i32],
+        trace: const ['ref.is_data']));
+    writeBytes(const [0xFB, 0x51]);
+  }
+
+  /// Emit a `ref.is_i31` instruction.
+  void ref_is_i31() {
+    assert(_verifyTypes(const [RefType.any()], const [NumType.i32],
+        trace: const ['ref.is_i31']));
+    writeBytes(const [0xFB, 0x52]);
+  }
+
+  /// Emit a `ref.as_func` instruction.
+  void ref_as_func() {
+    assert(_verifyTypes(
+        const [RefType.any()], const [RefType.func(nullable: false)],
+        trace: const ['ref.as_func']));
+    writeBytes(const [0xFB, 0x58]);
+  }
+
+  /// Emit a `ref.as_data` instruction.
+  void ref_as_data() {
+    assert(_verifyTypes(
+        const [RefType.any()], const [RefType.data(nullable: false)],
+        trace: const ['ref.as_data']));
+    writeBytes(const [0xFB, 0x59]);
+  }
+
+  /// Emit a `ref.as_i31` instruction.
+  void ref_as_i31() {
+    assert(_verifyTypes(
+        const [RefType.any()], const [RefType.i31(nullable: false)],
+        trace: const ['ref.as_i31']));
+    writeBytes(const [0xFB, 0x5A]);
+  }
+
+  /// Emit a `br_on_func` instruction.
+  void br_on_func(Label label) {
+    assert(_verifyTypes(const [RefType.any()], [_topOfStack],
+        trace: ['br_on_func', label]));
+    assert(_verifyBranchTypes(label, 1, const [RefType.func(nullable: false)]));
+    writeBytes(const [0xFB, 0x60]);
+    _writeLabel(label);
+  }
+
+  /// Emit a `br_on_data` instruction.
+  void br_on_data(Label label) {
+    assert(_verifyTypes(const [RefType.any()], [_topOfStack],
+        trace: ['br_on_data', label]));
+    assert(_verifyBranchTypes(label, 1, const [RefType.data(nullable: false)]));
+    writeBytes(const [0xFB, 0x61]);
+    _writeLabel(label);
+  }
+
+  /// Emit a `br_on_i31` instruction.
+  void br_on_i31(Label label) {
+    assert(_verifyTypes(const [RefType.any()], [_topOfStack],
+        trace: ['br_on_i31', label]));
+    assert(_verifyBranchTypes(label, 1, const [RefType.i31(nullable: false)]));
+    writeBytes(const [0xFB, 0x62]);
+    _writeLabel(label);
+  }
+
+  /// Emit a `br_on_non_func` instruction.
+  void br_on_non_func(Label label) {
+    assert(_verifyBranchTypes(label, 1, [_topOfStack]));
+    assert(_verifyTypes(
+        const [RefType.any()], const [RefType.func(nullable: false)],
+        trace: ['br_on_non_func', label]));
+    writeBytes(const [0xFB, 0x63]);
+    _writeLabel(label);
+  }
+
+  /// Emit a `br_on_non_data` instruction.
+  void br_on_non_data(Label label) {
+    assert(_verifyBranchTypes(label, 1, [_topOfStack]));
+    assert(_verifyTypes(
+        const [RefType.any()], const [RefType.data(nullable: false)],
+        trace: ['br_on_non_data', label]));
+    writeBytes(const [0xFB, 0x64]);
+    _writeLabel(label);
+  }
+
+  /// Emit a `br_on_non_i31` instruction.
+  void br_on_non_i31(Label label) {
+    assert(_verifyBranchTypes(label, 1, [_topOfStack]));
+    assert(_verifyTypes(
+        const [RefType.any()], const [RefType.i31(nullable: false)],
+        trace: ['br_on_non_i31', label]));
+    writeBytes(const [0xFB, 0x65]);
+    _writeLabel(label);
+  }
+
+  // Numeric instructions
+
+  /// Emit an `i32.const` instruction.
+  void i32_const(int value) {
+    assert(_verifyTypes(const [], const [NumType.i32],
+        trace: ['i32.const', value]));
+    assert(-1 << 31 <= value && value < 1 << 31);
+    writeByte(0x41);
+    writeSigned(value);
+  }
+
+  /// Emit an `i64.const` instruction.
+  void i64_const(int value) {
+    assert(_verifyTypes(const [], const [NumType.i64],
+        trace: ['i64.const', value]));
+    writeByte(0x42);
+    writeSigned(value);
+  }
+
+  /// Emit an `f32.const` instruction.
+  void f32_const(double value) {
+    assert(_verifyTypes(const [], const [NumType.f32],
+        trace: ['f32.const', value]));
+    writeByte(0x43);
+    writeF32(value);
+  }
+
+  /// Emit an `f64.const` instruction.
+  void f64_const(double value) {
+    assert(_verifyTypes(const [], const [NumType.f64],
+        trace: ['f64.const', value]));
+    writeByte(0x44);
+    writeF64(value);
+  }
+
+  /// Emit an `i32.eqz` instruction.
+  void i32_eqz() {
+    assert(_verifyTypes(const [NumType.i32], const [NumType.i32],
+        trace: const ['i32.eqz']));
+    writeByte(0x45);
+  }
+
+  /// Emit an `i32.eq` instruction.
+  void i32_eq() {
+    assert(_verifyTypes(const [NumType.i32, NumType.i32], const [NumType.i32],
+        trace: const ['i32.eq']));
+    writeByte(0x46);
+  }
+
+  /// Emit an `i32.ne` instruction.
+  void i32_ne() {
+    assert(_verifyTypes(const [NumType.i32, NumType.i32], const [NumType.i32],
+        trace: const ['i32.ne']));
+    writeByte(0x47);
+  }
+
+  /// Emit an `i32.lt_s` instruction.
+  void i32_lt_s() {
+    assert(_verifyTypes(const [NumType.i32, NumType.i32], const [NumType.i32],
+        trace: const ['i32.lt_s']));
+    writeByte(0x48);
+  }
+
+  /// Emit an `i32.lt_u` instruction.
+  void i32_lt_u() {
+    assert(_verifyTypes(const [NumType.i32, NumType.i32], const [NumType.i32],
+        trace: const ['i32.lt_u']));
+    writeByte(0x49);
+  }
+
+  /// Emit an `i32.gt_s` instruction.
+  void i32_gt_s() {
+    assert(_verifyTypes(const [NumType.i32, NumType.i32], const [NumType.i32],
+        trace: const ['i32.gt_s']));
+    writeByte(0x4A);
+  }
+
+  /// Emit an `i32.gt_u` instruction.
+  void i32_gt_u() {
+    assert(_verifyTypes(const [NumType.i32, NumType.i32], const [NumType.i32],
+        trace: const ['i32.gt_u']));
+    writeByte(0x4B);
+  }
+
+  /// Emit an `i32.le_s` instruction.
+  void i32_le_s() {
+    assert(_verifyTypes(const [NumType.i32, NumType.i32], const [NumType.i32],
+        trace: const ['i32.le_s']));
+    writeByte(0x4C);
+  }
+
+  /// Emit an `i32.le_u` instruction.
+  void i32_le_u() {
+    assert(_verifyTypes(const [NumType.i32, NumType.i32], const [NumType.i32],
+        trace: const ['i32.le_u']));
+    writeByte(0x4D);
+  }
+
+  /// Emit an `i32.ge_s` instruction.
+  void i32_ge_s() {
+    assert(_verifyTypes(const [NumType.i32, NumType.i32], const [NumType.i32],
+        trace: const ['i32.ge_s']));
+    writeByte(0x4E);
+  }
+
+  /// Emit an `i32.ge_u` instruction.
+  void i32_ge_u() {
+    assert(_verifyTypes(const [NumType.i32, NumType.i32], const [NumType.i32],
+        trace: const ['i32.ge_u']));
+    writeByte(0x4F);
+  }
+
+  /// Emit an `i64.eqz` instruction.
+  void i64_eqz() {
+    assert(_verifyTypes(const [NumType.i64], const [NumType.i32],
+        trace: const ['i64.eqz']));
+    writeByte(0x50);
+  }
+
+  /// Emit an `i64.eq` instruction.
+  void i64_eq() {
+    assert(_verifyTypes(const [NumType.i64, NumType.i64], const [NumType.i32],
+        trace: const ['i64.eq']));
+    writeByte(0x51);
+  }
+
+  /// Emit an `i64.ne` instruction.
+  void i64_ne() {
+    assert(_verifyTypes(const [NumType.i64, NumType.i64], const [NumType.i32],
+        trace: const ['i64.ne']));
+    writeByte(0x52);
+  }
+
+  /// Emit an `i64.lt_s` instruction.
+  void i64_lt_s() {
+    assert(_verifyTypes(const [NumType.i64, NumType.i64], const [NumType.i32],
+        trace: const ['i64.lt_s']));
+    writeByte(0x53);
+  }
+
+  /// Emit an `i64.lt_u` instruction.
+  void i64_lt_u() {
+    assert(_verifyTypes(const [NumType.i64, NumType.i64], const [NumType.i32],
+        trace: const ['i64.lt_u']));
+    writeByte(0x54);
+  }
+
+  /// Emit an `i64.gt_s` instruction.
+  void i64_gt_s() {
+    assert(_verifyTypes(const [NumType.i64, NumType.i64], const [NumType.i32],
+        trace: const ['i64.gt_s']));
+    writeByte(0x55);
+  }
+
+  /// Emit an `i64.gt_u` instruction.
+  void i64_gt_u() {
+    assert(_verifyTypes(const [NumType.i64, NumType.i64], const [NumType.i32],
+        trace: const ['i64.gt_u']));
+    writeByte(0x56);
+  }
+
+  /// Emit an `i64.le_s` instruction.
+  void i64_le_s() {
+    assert(_verifyTypes(const [NumType.i64, NumType.i64], const [NumType.i32],
+        trace: const ['i64.le_s']));
+    writeByte(0x57);
+  }
+
+  /// Emit an `i64.le_u` instruction.
+  void i64_le_u() {
+    assert(_verifyTypes(const [NumType.i64, NumType.i64], const [NumType.i32],
+        trace: const ['i64.le_u']));
+    writeByte(0x58);
+  }
+
+  /// Emit an `i64.ge_s` instruction.
+  void i64_ge_s() {
+    assert(_verifyTypes(const [NumType.i64, NumType.i64], const [NumType.i32],
+        trace: const ['i64.ge_s']));
+    writeByte(0x59);
+  }
+
+  /// Emit an `i64.ge_u` instruction.
+  void i64_ge_u() {
+    assert(_verifyTypes(const [NumType.i64, NumType.i64], const [NumType.i32],
+        trace: const ['i64.ge_u']));
+    writeByte(0x5A);
+  }
+
+  /// Emit an `f32.eq` instruction.
+  void f32_eq() {
+    assert(_verifyTypes(const [NumType.f32, NumType.f32], const [NumType.i32],
+        trace: const ['f32.eq']));
+    writeByte(0x5B);
+  }
+
+  /// Emit an `f32.ne` instruction.
+  void f32_ne() {
+    assert(_verifyTypes(const [NumType.f32, NumType.f32], const [NumType.i32],
+        trace: const ['f32.ne']));
+    writeByte(0x5C);
+  }
+
+  /// Emit an `f32.lt` instruction.
+  void f32_lt() {
+    assert(_verifyTypes(const [NumType.f32, NumType.f32], const [NumType.i32],
+        trace: const ['f32.lt']));
+    writeByte(0x5D);
+  }
+
+  /// Emit an `f32.gt` instruction.
+  void f32_gt() {
+    assert(_verifyTypes(const [NumType.f32, NumType.f32], const [NumType.i32],
+        trace: const ['f32.gt']));
+    writeByte(0x5E);
+  }
+
+  /// Emit an `f32.le` instruction.
+  void f32_le() {
+    assert(_verifyTypes(const [NumType.f32, NumType.f32], const [NumType.i32],
+        trace: const ['f32.le']));
+    writeByte(0x5F);
+  }
+
+  /// Emit an `f32.ge` instruction.
+  void f32_ge() {
+    assert(_verifyTypes(const [NumType.f32, NumType.f32], const [NumType.i32],
+        trace: const ['f32.ge']));
+    writeByte(0x60);
+  }
+
+  /// Emit an `f64.eq` instruction.
+  void f64_eq() {
+    assert(_verifyTypes(const [NumType.f64, NumType.f64], const [NumType.i32],
+        trace: const ['f64.eq']));
+    writeByte(0x61);
+  }
+
+  /// Emit an `f64.ne` instruction.
+  void f64_ne() {
+    assert(_verifyTypes(const [NumType.f64, NumType.f64], const [NumType.i32],
+        trace: const ['f64.ne']));
+    writeByte(0x62);
+  }
+
+  /// Emit an `f64.lt` instruction.
+  void f64_lt() {
+    assert(_verifyTypes(const [NumType.f64, NumType.f64], const [NumType.i32],
+        trace: const ['f64.lt']));
+    writeByte(0x63);
+  }
+
+  /// Emit an `f64.gt` instruction.
+  void f64_gt() {
+    assert(_verifyTypes(const [NumType.f64, NumType.f64], const [NumType.i32],
+        trace: const ['f64.gt']));
+    writeByte(0x64);
+  }
+
+  /// Emit an `f64.le` instruction.
+  void f64_le() {
+    assert(_verifyTypes(const [NumType.f64, NumType.f64], const [NumType.i32],
+        trace: const ['f64.le']));
+    writeByte(0x65);
+  }
+
+  /// Emit an `f64.ge` instruction.
+  void f64_ge() {
+    assert(_verifyTypes(const [NumType.f64, NumType.f64], const [NumType.i32],
+        trace: const ['f64.ge']));
+    writeByte(0x66);
+  }
+
+  /// Emit an `i32.clz` instruction.
+  void i32_clz() {
+    assert(_verifyTypes(const [NumType.i32], const [NumType.i32],
+        trace: const ['i32.clz']));
+    writeByte(0x67);
+  }
+
+  /// Emit an `i32.ctz` instruction.
+  void i32_ctz() {
+    assert(_verifyTypes(const [NumType.i32], const [NumType.i32],
+        trace: const ['i32.ctz']));
+    writeByte(0x68);
+  }
+
+  /// Emit an `i32.popcnt` instruction.
+  void i32_popcnt() {
+    assert(_verifyTypes(const [NumType.i32], const [NumType.i32],
+        trace: const ['i32.popcnt']));
+    writeByte(0x69);
+  }
+
+  /// Emit an `i32.add` instruction.
+  void i32_add() {
+    assert(_verifyTypes(const [NumType.i32, NumType.i32], const [NumType.i32],
+        trace: const ['i32.add']));
+    writeByte(0x6A);
+  }
+
+  /// Emit an `i32.sub` instruction.
+  void i32_sub() {
+    assert(_verifyTypes(const [NumType.i32, NumType.i32], const [NumType.i32],
+        trace: const ['i32.sub']));
+    writeByte(0x6B);
+  }
+
+  /// Emit an `i32.mul` instruction.
+  void i32_mul() {
+    assert(_verifyTypes(const [NumType.i32, NumType.i32], const [NumType.i32],
+        trace: const ['i32.mul']));
+    writeByte(0x6C);
+  }
+
+  /// Emit an `i32.div_s` instruction.
+  void i32_div_s() {
+    assert(_verifyTypes(const [NumType.i32, NumType.i32], const [NumType.i32],
+        trace: const ['i32.div_s']));
+    writeByte(0x6D);
+  }
+
+  /// Emit an `i32.div_u` instruction.
+  void i32_div_u() {
+    assert(_verifyTypes(const [NumType.i32, NumType.i32], const [NumType.i32],
+        trace: const ['i32.div_u']));
+    writeByte(0x6E);
+  }
+
+  /// Emit an `i32.rem_s` instruction.
+  void i32_rem_s() {
+    assert(_verifyTypes(const [NumType.i32, NumType.i32], const [NumType.i32],
+        trace: const ['i32.rem_s']));
+    writeByte(0x6F);
+  }
+
+  /// Emit an `i32.rem_u` instruction.
+  void i32_rem_u() {
+    assert(_verifyTypes(const [NumType.i32, NumType.i32], const [NumType.i32],
+        trace: const ['i32.rem_u']));
+    writeByte(0x70);
+  }
+
+  /// Emit an `i32.and` instruction.
+  void i32_and() {
+    assert(_verifyTypes(const [NumType.i32, NumType.i32], const [NumType.i32],
+        trace: const ['i32.and']));
+    writeByte(0x71);
+  }
+
+  /// Emit an `i32.or` instruction.
+  void i32_or() {
+    assert(_verifyTypes(const [NumType.i32, NumType.i32], const [NumType.i32],
+        trace: const ['i32.or']));
+    writeByte(0x72);
+  }
+
+  /// Emit an `i32.xor` instruction.
+  void i32_xor() {
+    assert(_verifyTypes(const [NumType.i32, NumType.i32], const [NumType.i32],
+        trace: const ['i32.xor']));
+    writeByte(0x73);
+  }
+
+  /// Emit an `i32.shl` instruction.
+  void i32_shl() {
+    assert(_verifyTypes(const [NumType.i32, NumType.i32], const [NumType.i32],
+        trace: const ['i32.shl']));
+    writeByte(0x74);
+  }
+
+  /// Emit an `i32.shr_s` instruction.
+  void i32_shr_s() {
+    assert(_verifyTypes(const [NumType.i32, NumType.i32], const [NumType.i32],
+        trace: const ['i32.shr_s']));
+    writeByte(0x75);
+  }
+
+  /// Emit an `i32.shr_u` instruction.
+  void i32_shr_u() {
+    assert(_verifyTypes(const [NumType.i32, NumType.i32], const [NumType.i32],
+        trace: const ['i32.shr_u']));
+    writeByte(0x76);
+  }
+
+  /// Emit an `i32.rotl` instruction.
+  void i32_rotl() {
+    assert(_verifyTypes(const [NumType.i32, NumType.i32], const [NumType.i32],
+        trace: const ['i32.rotl']));
+    writeByte(0x77);
+  }
+
+  /// Emit an `i32.rotr` instruction.
+  void i32_rotr() {
+    assert(_verifyTypes(const [NumType.i32, NumType.i32], const [NumType.i32],
+        trace: const ['i32.rotr']));
+    writeByte(0x78);
+  }
+
+  /// Emit an `i64.clz` instruction.
+  void i64_clz() {
+    assert(_verifyTypes(const [NumType.i64], const [NumType.i64],
+        trace: const ['i64.clz']));
+    writeByte(0x79);
+  }
+
+  /// Emit an `i64.ctz` instruction.
+  void i64_ctz() {
+    assert(_verifyTypes(const [NumType.i64], const [NumType.i64],
+        trace: const ['i64.ctz']));
+    writeByte(0x7A);
+  }
+
+  /// Emit an `i64.popcnt` instruction.
+  void i64_popcnt() {
+    assert(_verifyTypes(const [NumType.i64], const [NumType.i64],
+        trace: const ['i64.popcnt']));
+    writeByte(0x7B);
+  }
+
+  /// Emit an `i64.add` instruction.
+  void i64_add() {
+    assert(_verifyTypes(const [NumType.i64, NumType.i64], const [NumType.i64],
+        trace: const ['i64.add']));
+    writeByte(0x7C);
+  }
+
+  /// Emit an `i64.sub` instruction.
+  void i64_sub() {
+    assert(_verifyTypes(const [NumType.i64, NumType.i64], const [NumType.i64],
+        trace: const ['i64.sub']));
+    writeByte(0x7D);
+  }
+
+  /// Emit an `i64.mul` instruction.
+  void i64_mul() {
+    assert(_verifyTypes(const [NumType.i64, NumType.i64], const [NumType.i64],
+        trace: const ['i64.mul']));
+    writeByte(0x7E);
+  }
+
+  /// Emit an `i64.div_s` instruction.
+  void i64_div_s() {
+    assert(_verifyTypes(const [NumType.i64, NumType.i64], const [NumType.i64],
+        trace: const ['i64.div_s']));
+    writeByte(0x7F);
+  }
+
+  /// Emit an `i64.div_u` instruction.
+  void i64_div_u() {
+    assert(_verifyTypes(const [NumType.i64, NumType.i64], const [NumType.i64],
+        trace: const ['i64.div_u']));
+    writeByte(0x80);
+  }
+
+  /// Emit an `i64.rem_s` instruction.
+  void i64_rem_s() {
+    assert(_verifyTypes(const [NumType.i64, NumType.i64], const [NumType.i64],
+        trace: const ['i64.rem_s']));
+    writeByte(0x81);
+  }
+
+  /// Emit an `i64.rem_u` instruction.
+  void i64_rem_u() {
+    assert(_verifyTypes(const [NumType.i64, NumType.i64], const [NumType.i64],
+        trace: const ['i64.rem_u']));
+    writeByte(0x82);
+  }
+
+  /// Emit an `i64.and` instruction.
+  void i64_and() {
+    assert(_verifyTypes(const [NumType.i64, NumType.i64], const [NumType.i64],
+        trace: const ['i64.and']));
+    writeByte(0x83);
+  }
+
+  /// Emit an `i64.or` instruction.
+  void i64_or() {
+    assert(_verifyTypes(const [NumType.i64, NumType.i64], const [NumType.i64],
+        trace: const ['i64.or']));
+    writeByte(0x84);
+  }
+
+  /// Emit an `i64.xor` instruction.
+  void i64_xor() {
+    assert(_verifyTypes(const [NumType.i64, NumType.i64], const [NumType.i64],
+        trace: const ['i64.xor']));
+    writeByte(0x85);
+  }
+
+  /// Emit an `i64.shl` instruction.
+  void i64_shl() {
+    assert(_verifyTypes(const [NumType.i64, NumType.i64], const [NumType.i64],
+        trace: const ['i64.shl']));
+    writeByte(0x86);
+  }
+
+  /// Emit an `i64.shr_s` instruction.
+  void i64_shr_s() {
+    assert(_verifyTypes(const [NumType.i64, NumType.i64], const [NumType.i64],
+        trace: const ['i64.shr_s']));
+    writeByte(0x87);
+  }
+
+  /// Emit an `i64.shr_u` instruction.
+  void i64_shr_u() {
+    assert(_verifyTypes(const [NumType.i64, NumType.i64], const [NumType.i64],
+        trace: const ['i64.shr_u']));
+    writeByte(0x88);
+  }
+
+  /// Emit an `i64.rotl` instruction.
+  void i64_rotl() {
+    assert(_verifyTypes(const [NumType.i64, NumType.i64], const [NumType.i64],
+        trace: const ['i64.rotl']));
+    writeByte(0x89);
+  }
+
+  /// Emit an `i64.rotr` instruction.
+  void i64_rotr() {
+    assert(_verifyTypes(const [NumType.i64, NumType.i64], const [NumType.i64],
+        trace: const ['i64.rotr']));
+    writeByte(0x8A);
+  }
+
+  /// Emit an `f32.abs` instruction.
+  void f32_abs() {
+    assert(_verifyTypes(const [NumType.f32], const [NumType.f32],
+        trace: const ['f32.abs']));
+    writeByte(0x8B);
+  }
+
+  /// Emit an `f32.neg` instruction.
+  void f32_neg() {
+    assert(_verifyTypes(const [NumType.f32], const [NumType.f32],
+        trace: const ['f32.neg']));
+    writeByte(0x8C);
+  }
+
+  /// Emit an `f32.ceil` instruction.
+  void f32_ceil() {
+    assert(_verifyTypes(const [NumType.f32], const [NumType.f32],
+        trace: const ['f32.ceil']));
+    writeByte(0x8D);
+  }
+
+  /// Emit an `f32.floor` instruction.
+  void f32_floor() {
+    assert(_verifyTypes(const [NumType.f32], const [NumType.f32],
+        trace: const ['f32.floor']));
+    writeByte(0x8E);
+  }
+
+  /// Emit an `f32.trunc` instruction.
+  void f32_trunc() {
+    assert(_verifyTypes(const [NumType.f32], const [NumType.f32],
+        trace: const ['f32.trunc']));
+    writeByte(0x8F);
+  }
+
+  /// Emit an `f32.nearest` instruction.
+  void f32_nearest() {
+    assert(_verifyTypes(const [NumType.f32], const [NumType.f32],
+        trace: const ['f32.nearest']));
+    writeByte(0x90);
+  }
+
+  /// Emit an `f32.sqrt` instruction.
+  void f32_sqrt() {
+    assert(_verifyTypes(const [NumType.f32], const [NumType.f32],
+        trace: const ['f32.sqrt']));
+    writeByte(0x91);
+  }
+
+  /// Emit an `f32.add` instruction.
+  void f32_add() {
+    assert(_verifyTypes(const [NumType.f32, NumType.f32], const [NumType.f32],
+        trace: const ['f32.add']));
+    writeByte(0x92);
+  }
+
+  /// Emit an `f32.sub` instruction.
+  void f32_sub() {
+    assert(_verifyTypes(const [NumType.f32, NumType.f32], const [NumType.f32],
+        trace: const ['f32.sub']));
+    writeByte(0x93);
+  }
+
+  /// Emit an `f32.mul` instruction.
+  void f32_mul() {
+    assert(_verifyTypes(const [NumType.f32, NumType.f32], const [NumType.f32],
+        trace: const ['f32.mul']));
+    writeByte(0x94);
+  }
+
+  /// Emit an `f32.div` instruction.
+  void f32_div() {
+    assert(_verifyTypes(const [NumType.f32, NumType.f32], const [NumType.f32],
+        trace: const ['f32.div']));
+    writeByte(0x95);
+  }
+
+  /// Emit an `f32.min` instruction.
+  void f32_min() {
+    assert(_verifyTypes(const [NumType.f32, NumType.f32], const [NumType.f32],
+        trace: const ['f32.min']));
+    writeByte(0x96);
+  }
+
+  /// Emit an `f32.max` instruction.
+  void f32_max() {
+    assert(_verifyTypes(const [NumType.f32, NumType.f32], const [NumType.f32],
+        trace: const ['f32.max']));
+    writeByte(0x97);
+  }
+
+  /// Emit an `f32.copysign` instruction.
+  void f32_copysign() {
+    assert(_verifyTypes(const [NumType.f32, NumType.f32], const [NumType.f32],
+        trace: const ['f32.copysign']));
+    writeByte(0x98);
+  }
+
+  /// Emit an `f64.abs` instruction.
+  void f64_abs() {
+    assert(_verifyTypes(const [NumType.f64], const [NumType.f64],
+        trace: const ['f64.abs']));
+    writeByte(0x99);
+  }
+
+  /// Emit an `f64.neg` instruction.
+  void f64_neg() {
+    assert(_verifyTypes(const [NumType.f64], const [NumType.f64],
+        trace: const ['f64.neg']));
+    writeByte(0x9A);
+  }
+
+  /// Emit an `f64.ceil` instruction.
+  void f64_ceil() {
+    assert(_verifyTypes(const [NumType.f64], const [NumType.f64],
+        trace: const ['f64.ceil']));
+    writeByte(0x9B);
+  }
+
+  /// Emit an `f64.floor` instruction.
+  void f64_floor() {
+    assert(_verifyTypes(const [NumType.f64], const [NumType.f64],
+        trace: const ['f64.floor']));
+    writeByte(0x9C);
+  }
+
+  /// Emit an `f64.trunc` instruction.
+  void f64_trunc() {
+    assert(_verifyTypes(const [NumType.f64], const [NumType.f64],
+        trace: const ['f64.trunc']));
+    writeByte(0x9D);
+  }
+
+  /// Emit an `f64.nearest` instruction.
+  void f64_nearest() {
+    assert(_verifyTypes(const [NumType.f64], const [NumType.f64],
+        trace: const ['f64.nearest']));
+    writeByte(0x9E);
+  }
+
+  /// Emit an `f64.sqrt` instruction.
+  void f64_sqrt() {
+    assert(_verifyTypes(const [NumType.f64], const [NumType.f64],
+        trace: const ['f64.sqrt']));
+    writeByte(0x9F);
+  }
+
+  /// Emit an `f64.add` instruction.
+  void f64_add() {
+    assert(_verifyTypes(const [NumType.f64, NumType.f64], const [NumType.f64],
+        trace: const ['f64.add']));
+    writeByte(0xA0);
+  }
+
+  /// Emit an `f64.sub` instruction.
+  void f64_sub() {
+    assert(_verifyTypes(const [NumType.f64, NumType.f64], const [NumType.f64],
+        trace: const ['f64.sub']));
+    writeByte(0xA1);
+  }
+
+  /// Emit an `f64.mul` instruction.
+  void f64_mul() {
+    assert(_verifyTypes(const [NumType.f64, NumType.f64], const [NumType.f64],
+        trace: const ['f64.mul']));
+    writeByte(0xA2);
+  }
+
+  /// Emit an `f64.div` instruction.
+  void f64_div() {
+    assert(_verifyTypes(const [NumType.f64, NumType.f64], const [NumType.f64],
+        trace: const ['f64.div']));
+    writeByte(0xA3);
+  }
+
+  /// Emit an `f64.min` instruction.
+  void f64_min() {
+    assert(_verifyTypes(const [NumType.f64, NumType.f64], const [NumType.f64],
+        trace: const ['f64.min']));
+    writeByte(0xA4);
+  }
+
+  /// Emit an `f64.max` instruction.
+  void f64_max() {
+    assert(_verifyTypes(const [NumType.f64, NumType.f64], const [NumType.f64],
+        trace: const ['f64.max']));
+    writeByte(0xA5);
+  }
+
+  /// Emit an `f64.copysign` instruction.
+  void f64_copysign() {
+    assert(_verifyTypes(const [NumType.f64, NumType.f64], const [NumType.f64],
+        trace: const ['f64.copysign']));
+    writeByte(0xA6);
+  }
+
+  /// Emit an `i32.wrap_i64` instruction.
+  void i32_wrap_i64() {
+    assert(_verifyTypes(const [NumType.i64], const [NumType.i32],
+        trace: const ['i32.wrap_i64']));
+    writeByte(0xA7);
+  }
+
+  /// Emit an `i32.trunc_f32_s` instruction.
+  void i32_trunc_f32_s() {
+    assert(_verifyTypes(const [NumType.f32], const [NumType.i32],
+        trace: const ['i32.trunc_f32_s']));
+    writeByte(0xA8);
+  }
+
+  /// Emit an `i32.trunc_f32_u` instruction.
+  void i32_trunc_f32_u() {
+    assert(_verifyTypes(const [NumType.f32], const [NumType.i32],
+        trace: const ['i32.trunc_f32_u']));
+    writeByte(0xA9);
+  }
+
+  /// Emit an `i32.trunc_f64_s` instruction.
+  void i32_trunc_f64_s() {
+    assert(_verifyTypes(const [NumType.f64], const [NumType.i32],
+        trace: const ['i32.trunc_f64_s']));
+    writeByte(0xAA);
+  }
+
+  /// Emit an `i32.trunc_f64_u` instruction.
+  void i32_trunc_f64_u() {
+    assert(_verifyTypes(const [NumType.f64], const [NumType.i32],
+        trace: const ['i32.trunc_f64_u']));
+    writeByte(0xAB);
+  }
+
+  /// Emit an `i64.extend_i32_s` instruction.
+  void i64_extend_i32_s() {
+    assert(_verifyTypes(const [NumType.i32], const [NumType.i64],
+        trace: const ['i64.extend_i32_s']));
+    writeByte(0xAC);
+  }
+
+  /// Emit an `i64.extend_i32_u` instruction.
+  void i64_extend_i32_u() {
+    assert(_verifyTypes(const [NumType.i32], const [NumType.i64],
+        trace: const ['i64.extend_i32_u']));
+    writeByte(0xAD);
+  }
+
+  /// Emit an `i64.trunc_f32_s` instruction.
+  void i64_trunc_f32_s() {
+    assert(_verifyTypes(const [NumType.f32], const [NumType.i64],
+        trace: const ['i64.trunc_f32_s']));
+    writeByte(0xAE);
+  }
+
+  /// Emit an `i64.trunc_f32_u` instruction.
+  void i64_trunc_f32_u() {
+    assert(_verifyTypes(const [NumType.f32], const [NumType.i64],
+        trace: const ['i64.trunc_f32_u']));
+    writeByte(0xAF);
+  }
+
+  /// Emit an `i64.trunc_f64_s` instruction.
+  void i64_trunc_f64_s() {
+    assert(_verifyTypes(const [NumType.f64], const [NumType.i64],
+        trace: const ['i64.trunc_f64_s']));
+    writeByte(0xB0);
+  }
+
+  /// Emit an `i64.trunc_f64_u` instruction.
+  void i64_trunc_f64_u() {
+    assert(_verifyTypes(const [NumType.f64], const [NumType.i64],
+        trace: const ['i64.trunc_f64_u']));
+    writeByte(0xB1);
+  }
+
+  /// Emit an `f32.convert_i32_s` instruction.
+  void f32_convert_i32_s() {
+    assert(_verifyTypes(const [NumType.i32], const [NumType.f32],
+        trace: const ['f32.convert_i32_s']));
+    writeByte(0xB2);
+  }
+
+  /// Emit an `f32.convert_i32_u` instruction.
+  void f32_convert_i32_u() {
+    assert(_verifyTypes(const [NumType.i32], const [NumType.f32],
+        trace: const ['f32.convert_i32_u']));
+    writeByte(0xB3);
+  }
+
+  /// Emit an `f32.convert_i64_s` instruction.
+  void f32_convert_i64_s() {
+    assert(_verifyTypes(const [NumType.i64], const [NumType.f32],
+        trace: const ['f32.convert_i64_s']));
+    writeByte(0xB4);
+  }
+
+  /// Emit an `f32.convert_i64_u` instruction.
+  void f32_convert_i64_u() {
+    assert(_verifyTypes(const [NumType.i64], const [NumType.f32],
+        trace: const ['f32.convert_i64_u']));
+    writeByte(0xB5);
+  }
+
+  /// Emit an `f32.demote_f64` instruction.
+  void f32_demote_f64() {
+    assert(_verifyTypes(const [NumType.f64], const [NumType.f32],
+        trace: const ['f32.demote_f64']));
+    writeByte(0xB6);
+  }
+
+  /// Emit an `f64.convert_i32_s` instruction.
+  void f64_convert_i32_s() {
+    assert(_verifyTypes(const [NumType.i32], const [NumType.f64],
+        trace: const ['f64.convert_i32_s']));
+    writeByte(0xB7);
+  }
+
+  /// Emit an `f64.convert_i32_u` instruction.
+  void f64_convert_i32_u() {
+    assert(_verifyTypes(const [NumType.i32], const [NumType.f64],
+        trace: const ['f64.convert_i32_u']));
+    writeByte(0xB8);
+  }
+
+  /// Emit an `f64.convert_i64_s` instruction.
+  void f64_convert_i64_s() {
+    assert(_verifyTypes(const [NumType.i64], const [NumType.f64],
+        trace: const ['f64.convert_i64_s']));
+    writeByte(0xB9);
+  }
+
+  /// Emit an `f64.convert_i64_u` instruction.
+  void f64_convert_i64_u() {
+    assert(_verifyTypes(const [NumType.i64], const [NumType.f64],
+        trace: const ['f64.convert_i64_u']));
+    writeByte(0xBA);
+  }
+
+  /// Emit an `f64.promote_f32` instruction.
+  void f64_promote_f32() {
+    assert(_verifyTypes(const [NumType.f32], const [NumType.f64],
+        trace: const ['f64.promote_f32']));
+    writeByte(0xBB);
+  }
+
+  /// Emit an `i32.reinterpret_f32` instruction.
+  void i32_reinterpret_f32() {
+    assert(_verifyTypes(const [NumType.f32], const [NumType.i32],
+        trace: const ['i32.reinterpret_f32']));
+    writeByte(0xBC);
+  }
+
+  /// Emit an `i64.reinterpret_f64` instruction.
+  void i64_reinterpret_f64() {
+    assert(_verifyTypes(const [NumType.f64], const [NumType.i64],
+        trace: const ['i64.reinterpret_f64']));
+    writeByte(0xBD);
+  }
+
+  /// Emit an `f32.reinterpret_i32` instruction.
+  void f32_reinterpret_i32() {
+    assert(_verifyTypes(const [NumType.i32], const [NumType.f32],
+        trace: const ['f32.reinterpret_i32']));
+    writeByte(0xBE);
+  }
+
+  /// Emit an `f64.reinterpret_i64` instruction.
+  void f64_reinterpret_i64() {
+    assert(_verifyTypes(const [NumType.i64], const [NumType.f64],
+        trace: const ['f64.reinterpret_i64']));
+    writeByte(0xBF);
+  }
+
+  /// Emit an `i32.extend8_s` instruction.
+  void i32_extend8_s() {
+    assert(_verifyTypes(const [NumType.i32], const [NumType.i32],
+        trace: const ['i32.extend8_s']));
+    writeByte(0xC0);
+  }
+
+  /// Emit an `i32.extend16_s` instruction.
+  void i32_extend16_s() {
+    assert(_verifyTypes(const [NumType.i32], const [NumType.i32],
+        trace: const ['i32.extend16_s']));
+    writeByte(0xC1);
+  }
+
+  /// Emit an `i64.extend8_s` instruction.
+  void i64_extend8_s() {
+    assert(_verifyTypes(const [NumType.i64], const [NumType.i64],
+        trace: const ['i64.extend8_s']));
+    writeByte(0xC2);
+  }
+
+  /// Emit an `i64.extend16_s` instruction.
+  void i64_extend16_s() {
+    assert(_verifyTypes(const [NumType.i64], const [NumType.i64],
+        trace: const ['i64.extend16_s']));
+    writeByte(0xC3);
+  }
+
+  /// Emit an `i64.extend32_s` instruction.
+  void i64_extend32_s() {
+    assert(_verifyTypes(const [NumType.i64], const [NumType.i64],
+        trace: const ['i64.extend32_s']));
+    writeByte(0xC4);
+  }
+
+  /// Emit an `i32.trunc_sat_f32_s` instruction.
+  void i32_trunc_sat_f32_s() {
+    assert(_verifyTypes(const [NumType.f32], const [NumType.i32],
+        trace: const ['i32.trunc_sat_f32_s']));
+    writeBytes(const [0xFC, 0x00]);
+  }
+
+  /// Emit an `i32.trunc_sat_f32_u` instruction.
+  void i32_trunc_sat_f32_u() {
+    assert(_verifyTypes(const [NumType.f32], const [NumType.i32],
+        trace: const ['i32.trunc_sat_f32_u']));
+    writeBytes(const [0xFC, 0x01]);
+  }
+
+  /// Emit an `i32.trunc_sat_f64_s` instruction.
+  void i32_trunc_sat_f64_s() {
+    assert(_verifyTypes(const [NumType.f64], const [NumType.i32],
+        trace: const ['i32.trunc_sat_f64_s']));
+    writeBytes(const [0xFC, 0x02]);
+  }
+
+  /// Emit an `i32.trunc_sat_f64_u` instruction.
+  void i32_trunc_sat_f64_u() {
+    assert(_verifyTypes(const [NumType.f64], const [NumType.i32],
+        trace: const ['i32.trunc_sat_f64_u']));
+    writeBytes(const [0xFC, 0x03]);
+  }
+
+  /// Emit an `i64.trunc_sat_f32_s` instruction.
+  void i64_trunc_sat_f32_s() {
+    assert(_verifyTypes(const [NumType.f32], const [NumType.i64],
+        trace: const ['i64.trunc_sat_f32_s']));
+    writeBytes(const [0xFC, 0x04]);
+  }
+
+  /// Emit an `i64.trunc_sat_f32_u` instruction.
+  void i64_trunc_sat_f32_u() {
+    assert(_verifyTypes(const [NumType.f32], const [NumType.i64],
+        trace: const ['i64.trunc_sat_f32_u']));
+    writeBytes(const [0xFC, 0x05]);
+  }
+
+  /// Emit an `i64.trunc_sat_f64_s` instruction.
+  void i64_trunc_sat_f64_s() {
+    assert(_verifyTypes(const [NumType.f64], const [NumType.i64],
+        trace: const ['i64.trunc_sat_f64_s']));
+    writeBytes(const [0xFC, 0x06]);
+  }
+
+  /// Emit an `i64.trunc_sat_f64_u` instruction.
+  void i64_trunc_sat_f64_u() {
+    assert(_verifyTypes(const [NumType.f64], const [NumType.i64],
+        trace: const ['i64.trunc_sat_f64_u']));
+    writeBytes(const [0xFC, 0x07]);
+  }
+}
diff --git a/pkg/wasm_builder/lib/src/module.dart b/pkg/wasm_builder/lib/src/module.dart
new file mode 100644
index 0000000..95ad8df
--- /dev/null
+++ b/pkg/wasm_builder/lib/src/module.dart
@@ -0,0 +1,820 @@
+// 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.
+
+import 'dart:typed_data';
+
+import 'instructions.dart';
+import 'serialize.dart';
+import 'types.dart';
+
+/// A Wasm module.
+///
+/// Serves as a builder for building new modules.
+class Module with SerializerMixin {
+  final List<int>? watchPoints;
+
+  final Map<_FunctionTypeKey, FunctionType> functionTypeMap = {};
+
+  final List<DefType> defTypes = [];
+  final List<BaseFunction> functions = [];
+  final List<Table> tables = [];
+  final List<Memory> memories = [];
+  final List<DataSegment> dataSegments = [];
+  final List<Global> globals = [];
+  final List<Export> exports = [];
+  BaseFunction? startFunction = null;
+
+  bool anyFunctionsDefined = false;
+  bool anyGlobalsDefined = false;
+  bool dataReferencedFromGlobalInitializer = false;
+
+  int functionNameCount = 0;
+
+  /// Create a new, initially empty, module.
+  ///
+  /// The [watchPoints] is a list of byte offsets within the final module of
+  /// bytes to watch. When the module is serialized, the stack traces leading to
+  /// the production of all watched bytes are printed. This can be used to debug
+  /// runtime errors happening at specific offsets within the module.
+  Module({this.watchPoints}) {
+    if (watchPoints != null) {
+      SerializerMixin.traceEnabled = true;
+    }
+  }
+
+  /// All module imports (functions and globals).
+  Iterable<Import> get imports =>
+      functions.whereType<Import>().followedBy(globals.whereType<Import>());
+
+  /// All functions defined in the module.
+  Iterable<DefinedFunction> get definedFunctions =>
+      functions.whereType<DefinedFunction>();
+
+  /// Add a new function type to the module.
+  ///
+  /// All function types are canonicalized, such that identical types become
+  /// the same type definition in the module, assuming nominal type identity
+  /// of all inputs and outputs.
+  ///
+  /// Inputs and outputs can't be changed after the function type is created.
+  /// This means that recursive function types (without any non-function types
+  /// on the recursion path) are not supported.
+  FunctionType addFunctionType(
+      Iterable<ValueType> inputs, Iterable<ValueType> outputs,
+      {HeapType? superType}) {
+    final List<ValueType> inputList = List.unmodifiable(inputs);
+    final List<ValueType> outputList = List.unmodifiable(outputs);
+    final _FunctionTypeKey key = _FunctionTypeKey(inputList, outputList);
+    return functionTypeMap.putIfAbsent(key, () {
+      final type = FunctionType(inputList, outputList, superType: superType)
+        ..index = defTypes.length;
+      defTypes.add(type);
+      return type;
+    });
+  }
+
+  /// Add a new struct type to the module.
+  ///
+  /// Fields can be added later, by adding to the [fields] list. This enables
+  /// struct types to be recursive.
+  StructType addStructType(String name,
+      {Iterable<FieldType>? fields, HeapType? superType}) {
+    final type = StructType(name, fields: fields, superType: superType)
+      ..index = defTypes.length;
+    defTypes.add(type);
+    return type;
+  }
+
+  /// Add a new array type to the module.
+  ///
+  /// The element type can be specified later. This enables array types to be
+  /// recursive.
+  ArrayType addArrayType(String name,
+      {FieldType? elementType, HeapType? superType}) {
+    final type = ArrayType(name, elementType: elementType, superType: superType)
+      ..index = defTypes.length;
+    defTypes.add(type);
+    return type;
+  }
+
+  /// Add a new function to the module with the given function type.
+  ///
+  /// The [DefinedFunction.body] must be completed (including the terminating
+  /// `end`) before the module can be serialized.
+  DefinedFunction addFunction(FunctionType type, [String? name]) {
+    anyFunctionsDefined = true;
+    if (name != null) functionNameCount++;
+    final function = DefinedFunction(this, functions.length, type, name);
+    functions.add(function);
+    return function;
+  }
+
+  /// Add a new table to the module.
+  Table addTable(int minSize, [int? maxSize]) {
+    final table = Table(tables.length, minSize, maxSize);
+    tables.add(table);
+    return table;
+  }
+
+  /// Add a new memory to the module.
+  Memory addMemory(int minSize, [int? maxSize]) {
+    final memory = Memory(memories.length, minSize, maxSize);
+    memories.add(memory);
+    return memory;
+  }
+
+  /// Add a new data segment to the module.
+  ///
+  /// Either [memory] and [offset] must be both specified or both omitted. If
+  /// they are specified, the segment becomes an *active* segment, otherwise it
+  /// becomes a *passive* segment.
+  ///
+  /// If [initialContent] is specified, it defines the initial content of the
+  /// segment. The content can be extended later.
+  DataSegment addDataSegment(
+      [Uint8List? initialContent, Memory? memory, int? offset]) {
+    initialContent ??= Uint8List(0);
+    assert((memory != null) == (offset != null));
+    assert(memory == null ||
+        offset! >= 0 && offset + initialContent.length <= memory.minSize);
+    final DataSegment data =
+        DataSegment(dataSegments.length, initialContent, memory, offset);
+    dataSegments.add(data);
+    return data;
+  }
+
+  /// Add a global variable to the module.
+  ///
+  /// The [DefinedGlobal.initializer] must be completed (including the
+  /// terminating `end`) before the module can be serialized.
+  DefinedGlobal addGlobal(GlobalType type) {
+    anyGlobalsDefined = true;
+    final global = DefinedGlobal(this, globals.length, type);
+    globals.add(global);
+    return global;
+  }
+
+  /// Import a function into the module.
+  ///
+  /// All imported functions must be specified before any functions are declared
+  /// using [Module.addFunction].
+  ImportedFunction importFunction(String module, String name, FunctionType type,
+      [String? functionName]) {
+    if (anyFunctionsDefined) {
+      throw "All function imports must be specified before any definitions.";
+    }
+    if (functionName != null) functionNameCount++;
+    final function =
+        ImportedFunction(module, name, functions.length, type, functionName);
+    functions.add(function);
+    return function;
+  }
+
+  /// Import a global variable into the module.
+  ///
+  /// All imported globals must be specified before any globals are declared
+  /// using [Module.addGlobal].
+  ImportedGlobal importGlobal(String module, String name, GlobalType type) {
+    if (anyGlobalsDefined) {
+      throw "All global imports must be specified before any definitions.";
+    }
+    final global = ImportedGlobal(module, name, functions.length, type);
+    globals.add(global);
+    return global;
+  }
+
+  void _addExport(Export export) {
+    assert(!exports.any((e) => e.name == export.name), export.name);
+    exports.add(export);
+  }
+
+  /// Export a function from the module.
+  ///
+  /// All exports must have unique names.
+  void exportFunction(String name, BaseFunction function) {
+    function.exportedName = name;
+    _addExport(FunctionExport(name, function));
+  }
+
+  /// Export a global variable from the module.
+  ///
+  /// All exports must have unique names.
+  void exportGlobal(String name, Global global) {
+    exports.add(GlobalExport(name, global));
+  }
+
+  /// Serialize the module to its binary representation.
+  Uint8List encode({bool emitNameSection: true}) {
+    // Wasm module preamble: magic number, version 1.
+    writeBytes(const [0x00, 0x61, 0x73, 0x6D, 0x01, 0x00, 0x00, 0x00]);
+    TypeSection(this).serialize(this);
+    ImportSection(this).serialize(this);
+    FunctionSection(this).serialize(this);
+    TableSection(this).serialize(this);
+    MemorySection(this).serialize(this);
+    if (dataReferencedFromGlobalInitializer) {
+      DataCountSection(this).serialize(this);
+    }
+    GlobalSection(this).serialize(this);
+    ExportSection(this).serialize(this);
+    StartSection(this).serialize(this);
+    ElementSection(this).serialize(this);
+    if (!dataReferencedFromGlobalInitializer) {
+      DataCountSection(this).serialize(this);
+    }
+    CodeSection(this).serialize(this);
+    DataSection(this).serialize(this);
+    if (emitNameSection) {
+      NameSection(this).serialize(this);
+    }
+    return data;
+  }
+}
+
+class _FunctionTypeKey {
+  final List<ValueType> inputs;
+  final List<ValueType> outputs;
+
+  _FunctionTypeKey(this.inputs, this.outputs);
+
+  @override
+  bool operator ==(Object other) {
+    if (other is! _FunctionTypeKey) return false;
+    if (inputs.length != other.inputs.length) return false;
+    if (outputs.length != other.outputs.length) return false;
+    for (int i = 0; i < inputs.length; i++) {
+      if (inputs[i] != other.inputs[i]) return false;
+    }
+    for (int i = 0; i < outputs.length; i++) {
+      if (outputs[i] != other.outputs[i]) return false;
+    }
+    return true;
+  }
+
+  @override
+  int get hashCode {
+    int inputHash = 13;
+    for (var input in inputs) {
+      inputHash = inputHash * 17 + input.hashCode;
+    }
+    int outputHash = 23;
+    for (var output in outputs) {
+      outputHash = outputHash * 29 + output.hashCode;
+    }
+    return (inputHash * 2 + 1) * (outputHash * 2 + 1);
+  }
+}
+
+/// An (imported or defined) Wasm function.
+abstract class BaseFunction {
+  final int index;
+  final FunctionType type;
+  final String? functionName;
+  String? exportedName;
+
+  BaseFunction(this.index, this.type, this.functionName);
+}
+
+/// A function defined in the module.
+class DefinedFunction extends BaseFunction
+    with SerializerMixin
+    implements Serializable {
+  /// All local variables defined in the function, including its inputs.
+  final List<Local> locals = [];
+
+  /// The body of the function.
+  late final Instructions body;
+
+  DefinedFunction(Module module, int index, FunctionType type,
+      [String? functionName])
+      : super(index, type, functionName) {
+    for (ValueType paramType in type.inputs) {
+      addLocal(paramType);
+    }
+    body = Instructions(module, type.outputs, locals: locals);
+  }
+
+  /// Add a local variable to the function.
+  Local addLocal(ValueType type) {
+    Local local = Local(locals.length, type);
+    locals.add(local);
+    return local;
+  }
+
+  @override
+  void serialize(Serializer s) {
+    // Serialize locals internally first in order to compute the total size of
+    // the serialized data.
+    int paramCount = type.inputs.length;
+    int entries = 0;
+    for (int i = paramCount + 1; i <= locals.length; i++) {
+      if (i == locals.length || locals[i - 1].type != locals[i].type) entries++;
+    }
+    writeUnsigned(entries);
+    int start = paramCount;
+    for (int i = paramCount + 1; i <= locals.length; i++) {
+      if (i == locals.length || locals[i - 1].type != locals[i].type) {
+        writeUnsigned(i - start);
+        write(locals[i - 1].type);
+        start = i;
+      }
+    }
+
+    // Bundle locals and body
+    assert(body.isComplete);
+    s.writeUnsigned(data.length + body.data.length);
+    s.writeData(this);
+    s.writeData(body);
+  }
+
+  @override
+  String toString() => exportedName ?? "#$index";
+}
+
+/// A local variable defined in a function.
+class Local {
+  final int index;
+  final ValueType type;
+
+  Local(this.index, this.type);
+
+  @override
+  String toString() => "$index";
+}
+
+/// A table in a module.
+class Table implements Serializable {
+  final int index;
+  final int minSize;
+  final int? maxSize;
+  final List<BaseFunction?> elements;
+
+  Table(this.index, this.minSize, this.maxSize)
+      : elements = List.filled(minSize, null);
+
+  void setElement(int index, BaseFunction function) {
+    elements[index] = function;
+  }
+
+  @override
+  void serialize(Serializer s) {
+    s.writeByte(0x70); // funcref
+    if (maxSize == null) {
+      s.writeByte(0x00);
+      s.writeUnsigned(minSize);
+    } else {
+      s.writeByte(0x01);
+      s.writeUnsigned(minSize);
+      s.writeUnsigned(maxSize!);
+    }
+  }
+}
+
+/// A memory in a module.
+class Memory implements Serializable {
+  final int index;
+  final int minSize;
+  final int? maxSize;
+
+  Memory(this.index, this.minSize, [this.maxSize]);
+
+  @override
+  void serialize(Serializer s) {
+    if (maxSize == null) {
+      s.writeByte(0x00);
+      s.writeUnsigned(minSize);
+    } else {
+      s.writeByte(0x01);
+      s.writeUnsigned(minSize);
+      s.writeUnsigned(maxSize!);
+    }
+  }
+}
+
+/// A data segment in a module.
+class DataSegment implements Serializable {
+  final int index;
+  final BytesBuilder content;
+  final Memory? memory;
+  final int? offset;
+
+  DataSegment(this.index, Uint8List initialContent, this.memory, this.offset)
+      : content = BytesBuilder()..add(initialContent);
+
+  bool get isActive => memory != null;
+  bool get isPassive => memory == null;
+
+  int get length => content.length;
+
+  /// Append content to the data segment.
+  void append(Uint8List data) {
+    content.add(data);
+    assert(isPassive ||
+        offset! >= 0 && offset! + content.length <= memory!.minSize);
+  }
+
+  @override
+  void serialize(Serializer s) {
+    if (memory != null) {
+      // Active segment
+      if (memory!.index == 0) {
+        s.writeByte(0x00);
+      } else {
+        s.writeByte(0x02);
+        s.writeUnsigned(memory!.index);
+      }
+      s.writeByte(0x41); // i32.const
+      s.writeSigned(offset!);
+      s.writeByte(0x0B); // end
+    } else {
+      // Passive segment
+      s.writeByte(0x01);
+    }
+    s.writeUnsigned(content.length);
+    s.writeBytes(content.toBytes());
+  }
+}
+
+/// An (imported or defined) global variable in a module.
+abstract class Global {
+  final int index;
+  final GlobalType type;
+
+  Global(this.index, this.type);
+
+  @override
+  String toString() => "$index";
+}
+
+/// A global variable defined in the module.
+class DefinedGlobal extends Global implements Serializable {
+  final Instructions initializer;
+
+  DefinedGlobal(Module module, int index, GlobalType type)
+      : initializer =
+            Instructions(module, [type.type], isGlobalInitializer: true),
+        super(index, type);
+
+  @override
+  void serialize(Serializer s) {
+    assert(initializer.isComplete);
+    s.write(type);
+    s.writeData(initializer);
+  }
+}
+
+/// Any import (function or global).
+abstract class Import implements Serializable {
+  String get module;
+  String get name;
+}
+
+/// An imported function.
+class ImportedFunction extends BaseFunction implements Import {
+  final String module;
+  final String name;
+
+  ImportedFunction(this.module, this.name, int index, FunctionType type,
+      [String? functionName])
+      : super(index, type, functionName);
+
+  @override
+  void serialize(Serializer s) {
+    s.writeName(module);
+    s.writeName(name);
+    s.writeByte(0x00);
+    s.writeUnsigned(type.index);
+  }
+
+  @override
+  String toString() => "$module.$name";
+}
+
+/// An imported global variable.
+class ImportedGlobal extends Global implements Import {
+  final String module;
+  final String name;
+
+  ImportedGlobal(this.module, this.name, int index, GlobalType type)
+      : super(index, type);
+
+  @override
+  void serialize(Serializer s) {
+    s.writeName(module);
+    s.writeName(name);
+    s.writeByte(0x03);
+    s.write(type);
+  }
+}
+
+abstract class Export implements Serializable {
+  final String name;
+
+  Export(this.name);
+}
+
+class FunctionExport extends Export {
+  final BaseFunction function;
+
+  FunctionExport(String name, this.function) : super(name);
+
+  @override
+  void serialize(Serializer s) {
+    s.writeName(name);
+    s.writeByte(0x00);
+    s.writeUnsigned(function.index);
+  }
+}
+
+class GlobalExport extends Export {
+  final Global global;
+
+  GlobalExport(String name, this.global) : super(name);
+
+  @override
+  void serialize(Serializer s) {
+    s.writeName(name);
+    s.writeByte(0x03);
+    s.writeUnsigned(global.index);
+  }
+}
+
+abstract class Section with SerializerMixin implements Serializable {
+  final Module module;
+
+  Section(this.module);
+
+  void serialize(Serializer s) {
+    if (isNotEmpty) {
+      serializeContents();
+      s.writeByte(id);
+      s.writeUnsigned(data.length);
+      s.writeData(this, module.watchPoints);
+    }
+  }
+
+  int get id;
+
+  bool get isNotEmpty;
+
+  void serializeContents();
+}
+
+class TypeSection extends Section {
+  TypeSection(Module module) : super(module);
+
+  @override
+  int get id => 1;
+
+  @override
+  bool get isNotEmpty => module.defTypes.isNotEmpty;
+
+  @override
+  void serializeContents() {
+    writeUnsigned(module.defTypes.length);
+    for (DefType defType in module.defTypes) {
+      defType.serializeDefinition(this);
+    }
+  }
+}
+
+class ImportSection extends Section {
+  ImportSection(Module module) : super(module);
+
+  @override
+  int get id => 2;
+
+  @override
+  bool get isNotEmpty => module.imports.isNotEmpty;
+
+  @override
+  void serializeContents() {
+    writeList(module.imports.toList());
+  }
+}
+
+class FunctionSection extends Section {
+  FunctionSection(Module module) : super(module);
+
+  @override
+  int get id => 3;
+
+  @override
+  bool get isNotEmpty => module.definedFunctions.isNotEmpty;
+
+  @override
+  void serializeContents() {
+    writeUnsigned(module.definedFunctions.length);
+    for (var function in module.definedFunctions) {
+      writeUnsigned(function.type.index);
+    }
+  }
+}
+
+class TableSection extends Section {
+  TableSection(Module module) : super(module);
+
+  @override
+  int get id => 4;
+
+  @override
+  bool get isNotEmpty => module.tables.isNotEmpty;
+
+  @override
+  void serializeContents() {
+    writeList(module.tables);
+  }
+}
+
+class MemorySection extends Section {
+  MemorySection(Module module) : super(module);
+
+  @override
+  int get id => 5;
+
+  @override
+  bool get isNotEmpty => module.memories.isNotEmpty;
+
+  @override
+  void serializeContents() {
+    writeList(module.memories);
+  }
+}
+
+class GlobalSection extends Section {
+  GlobalSection(Module module) : super(module);
+
+  @override
+  int get id => 6;
+
+  @override
+  bool get isNotEmpty => module.globals.whereType<DefinedGlobal>().isNotEmpty;
+
+  @override
+  void serializeContents() {
+    writeList(module.globals.whereType<DefinedGlobal>().toList());
+  }
+}
+
+class ExportSection extends Section {
+  ExportSection(Module module) : super(module);
+
+  @override
+  int get id => 7;
+
+  @override
+  bool get isNotEmpty => module.exports.isNotEmpty;
+
+  @override
+  void serializeContents() {
+    writeList(module.exports);
+  }
+}
+
+class StartSection extends Section {
+  StartSection(Module module) : super(module);
+
+  @override
+  int get id => 8;
+
+  @override
+  bool get isNotEmpty => module.startFunction != null;
+
+  @override
+  void serializeContents() {
+    writeUnsigned(module.startFunction!.index);
+  }
+}
+
+class _Element implements Serializable {
+  final Table table;
+  final int startIndex;
+  final List<BaseFunction> entries = [];
+
+  _Element(this.table, this.startIndex);
+
+  @override
+  void serialize(Serializer s) {
+    s.writeUnsigned(table.index);
+    s.writeByte(0x41); // i32.const
+    s.writeSigned(startIndex);
+    s.writeByte(0x0B); // end
+    s.writeUnsigned(entries.length);
+    for (var entry in entries) {
+      s.writeUnsigned(entry.index);
+    }
+  }
+}
+
+class ElementSection extends Section {
+  ElementSection(Module module) : super(module);
+
+  @override
+  int get id => 9;
+
+  @override
+  bool get isNotEmpty =>
+      module.tables.any((table) => table.elements.any((e) => e != null));
+
+  @override
+  void serializeContents() {
+    // Group nonempty element entries into contiguous stretches and serialize
+    // each stretch as an element.
+    List<_Element> elements = [];
+    for (Table table in module.tables) {
+      _Element? current = null;
+      for (int i = 0; i < table.elements.length; i++) {
+        BaseFunction? function = table.elements[i];
+        if (function != null) {
+          if (current == null) {
+            current = _Element(table, i);
+            elements.add(current);
+          }
+          current.entries.add(function);
+        } else {
+          current = null;
+        }
+      }
+    }
+    writeList(elements);
+  }
+}
+
+class DataCountSection extends Section {
+  DataCountSection(Module module) : super(module);
+
+  @override
+  int get id => 12;
+
+  @override
+  bool get isNotEmpty => module.dataSegments.isNotEmpty;
+
+  @override
+  void serializeContents() {
+    writeUnsigned(module.dataSegments.length);
+  }
+}
+
+class CodeSection extends Section {
+  CodeSection(Module module) : super(module);
+
+  @override
+  int get id => 10;
+
+  @override
+  bool get isNotEmpty => module.definedFunctions.isNotEmpty;
+
+  @override
+  void serializeContents() {
+    writeList(module.definedFunctions.toList());
+  }
+}
+
+class DataSection extends Section {
+  DataSection(Module module) : super(module);
+
+  @override
+  int get id => 11;
+
+  @override
+  bool get isNotEmpty => module.dataSegments.isNotEmpty;
+
+  @override
+  void serializeContents() {
+    writeList(module.dataSegments);
+  }
+}
+
+abstract class CustomSection extends Section {
+  CustomSection(Module module) : super(module);
+
+  @override
+  int get id => 0;
+}
+
+class NameSection extends CustomSection {
+  NameSection(Module module) : super(module);
+
+  @override
+  bool get isNotEmpty => module.functionNameCount > 0;
+
+  @override
+  void serializeContents() {
+    writeName("name");
+    var functionNameSubsection = _NameSubsection();
+    functionNameSubsection.writeUnsigned(module.functionNameCount);
+    for (int i = 0; i < module.functions.length; i++) {
+      String? functionName = module.functions[i].functionName;
+      if (functionName != null) {
+        functionNameSubsection.writeUnsigned(i);
+        functionNameSubsection.writeName(functionName);
+      }
+    }
+    writeByte(1); // Function names subsection
+    writeUnsigned(functionNameSubsection.data.length);
+    writeData(functionNameSubsection);
+  }
+}
+
+class _NameSubsection with SerializerMixin {}
diff --git a/pkg/wasm_builder/lib/src/serialize.dart b/pkg/wasm_builder/lib/src/serialize.dart
new file mode 100644
index 0000000..007b6e1
--- /dev/null
+++ b/pkg/wasm_builder/lib/src/serialize.dart
@@ -0,0 +1,137 @@
+// 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.
+
+import 'dart:collection';
+import 'dart:convert';
+import 'dart:typed_data';
+
+abstract class Serializer {
+  void writeByte(int byte);
+  void writeBytes(List<int> bytes);
+  void writeSigned(int value);
+  void writeUnsigned(int value);
+  void writeF32(double value);
+  void writeF64(double value);
+  void writeName(String name);
+  void write(Serializable object);
+  void writeList(List<Serializable> objects);
+  void writeData(Serializer chunk, [List<int>? watchPoints]);
+
+  Uint8List get data;
+}
+
+abstract class Serializable {
+  void serialize(Serializer s);
+}
+
+mixin SerializerMixin implements Serializer {
+  static bool traceEnabled = false;
+
+  // The prefix of `_data` up to `_index` contains the data serialized so far.
+  Uint8List _data = Uint8List(24);
+  int _index = 0;
+
+  // Stack traces or other serializers attached to byte positions within the
+  // chunk of data produced by this serializer.
+  late final SplayTreeMap<int, Object> _traces = SplayTreeMap();
+
+  void _ensure(int size) {
+    // Ensure space for at least `size` additional bytes.
+    if (_data.length < _index + size) {
+      int newLength = _data.length * 2;
+      while (newLength < _index + size) newLength *= 2;
+      _data = Uint8List(newLength)..setRange(0, _data.length, _data);
+    }
+  }
+
+  void _debugTrace(Object data) {
+    _traces[_index] ??= data;
+  }
+
+  void writeByte(int byte) {
+    if (traceEnabled) _debugTrace(StackTrace.current);
+    assert(byte == byte & 0xFF);
+    _ensure(1);
+    _data[_index++] = byte;
+  }
+
+  void writeBytes(List<int> bytes) {
+    if (traceEnabled) _debugTrace(StackTrace.current);
+    _ensure(bytes.length);
+    _data.setRange(_index, _index += bytes.length, bytes);
+  }
+
+  void writeSigned(int value) {
+    while (value < -0x40 || value >= 0x40) {
+      writeByte((value & 0x7F) | 0x80);
+      value >>= 7;
+    }
+    writeByte(value & 0x7F);
+  }
+
+  void writeUnsigned(int value) {
+    assert(value >= 0);
+    while (value >= 0x80) {
+      writeByte((value & 0x7F) | 0x80);
+      value >>= 7;
+    }
+    writeByte(value);
+  }
+
+  void writeF32(double value) {
+    // Get the binary representation of the F32.
+    List<int> bytes = Float32List.fromList([value]).buffer.asUint8List();
+    assert(bytes.length == 4);
+    if (Endian.host == Endian.big) bytes = bytes.reversed.toList();
+    writeBytes(bytes);
+  }
+
+  void writeF64(double value) {
+    // Get the binary representation of the F64.
+    List<int> bytes = Float64List.fromList([value]).buffer.asUint8List();
+    assert(bytes.length == 8);
+    if (Endian.host == Endian.big) bytes = bytes.reversed.toList();
+    writeBytes(bytes);
+  }
+
+  void writeName(String name) {
+    List<int> bytes = utf8.encode(name);
+    writeUnsigned(bytes.length);
+    writeBytes(bytes);
+  }
+
+  void write(Serializable object) {
+    object.serialize(this);
+  }
+
+  void writeList(List<Serializable> objects) {
+    writeUnsigned(objects.length);
+    for (int i = 0; i < objects.length; i++) write(objects[i]);
+  }
+
+  void writeData(Serializer chunk, [List<int>? watchPoints]) {
+    if (traceEnabled) _debugTrace(chunk);
+    if (watchPoints != null) {
+      for (int watchPoint in watchPoints) {
+        if (_index <= watchPoint && watchPoint < _index + chunk.data.length) {
+          int byteValue = chunk.data[watchPoint - _index];
+          Object trace = this;
+          int offset = watchPoint;
+          while (trace is SerializerMixin) {
+            int keyOffset = trace._traces.containsKey(offset)
+                ? offset
+                : trace._traces.lastKeyBefore(offset)!;
+            trace = trace._traces[keyOffset]!;
+            offset -= keyOffset;
+          }
+          String byte = byteValue.toRadixString(16).padLeft(2, '0');
+          print("Watch $watchPoint: 0x$byte\n$trace");
+        }
+      }
+    }
+    writeBytes(chunk.data);
+  }
+
+  Uint8List get data => Uint8List.sublistView(_data, 0, _index);
+}
diff --git a/pkg/wasm_builder/lib/src/types.dart b/pkg/wasm_builder/lib/src/types.dart
new file mode 100644
index 0000000..ecc8b1e
--- /dev/null
+++ b/pkg/wasm_builder/lib/src/types.dart
@@ -0,0 +1,657 @@
+// 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.
+
+import 'serialize.dart';
+
+// Representations of all Wasm types.
+
+/// A *storage type*.
+abstract class StorageType implements Serializable {
+  /// Returns whether this type is a subtype of the [other] type, i.e. whether
+  /// it can be used as input where [other] is expected.
+  bool isSubtypeOf(StorageType other);
+
+  /// The *unpacked* form of this storage type, i.e. the *value type* to use
+  /// when reading/writing this storage type from/to memory.
+  ValueType get unpacked;
+
+  /// Whether this is a primitive (i.e. not reference) type.
+  bool get isPrimitive;
+
+  /// For primitive types: the size in bytes of a value of this type.
+  int get byteSize;
+}
+
+/// A *value type*.
+abstract class ValueType implements StorageType {
+  const ValueType();
+
+  @override
+  ValueType get unpacked => this;
+
+  @override
+  bool get isPrimitive => false;
+
+  @override
+  int get byteSize => throw "Size of non-primitive type $runtimeType";
+
+  /// Whether this type is nullable. Primitive types are never nullable.
+  bool get nullable => false;
+
+  /// If this exists in both a nullable and non-nullable version, return the
+  /// version with the given nullability.
+  ValueType withNullability(bool nullable) => this;
+
+  /// Whether this type is defaultable. Primitive types are always defaultable.
+  bool get defaultable => true;
+}
+
+enum NumTypeKind { i32, i64, f32, f64, v128 }
+
+/// A *number type* or *vector type*.
+class NumType extends ValueType {
+  final NumTypeKind kind;
+
+  const NumType._(this.kind);
+
+  /// The `i32` type.
+  static const i32 = NumType._(NumTypeKind.i32);
+
+  /// The `i64` type.
+  static const i64 = NumType._(NumTypeKind.i64);
+
+  /// The `f32` type.
+  static const f32 = NumType._(NumTypeKind.f32);
+
+  /// The `f64` type.
+  static const f64 = NumType._(NumTypeKind.f64);
+
+  /// The `v128` type.
+  static const v128 = NumType._(NumTypeKind.v128);
+
+  @override
+  bool isSubtypeOf(StorageType other) => this == other;
+
+  @override
+  bool get isPrimitive => true;
+
+  @override
+  int get byteSize {
+    switch (kind) {
+      case NumTypeKind.i32:
+      case NumTypeKind.f32:
+        return 4;
+      case NumTypeKind.i64:
+      case NumTypeKind.f64:
+        return 8;
+      case NumTypeKind.v128:
+        return 16;
+    }
+  }
+
+  @override
+  void serialize(Serializer s) {
+    switch (kind) {
+      case NumTypeKind.i32:
+        s.writeByte(0x7F);
+        break;
+      case NumTypeKind.i64:
+        s.writeByte(0x7E);
+        break;
+      case NumTypeKind.f32:
+        s.writeByte(0x7D);
+        break;
+      case NumTypeKind.f64:
+        s.writeByte(0x7C);
+        break;
+      case NumTypeKind.v128:
+        s.writeByte(0x7B);
+        break;
+    }
+  }
+
+  @override
+  String toString() {
+    switch (kind) {
+      case NumTypeKind.i32:
+        return "i32";
+      case NumTypeKind.i64:
+        return "i64";
+      case NumTypeKind.f32:
+        return "f32";
+      case NumTypeKind.f64:
+        return "f64";
+      case NumTypeKind.v128:
+        return "v128";
+    }
+  }
+}
+
+/// An RTT (runtime type) type.
+class Rtt extends ValueType {
+  final DefType defType;
+  final int? depth;
+
+  const Rtt(this.defType, [this.depth]);
+
+  @override
+  bool get defaultable => false;
+
+  @override
+  bool isSubtypeOf(StorageType other) =>
+      other is Rtt &&
+      defType == other.defType &&
+      (other.depth == null || depth == other.depth);
+
+  @override
+  void serialize(Serializer s) {
+    if (depth != null) {
+      s.writeByte(0x69);
+      s.writeUnsigned(depth!);
+    } else {
+      s.writeByte(0x68);
+    }
+    s.writeSigned(defType.index);
+  }
+
+  @override
+  String toString() => depth == null ? "rtt $defType" : "rtt $depth $defType";
+
+  @override
+  bool operator ==(Object other) =>
+      other is Rtt && other.defType == defType && other.depth == depth;
+
+  @override
+  int get hashCode => defType.hashCode * (3 + (depth ?? -3) * 2);
+}
+
+/// A *reference type*.
+class RefType extends ValueType {
+  /// The *heap type* of this reference type.
+  final HeapType heapType;
+
+  /// The nullability of this reference type.
+  final bool nullable;
+
+  RefType(this.heapType, {bool? nullable})
+      : this.nullable = nullable ??
+            heapType.nullableByDefault ??
+            (throw "Unspecified nullability");
+
+  const RefType._(this.heapType, this.nullable);
+
+  /// A (possibly nullable) reference to the `any` heap type.
+  const RefType.any({bool nullable = AnyHeapType.defaultNullability})
+      : this._(HeapType.any, nullable);
+
+  /// A (possibly nullable) reference to the `eq` heap type.
+  const RefType.eq({bool nullable = EqHeapType.defaultNullability})
+      : this._(HeapType.eq, nullable);
+
+  /// A (possibly nullable) reference to the `func` heap type.
+  const RefType.func({bool nullable = FuncHeapType.defaultNullability})
+      : this._(HeapType.func, nullable);
+
+  /// A (possibly nullable) reference to the `data` heap type.
+  const RefType.data({bool nullable = DataHeapType.defaultNullability})
+      : this._(HeapType.data, nullable);
+
+  /// A (possibly nullable) reference to the `i31` heap type.
+  const RefType.i31({bool nullable = I31HeapType.defaultNullability})
+      : this._(HeapType.i31, nullable);
+
+  /// A (possibly nullable) reference to the `extern` heap type.
+  const RefType.extern({bool nullable = ExternHeapType.defaultNullability})
+      : this._(HeapType.extern, nullable);
+
+  /// A (possibly nullable) reference to a custom heap type.
+  RefType.def(DefType defType, {required bool nullable})
+      : this(defType, nullable: nullable);
+
+  @override
+  ValueType withNullability(bool nullable) =>
+      nullable == this.nullable ? this : RefType(heapType, nullable: nullable);
+
+  @override
+  bool get defaultable => nullable;
+
+  @override
+  bool isSubtypeOf(StorageType other) {
+    if (other is! RefType) return false;
+    if (nullable && !other.nullable) return false;
+    return heapType.isSubtypeOf(other.heapType);
+  }
+
+  @override
+  void serialize(Serializer s) {
+    if (nullable != heapType.nullableByDefault) {
+      s.writeByte(nullable ? 0x6C : 0x6B);
+    }
+    s.write(heapType);
+  }
+
+  @override
+  String toString() {
+    if (nullable == heapType.nullableByDefault) return "${heapType}ref";
+    return "ref${nullable ? " null " : " "}${heapType}";
+  }
+
+  @override
+  bool operator ==(Object other) =>
+      other is RefType &&
+      other.heapType == heapType &&
+      other.nullable == nullable;
+
+  @override
+  int get hashCode => heapType.hashCode * (nullable ? -1 : 1);
+}
+
+/// A *heap type*.
+abstract class HeapType implements Serializable {
+  const HeapType();
+
+  /// The `any` heap type.
+  static const any = AnyHeapType._();
+
+  /// The `eq` heap type.
+  static const eq = EqHeapType._();
+
+  /// The `func` heap type.
+  static const func = FuncHeapType._();
+
+  /// The `data` heap type.
+  static const data = DataHeapType._();
+
+  /// The `i31` heap type.
+  static const i31 = I31HeapType._();
+
+  /// The `extern` heap type.
+  static const extern = ExternHeapType._();
+
+  /// Whether this heap type is nullable by default, i.e. when written with the
+  /// -`ref` shorthand. A `null` value here means the heap type has no default
+  /// nullability, so the nullability of a reference has to be specified
+  /// explicitly.
+  bool? get nullableByDefault;
+
+  /// Whether this heap type is a declared subtype of the other heap type.
+  bool isSubtypeOf(HeapType other);
+
+  /// Whether this heap type is a structural subtype of the other heap type.
+  bool isStructuralSubtypeOf(HeapType other) => isSubtypeOf(other);
+}
+
+/// The `any` heap type.
+class AnyHeapType extends HeapType {
+  const AnyHeapType._();
+
+  static const defaultNullability = true;
+
+  @override
+  bool? get nullableByDefault => defaultNullability;
+
+  @override
+  bool isSubtypeOf(HeapType other) => other == HeapType.any;
+
+  @override
+  void serialize(Serializer s) => s.writeByte(0x6E);
+
+  @override
+  String toString() => "any";
+}
+
+/// The `eq` heap type.
+class EqHeapType extends HeapType {
+  const EqHeapType._();
+
+  static const defaultNullability = true;
+
+  @override
+  bool? get nullableByDefault => defaultNullability;
+
+  @override
+  bool isSubtypeOf(HeapType other) =>
+      other == HeapType.any || other == HeapType.eq;
+
+  @override
+  void serialize(Serializer s) => s.writeByte(0x6D);
+
+  @override
+  String toString() => "eq";
+}
+
+/// The `func` heap type.
+class FuncHeapType extends HeapType {
+  const FuncHeapType._();
+
+  static const defaultNullability = true;
+
+  @override
+  bool? get nullableByDefault => defaultNullability;
+
+  @override
+  bool isSubtypeOf(HeapType other) =>
+      other == HeapType.any || other == HeapType.func;
+
+  @override
+  void serialize(Serializer s) => s.writeByte(0x70);
+
+  @override
+  String toString() => "func";
+}
+
+/// The `data` heap type.
+class DataHeapType extends HeapType {
+  const DataHeapType._();
+
+  static const defaultNullability = false;
+
+  @override
+  bool? get nullableByDefault => defaultNullability;
+
+  @override
+  bool isSubtypeOf(HeapType other) =>
+      other == HeapType.any || other == HeapType.eq || other == HeapType.data;
+
+  @override
+  void serialize(Serializer s) => s.writeByte(0x67);
+
+  @override
+  String toString() => "data";
+}
+
+/// The `i31` heap type.
+class I31HeapType extends HeapType {
+  const I31HeapType._();
+
+  static const defaultNullability = false;
+
+  @override
+  bool? get nullableByDefault => defaultNullability;
+
+  @override
+  bool isSubtypeOf(HeapType other) =>
+      other == HeapType.any || other == HeapType.eq || other == HeapType.i31;
+
+  @override
+  void serialize(Serializer s) => s.writeByte(0x6A);
+
+  @override
+  String toString() => "i31";
+}
+
+/// The `extern` heap type.
+class ExternHeapType extends HeapType {
+  const ExternHeapType._();
+
+  static const defaultNullability = true;
+
+  @override
+  bool? get nullableByDefault => defaultNullability;
+
+  @override
+  bool isSubtypeOf(HeapType other) =>
+      other == HeapType.any || other == HeapType.extern;
+
+  @override
+  void serialize(Serializer s) => s.writeByte(0x6F);
+
+  @override
+  String toString() => "extern";
+}
+
+/// A custom heap type.
+abstract class DefType extends HeapType {
+  int? _index;
+
+  /// For nominal types: the declared supertype of this heap type.
+  final HeapType? superType;
+
+  /// The length of the supertype chain of this heap type.
+  final int depth;
+
+  DefType({this.superType})
+      : depth = superType is DefType ? superType.depth + 1 : 0;
+
+  int get index => _index ?? (throw "$runtimeType $this not added to module");
+  set index(int i) => _index = i;
+
+  bool get hasSuperType => superType != null;
+
+  @override
+  bool? get nullableByDefault => null;
+
+  @override
+  bool isSubtypeOf(HeapType other) {
+    if (this == other) return true;
+    if (hasSuperType) {
+      return superType!.isSubtypeOf(other);
+    }
+    return isStructuralSubtypeOf(other);
+  }
+
+  @override
+  void serialize(Serializer s) => s.writeSigned(index);
+
+  void serializeDefinition(Serializer s);
+}
+
+/// A custom function type.
+class FunctionType extends DefType {
+  final List<ValueType> inputs;
+  final List<ValueType> outputs;
+
+  FunctionType(this.inputs, this.outputs, {HeapType? superType})
+      : super(superType: superType);
+
+  @override
+  bool isStructuralSubtypeOf(HeapType other) {
+    if (other == HeapType.any || other == HeapType.func) return true;
+    if (other is! FunctionType) return false;
+    if (inputs.length != other.inputs.length) return false;
+    if (outputs.length != other.outputs.length) return false;
+    for (int i = 0; i < inputs.length; i++) {
+      // Inputs are contravariant.
+      if (!other.inputs[i].isSubtypeOf(inputs[i])) return false;
+    }
+    for (int i = 0; i < outputs.length; i++) {
+      // Outputs are covariant.
+      if (!outputs[i].isSubtypeOf(other.outputs[i])) return false;
+    }
+    return true;
+  }
+
+  @override
+  void serializeDefinition(Serializer s) {
+    s.writeByte(hasSuperType ? 0x5D : 0x60);
+    s.writeList(inputs);
+    s.writeList(outputs);
+    if (hasSuperType) {
+      assert(isStructuralSubtypeOf(superType!));
+      s.write(superType!);
+    }
+  }
+
+  @override
+  String toString() => "(${inputs.join(", ")}) -> (${outputs.join(", ")})";
+}
+
+/// A subtype of the `data` heap type, i.e. `struct` or `array`.
+abstract class DataType extends DefType {
+  final String name;
+
+  DataType(this.name, {HeapType? superType}) : super(superType: superType);
+
+  @override
+  String toString() => name;
+}
+
+/// A custom `struct` type.
+class StructType extends DataType {
+  final List<FieldType> fields = [];
+
+  StructType(String name, {Iterable<FieldType>? fields, HeapType? superType})
+      : super(name, superType: superType) {
+    if (fields != null) this.fields.addAll(fields);
+  }
+
+  @override
+  bool isStructuralSubtypeOf(HeapType other) {
+    if (other == HeapType.any ||
+        other == HeapType.eq ||
+        other == HeapType.data) {
+      return true;
+    }
+    if (other is! StructType) return false;
+    if (fields.length < other.fields.length) return false;
+    for (int i = 0; i < other.fields.length; i++) {
+      if (!fields[i].isSubtypeOf(other.fields[i])) return false;
+    }
+    return true;
+  }
+
+  @override
+  void serializeDefinition(Serializer s) {
+    s.writeByte(hasSuperType ? 0x5C : 0x5F);
+    s.writeList(fields);
+    if (hasSuperType) {
+      assert(isStructuralSubtypeOf(superType!));
+      s.write(superType!);
+    }
+  }
+}
+
+/// A custom `array` type.
+class ArrayType extends DataType {
+  late final FieldType elementType;
+
+  ArrayType(String name, {FieldType? elementType, HeapType? superType})
+      : super(name, superType: superType) {
+    if (elementType != null) this.elementType = elementType;
+  }
+
+  @override
+  bool isStructuralSubtypeOf(HeapType other) {
+    if (other == HeapType.any ||
+        other == HeapType.eq ||
+        other == HeapType.data) {
+      return true;
+    }
+    if (other is! ArrayType) return false;
+    return elementType.isSubtypeOf(other.elementType);
+  }
+
+  @override
+  void serializeDefinition(Serializer s) {
+    s.writeByte(hasSuperType ? 0x5B : 0x5E);
+    s.write(elementType);
+    if (hasSuperType) {
+      assert(isStructuralSubtypeOf(superType!));
+      s.write(superType!);
+    }
+  }
+}
+
+class _WithMutability<T extends StorageType> implements Serializable {
+  final T type;
+  final bool mutable;
+
+  _WithMutability(this.type, this.mutable);
+
+  @override
+  void serialize(Serializer s) {
+    s.write(type);
+    s.writeByte(mutable ? 0x01 : 0x00);
+  }
+
+  @override
+  String toString() => "${mutable ? "var " : "const "}$type";
+}
+
+/// A type for a global.
+///
+/// It consists of a type and a mutability.
+class GlobalType extends _WithMutability<ValueType> {
+  GlobalType(ValueType type, {bool mutable = true}) : super(type, mutable);
+}
+
+/// A type for a struct field or an array element.
+///
+/// It consists of a type and a mutability.
+class FieldType extends _WithMutability<StorageType> {
+  FieldType(StorageType type, {bool mutable = true}) : super(type, mutable);
+
+  /// The `i8` storage type as a field type.
+  FieldType.i8({bool mutable: true}) : this(PackedType.i8, mutable: mutable);
+
+  /// The `i16` storage type as a field type.
+  FieldType.i16({bool mutable: true}) : this(PackedType.i16, mutable: mutable);
+
+  bool isSubtypeOf(FieldType other) {
+    if (mutable != other.mutable) return false;
+    if (mutable) {
+      // Mutable fields are invariant.
+      return type == other.type;
+    } else {
+      // Immutable fields are covariant.
+      return type.isSubtypeOf(other.type);
+    }
+  }
+}
+
+enum PackedTypeKind { i8, i16 }
+
+/// A *packed type*, i.e. a storage type that only exists in memory.
+class PackedType implements StorageType {
+  final PackedTypeKind kind;
+
+  const PackedType._(this.kind);
+
+  /// The `i8` storage type.
+  static const i8 = PackedType._(PackedTypeKind.i8);
+
+  /// The `i16` storage type.
+  static const i16 = PackedType._(PackedTypeKind.i16);
+
+  @override
+  ValueType get unpacked => NumType.i32;
+
+  @override
+  bool isSubtypeOf(StorageType other) => this == other;
+
+  @override
+  bool get isPrimitive => true;
+
+  @override
+  int get byteSize {
+    switch (kind) {
+      case PackedTypeKind.i8:
+        return 1;
+      case PackedTypeKind.i16:
+        return 2;
+    }
+  }
+
+  @override
+  void serialize(Serializer s) {
+    switch (kind) {
+      case PackedTypeKind.i8:
+        s.writeByte(0x7A);
+        break;
+      case PackedTypeKind.i16:
+        s.writeByte(0x79);
+        break;
+    }
+  }
+
+  @override
+  String toString() {
+    switch (kind) {
+      case PackedTypeKind.i8:
+        return "i8";
+      case PackedTypeKind.i16:
+        return "i16";
+    }
+  }
+}
diff --git a/pkg/wasm_builder/lib/wasm_builder.dart b/pkg/wasm_builder/lib/wasm_builder.dart
new file mode 100644
index 0000000..8bf1b98
--- /dev/null
+++ b/pkg/wasm_builder/lib/wasm_builder.dart
@@ -0,0 +1,34 @@
+// 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.
+
+export 'src/module.dart'
+    show
+        DataSegment,
+        DefinedFunction,
+        DefinedGlobal,
+        BaseFunction,
+        Global,
+        ImportedFunction,
+        ImportedGlobal,
+        Local,
+        Memory,
+        Module,
+        Table;
+export 'src/types.dart'
+    show
+        ArrayType,
+        DataType,
+        DefType,
+        FieldType,
+        FunctionType,
+        GlobalType,
+        HeapType,
+        NumType,
+        PackedType,
+        RefType,
+        Rtt,
+        StorageType,
+        StructType,
+        ValueType;
+export 'src/instructions.dart' show Instructions, Label, ValidationError;
diff --git a/pkg/wasm_builder/pubspec.yaml b/pkg/wasm_builder/pubspec.yaml
new file mode 100644
index 0000000..af4d545
--- /dev/null
+++ b/pkg/wasm_builder/pubspec.yaml
@@ -0,0 +1,9 @@
+name: wasm_builder
+# This package is not intended for consumption on pub.dev. DO NOT publish.
+publish_to: none
+description: Generate binary Wasm modules
+
+environment:
+  sdk: '>=2.12.0'
+
+dependencies:
diff --git a/sdk/bin/dart2wasm b/sdk/bin/dart2wasm
new file mode 100755
index 0000000..893fcda
--- /dev/null
+++ b/sdk/bin/dart2wasm
@@ -0,0 +1,48 @@
+#!/usr/bin/env bash
+# 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.
+
+# Run dart2wasm on the Dart VM. This script assumes the Dart repo's
+# directory structure.
+
+function follow_links() {
+  file="$1"
+  while [ -h "$file" ]; do
+    # On Mac OS, readlink -f doesn't work.
+    file="$(readlink "$file")"
+  done
+  echo "$file"
+}
+
+# Unlike $0, $BASH_SOURCE points to the absolute path of this file.
+PROG_NAME="$(follow_links "$BASH_SOURCE")"
+
+# Handle the case where dart-sdk/bin has been symlinked to.
+BIN_DIR="$(cd "${PROG_NAME%/*}" ; pwd -P)"
+SDK_DIR="$(cd "${BIN_DIR}/.." ; pwd -P)"
+
+SDK_ARG="--dart-sdk=$SDK_DIR"
+
+DART="$BIN_DIR/dart"
+
+unset EXTRA_VM_OPTIONS
+declare -a EXTRA_VM_OPTIONS
+
+case $0 in
+  *_developer)
+    EXTRA_VM_OPTIONS+=('--enable_asserts')
+    ;;
+esac
+
+# We allow extra vm options to be passed in through an environment variable.
+if [[ $DART_VM_OPTIONS ]]; then
+  read -a OPTIONS <<< "$DART_VM_OPTIONS"
+  EXTRA_VM_OPTIONS+=("${OPTIONS[@]}")
+fi
+
+DART_ROOT="$(cd "${SDK_DIR}/.." ; pwd -P)"
+
+DART2WASM_COMPILER="$DART_ROOT/pkg/dart2wasm/bin/dart2wasm.dart"
+
+exec "$DART" "--packages=$DART_ROOT/.packages" "${EXTRA_VM_OPTIONS[@]}" "$DART2WASM_COMPILER" "$SDK_ARG" "$@"
diff --git a/sdk/bin/dart2wasm_developer b/sdk/bin/dart2wasm_developer
new file mode 100755
index 0000000..aabf8ea
--- /dev/null
+++ b/sdk/bin/dart2wasm_developer
@@ -0,0 +1,6 @@
+#!/usr/bin/env bash
+# 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.
+
+. ${BASH_SOURCE%_developer}
diff --git a/sdk/lib/_internal/vm/lib/compact_hash.dart b/sdk/lib/_internal/vm/lib/compact_hash.dart
index 6ed0c80..fa4b32d 100644
--- a/sdk/lib/_internal/vm/lib/compact_hash.dart
+++ b/sdk/lib/_internal/vm/lib/compact_hash.dart
@@ -624,6 +624,9 @@
   V? operator [](Object? o) => _validKey(o) ? super[o] : null;
   V? remove(Object? o) => _validKey(o) ? super.remove(o) : null;
 
+  @pragma("wasm:entry-point")
+  void operator []=(K key, V value);
+
   _CompactLinkedCustomHashMap(this._equality, this._hasher, validKey)
       : _validKey = (validKey != null) ? validKey : new _TypeTest<K>().test,
         super(_HashBase._INITIAL_INDEX_SIZE);
diff --git a/sdk/lib/_internal/vm/lib/typed_data_patch.dart b/sdk/lib/_internal/vm/lib/typed_data_patch.dart
index 37587c7..68c63b1 100644
--- a/sdk/lib/_internal/vm/lib/typed_data_patch.dart
+++ b/sdk/lib/_internal/vm/lib/typed_data_patch.dart
@@ -2228,6 +2228,7 @@
 }
 
 @pragma("vm:entry-point")
+@pragma("wasm:entry-point")
 class _Uint8List extends _TypedList
     with _IntListMixin, _TypedIntListMixin<Uint8List>
     implements Uint8List {
@@ -2281,6 +2282,7 @@
 }
 
 @pragma("vm:entry-point")
+@pragma("wasm:entry-point")
 class _Uint8ClampedList extends _TypedList
     with _IntListMixin, _TypedIntListMixin<Uint8ClampedList>
     implements Uint8ClampedList {
@@ -2334,6 +2336,7 @@
 }
 
 @pragma("vm:entry-point")
+@pragma("wasm:entry-point")
 class _Int16List extends _TypedList
     with _IntListMixin, _TypedIntListMixin<Int16List>
     implements Int16List {
@@ -2407,6 +2410,7 @@
 }
 
 @pragma("vm:entry-point")
+@pragma("wasm:entry-point")
 class _Uint16List extends _TypedList
     with _IntListMixin, _TypedIntListMixin<Uint16List>
     implements Uint16List {
@@ -2480,6 +2484,7 @@
 }
 
 @pragma("vm:entry-point")
+@pragma("wasm:entry-point")
 class _Int32List extends _TypedList
     with _IntListMixin, _TypedIntListMixin<Int32List>
     implements Int32List {
@@ -2540,6 +2545,7 @@
 }
 
 @pragma("vm:entry-point")
+@pragma("wasm:entry-point")
 class _Uint32List extends _TypedList
     with _IntListMixin, _TypedIntListMixin<Uint32List>
     implements Uint32List {
@@ -2600,6 +2606,7 @@
 }
 
 @pragma("vm:entry-point")
+@pragma("wasm:entry-point")
 class _Int64List extends _TypedList
     with _IntListMixin, _TypedIntListMixin<Int64List>
     implements Int64List {
@@ -2660,6 +2667,7 @@
 }
 
 @pragma("vm:entry-point")
+@pragma("wasm:entry-point")
 class _Uint64List extends _TypedList
     with _IntListMixin, _TypedIntListMixin<Uint64List>
     implements Uint64List {
@@ -2720,6 +2728,7 @@
 }
 
 @pragma("vm:entry-point")
+@pragma("wasm:entry-point")
 class _Float32List extends _TypedList
     with _DoubleListMixin, _TypedDoubleListMixin<Float32List>
     implements Float32List {
@@ -2781,6 +2790,7 @@
 }
 
 @pragma("vm:entry-point")
+@pragma("wasm:entry-point")
 class _Float64List extends _TypedList
     with _DoubleListMixin, _TypedDoubleListMixin<Float64List>
     implements Float64List {
@@ -3040,6 +3050,7 @@
 }
 
 @pragma("vm:entry-point")
+@pragma("wasm:entry-point")
 class _ExternalUint8Array extends _TypedList
     with _IntListMixin, _TypedIntListMixin<Uint8List>
     implements Uint8List {
@@ -4123,6 +4134,7 @@
 }
 
 @pragma("vm:entry-point")
+@pragma("wasm:entry-point")
 class _Int8ArrayView extends _TypedListView
     with _IntListMixin, _TypedIntListMixin<Int8List>
     implements Int8List {
@@ -4164,6 +4176,7 @@
 }
 
 @pragma("vm:entry-point")
+@pragma("wasm:entry-point")
 class _Uint8ArrayView extends _TypedListView
     with _IntListMixin, _TypedIntListMixin<Uint8List>
     implements Uint8List {
@@ -4205,6 +4218,7 @@
 }
 
 @pragma("vm:entry-point")
+@pragma("wasm:entry-point")
 class _Uint8ClampedArrayView extends _TypedListView
     with _IntListMixin, _TypedIntListMixin<Uint8ClampedList>
     implements Uint8ClampedList {
@@ -4246,6 +4260,7 @@
 }
 
 @pragma("vm:entry-point")
+@pragma("wasm:entry-point")
 class _Int16ArrayView extends _TypedListView
     with _IntListMixin, _TypedIntListMixin<Int16List>
     implements Int16List {
@@ -4300,6 +4315,7 @@
 }
 
 @pragma("vm:entry-point")
+@pragma("wasm:entry-point")
 class _Uint16ArrayView extends _TypedListView
     with _IntListMixin, _TypedIntListMixin<Uint16List>
     implements Uint16List {
@@ -4355,6 +4371,7 @@
 }
 
 @pragma("vm:entry-point")
+@pragma("wasm:entry-point")
 class _Int32ArrayView extends _TypedListView
     with _IntListMixin, _TypedIntListMixin<Int32List>
     implements Int32List {
@@ -4396,6 +4413,7 @@
 }
 
 @pragma("vm:entry-point")
+@pragma("wasm:entry-point")
 class _Uint32ArrayView extends _TypedListView
     with _IntListMixin, _TypedIntListMixin<Uint32List>
     implements Uint32List {
@@ -4437,6 +4455,7 @@
 }
 
 @pragma("vm:entry-point")
+@pragma("wasm:entry-point")
 class _Int64ArrayView extends _TypedListView
     with _IntListMixin, _TypedIntListMixin<Int64List>
     implements Int64List {
@@ -4478,6 +4497,7 @@
 }
 
 @pragma("vm:entry-point")
+@pragma("wasm:entry-point")
 class _Uint64ArrayView extends _TypedListView
     with _IntListMixin, _TypedIntListMixin<Uint64List>
     implements Uint64List {
@@ -4519,6 +4539,7 @@
 }
 
 @pragma("vm:entry-point")
+@pragma("wasm:entry-point")
 class _Float32ArrayView extends _TypedListView
     with _DoubleListMixin, _TypedDoubleListMixin<Float32List>
     implements Float32List {
@@ -4560,6 +4581,7 @@
 }
 
 @pragma("vm:entry-point")
+@pragma("wasm:entry-point")
 class _Float64ArrayView extends _TypedListView
     with _DoubleListMixin, _TypedDoubleListMixin<Float64List>
     implements Float64List {
@@ -4718,6 +4740,7 @@
 }
 
 @pragma("vm:entry-point")
+@pragma("wasm:entry-point")
 class _ByteDataView implements ByteData {
   @pragma("vm:recognized", "other")
   @pragma("vm:exact-result-type", _ByteDataView)
diff --git a/sdk/lib/_internal/wasm/lib/bool.dart b/sdk/lib/_internal/wasm/lib/bool.dart
new file mode 100644
index 0000000..929e519
--- /dev/null
+++ b/sdk/lib/_internal/wasm/lib/bool.dart
@@ -0,0 +1,21 @@
+// 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.
+
+@pragma("wasm:entry-point")
+class _BoxedBool implements bool {
+  // A boxed bool contains an unboxed bool.
+  @pragma("wasm:entry-point")
+  bool value = false;
+
+  @override
+  bool operator ==(Object other) {
+    return other is bool
+        ? this == other // Intrinsic ==
+        : false;
+  }
+
+  bool operator &(bool other) => this & other; // Intrinsic &
+  bool operator ^(bool other) => this ^ other; // Intrinsic ^
+  bool operator |(bool other) => this | other; // Intrinsic |
+}
diff --git a/sdk/lib/_internal/wasm/lib/class_id.dart b/sdk/lib/_internal/wasm/lib/class_id.dart
new file mode 100644
index 0000000..e650509
--- /dev/null
+++ b/sdk/lib/_internal/wasm/lib/class_id.dart
@@ -0,0 +1,20 @@
+// 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.
+
+// part of "internal_patch.dart";
+
+@pragma("wasm:entry-point")
+class ClassID {
+  external static int getID(Object value);
+
+  @pragma("wasm:class-id", "dart.typed_data#_ExternalUint8Array")
+  external static int get cidExternalUint8Array;
+  @pragma("wasm:class-id", "dart.typed_data#_Uint8List")
+  external static int get cidUint8Array;
+  @pragma("wasm:class-id", "dart.typed_data#_Uint8ArrayView")
+  external static int get cidUint8ArrayView;
+
+  // Dummy, only used by VM-specific hash table code.
+  static final int numPredefinedCids = 1;
+}
diff --git a/sdk/lib/_internal/wasm/lib/core_patch.dart b/sdk/lib/_internal/wasm/lib/core_patch.dart
new file mode 100644
index 0000000..64b7d9d
--- /dev/null
+++ b/sdk/lib/_internal/wasm/lib/core_patch.dart
@@ -0,0 +1,51 @@
+// 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.
+
+import "dart:_internal" show patch;
+
+import "dart:_internal"
+    show
+        allocateOneByteString,
+        allocateTwoByteString,
+        CodeUnits,
+        copyRangeFromUint8ListToOneByteString,
+        doubleToIntBits,
+        EfficientLengthIterable,
+        FixedLengthListMixin,
+        IterableElementError,
+        ListIterator,
+        Lists,
+        mix64,
+        POWERS_OF_TEN,
+        SubListIterable,
+        UnmodifiableListMixin,
+        has63BitSmis,
+        makeFixedListUnmodifiable,
+        makeListFixedLength,
+        patch,
+        unsafeCast,
+        writeIntoOneByteString,
+        writeIntoTwoByteString;
+
+import "dart:collection"
+    show
+        HashMap,
+        IterableBase,
+        LinkedHashMap,
+        LinkedList,
+        LinkedListEntry,
+        ListBase,
+        MapBase,
+        Maps,
+        UnmodifiableMapBase,
+        UnmodifiableMapView;
+
+import 'dart:math' show Random;
+
+import "dart:typed_data"
+    show Endian, Uint8List, Int64List, Uint16List, Uint32List;
+
+import 'dart:wasm';
+
+typedef _Smi = int; // For compatibility with VM patch files
diff --git a/sdk/lib/_internal/wasm/lib/date_patch.dart b/sdk/lib/_internal/wasm/lib/date_patch.dart
new file mode 100644
index 0000000..f2f0672
--- /dev/null
+++ b/sdk/lib/_internal/wasm/lib/date_patch.dart
@@ -0,0 +1,542 @@
+// 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.
+
+// part of "core_patch.dart";
+
+// This file is identical to the VM `date_patch.dart` except for the
+// implementation of `_getCurrentMicros` and the `_jsDateNow` import.
+// TODO(askesc): Share this file with the VM when the patching mechanism gains
+// support for patching an external member from a patch in a separate patch.
+
+@pragma("wasm:import", "Date.now")
+external double _jsDateNow();
+
+// VM implementation of DateTime.
+@patch
+class DateTime {
+  // Natives.
+  // The natives have been moved up here to work around Issue 10401.
+  @pragma("vm:external-name", "DateTime_currentTimeMicros")
+  static int _getCurrentMicros() =>
+      (_jsDateNow() * Duration.microsecondsPerMillisecond).toInt();
+
+  @pragma("vm:external-name", "DateTime_timeZoneName")
+  external static String _timeZoneNameForClampedSeconds(int secondsSinceEpoch);
+
+  @pragma("vm:external-name", "DateTime_timeZoneOffsetInSeconds")
+  external static int _timeZoneOffsetInSecondsForClampedSeconds(
+      int secondsSinceEpoch);
+
+  // Daylight-savings independent adjustment for the local time zone.
+  @pragma("vm:external-name", "DateTime_localTimeZoneAdjustmentInSeconds")
+  external static int _localTimeZoneAdjustmentInSeconds();
+
+  static const _MICROSECOND_INDEX = 0;
+  static const _MILLISECOND_INDEX = 1;
+  static const _SECOND_INDEX = 2;
+  static const _MINUTE_INDEX = 3;
+  static const _HOUR_INDEX = 4;
+  static const _DAY_INDEX = 5;
+  static const _WEEKDAY_INDEX = 6;
+  static const _MONTH_INDEX = 7;
+  static const _YEAR_INDEX = 8;
+
+  List<int>? __parts;
+
+  @patch
+  DateTime.fromMillisecondsSinceEpoch(int millisecondsSinceEpoch,
+      {bool isUtc: false})
+      : this._withValue(
+            _validateMilliseconds(millisecondsSinceEpoch) *
+                Duration.microsecondsPerMillisecond,
+            isUtc: isUtc);
+
+  @patch
+  DateTime.fromMicrosecondsSinceEpoch(int microsecondsSinceEpoch,
+      {bool isUtc: false})
+      : this._withValue(microsecondsSinceEpoch, isUtc: isUtc);
+
+  @patch
+  DateTime._internal(int year, int month, int day, int hour, int minute,
+      int second, int millisecond, int microsecond, bool isUtc)
+      : this.isUtc = isUtc,
+        this._value = _brokenDownDateToValue(year, month, day, hour, minute,
+                second, millisecond, microsecond, isUtc) ??
+            -1 {
+    if (_value == -1) throw new ArgumentError();
+    if (isUtc == null) throw new ArgumentError();
+  }
+
+  static int _validateMilliseconds(int millisecondsSinceEpoch) =>
+      RangeError.checkValueInInterval(
+          millisecondsSinceEpoch,
+          -_maxMillisecondsSinceEpoch,
+          _maxMillisecondsSinceEpoch,
+          "millisecondsSinceEpoch");
+
+  @patch
+  DateTime._now()
+      : isUtc = false,
+        _value = _getCurrentMicros();
+
+  @patch
+  String get timeZoneName {
+    if (isUtc) return "UTC";
+    return _timeZoneName(microsecondsSinceEpoch);
+  }
+
+  @patch
+  Duration get timeZoneOffset {
+    if (isUtc) return new Duration();
+    int offsetInSeconds = _timeZoneOffsetInSeconds(microsecondsSinceEpoch);
+    return new Duration(seconds: offsetInSeconds);
+  }
+
+  @patch
+  bool operator ==(dynamic other) =>
+      other is DateTime &&
+      _value == other.microsecondsSinceEpoch &&
+      isUtc == other.isUtc;
+
+  @patch
+  bool isBefore(DateTime other) => _value < other.microsecondsSinceEpoch;
+
+  @patch
+  bool isAfter(DateTime other) => _value > other.microsecondsSinceEpoch;
+
+  @patch
+  bool isAtSameMomentAs(DateTime other) =>
+      _value == other.microsecondsSinceEpoch;
+
+  @patch
+  int compareTo(DateTime other) =>
+      _value.compareTo(other.microsecondsSinceEpoch);
+
+  /** The first list contains the days until each month in non-leap years. The
+    * second list contains the days in leap years. */
+  static const List<List<int>> _DAYS_UNTIL_MONTH = const [
+    const [0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334],
+    const [0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335]
+  ];
+
+  static List<int> _computeUpperPart(int localMicros) {
+    const int DAYS_IN_4_YEARS = 4 * 365 + 1;
+    const int DAYS_IN_100_YEARS = 25 * DAYS_IN_4_YEARS - 1;
+    const int DAYS_IN_400_YEARS = 4 * DAYS_IN_100_YEARS + 1;
+    const int DAYS_1970_TO_2000 = 30 * 365 + 7;
+    const int DAYS_OFFSET =
+        1000 * DAYS_IN_400_YEARS + 5 * DAYS_IN_400_YEARS - DAYS_1970_TO_2000;
+    const int YEARS_OFFSET = 400000;
+
+    int resultYear = 0;
+    int resultMonth = 0;
+    int resultDay = 0;
+
+    // Always round down.
+    final int daysSince1970 =
+        _flooredDivision(localMicros, Duration.microsecondsPerDay);
+    int days = daysSince1970;
+    days += DAYS_OFFSET;
+    resultYear = 400 * (days ~/ DAYS_IN_400_YEARS) - YEARS_OFFSET;
+    days = unsafeCast<int>(days.remainder(DAYS_IN_400_YEARS));
+    days--;
+    int yd1 = days ~/ DAYS_IN_100_YEARS;
+    days = unsafeCast<int>(days.remainder(DAYS_IN_100_YEARS));
+    resultYear += 100 * yd1;
+    days++;
+    int yd2 = days ~/ DAYS_IN_4_YEARS;
+    days = unsafeCast<int>(days.remainder(DAYS_IN_4_YEARS));
+    resultYear += 4 * yd2;
+    days--;
+    int yd3 = days ~/ 365;
+    days = unsafeCast<int>(days.remainder(365));
+    resultYear += yd3;
+
+    bool isLeap = (yd1 == 0 || yd2 != 0) && yd3 == 0;
+    if (isLeap) days++;
+
+    List<int> daysUntilMonth = _DAYS_UNTIL_MONTH[isLeap ? 1 : 0];
+    for (resultMonth = 12;
+        daysUntilMonth[resultMonth - 1] > days;
+        resultMonth--) {
+      // Do nothing.
+    }
+    resultDay = days - daysUntilMonth[resultMonth - 1] + 1;
+
+    int resultMicrosecond = localMicros % Duration.microsecondsPerMillisecond;
+    int resultMillisecond =
+        _flooredDivision(localMicros, Duration.microsecondsPerMillisecond) %
+            Duration.millisecondsPerSecond;
+    int resultSecond =
+        _flooredDivision(localMicros, Duration.microsecondsPerSecond) %
+            Duration.secondsPerMinute;
+
+    int resultMinute =
+        _flooredDivision(localMicros, Duration.microsecondsPerMinute);
+    resultMinute %= Duration.minutesPerHour;
+
+    int resultHour =
+        _flooredDivision(localMicros, Duration.microsecondsPerHour);
+    resultHour %= Duration.hoursPerDay;
+
+    // In accordance with ISO 8601 a week
+    // starts with Monday. Monday has the value 1 up to Sunday with 7.
+    // 1970-1-1 was a Thursday.
+    int resultWeekday = ((daysSince1970 + DateTime.thursday - DateTime.monday) %
+            DateTime.daysPerWeek) +
+        DateTime.monday;
+
+    List<int> list = new List<int>.filled(_YEAR_INDEX + 1, 0);
+    list[_MICROSECOND_INDEX] = resultMicrosecond;
+    list[_MILLISECOND_INDEX] = resultMillisecond;
+    list[_SECOND_INDEX] = resultSecond;
+    list[_MINUTE_INDEX] = resultMinute;
+    list[_HOUR_INDEX] = resultHour;
+    list[_DAY_INDEX] = resultDay;
+    list[_WEEKDAY_INDEX] = resultWeekday;
+    list[_MONTH_INDEX] = resultMonth;
+    list[_YEAR_INDEX] = resultYear;
+    return list;
+  }
+
+  List<int> get _parts {
+    return __parts ??= _computeUpperPart(_localDateInUtcMicros);
+  }
+
+  @patch
+  DateTime add(Duration duration) {
+    return new DateTime._withValue(_value + duration.inMicroseconds,
+        isUtc: isUtc);
+  }
+
+  @patch
+  DateTime subtract(Duration duration) {
+    return new DateTime._withValue(_value - duration.inMicroseconds,
+        isUtc: isUtc);
+  }
+
+  @patch
+  Duration difference(DateTime other) {
+    return new Duration(microseconds: _value - other.microsecondsSinceEpoch);
+  }
+
+  @patch
+  int get millisecondsSinceEpoch =>
+      _value ~/ Duration.microsecondsPerMillisecond;
+
+  @patch
+  int get microsecondsSinceEpoch => _value;
+
+  @patch
+  int get microsecond => _parts[_MICROSECOND_INDEX];
+
+  @patch
+  int get millisecond => _parts[_MILLISECOND_INDEX];
+
+  @patch
+  int get second => _parts[_SECOND_INDEX];
+
+  @patch
+  int get minute => _parts[_MINUTE_INDEX];
+
+  @patch
+  int get hour => _parts[_HOUR_INDEX];
+
+  @patch
+  int get day => _parts[_DAY_INDEX];
+
+  @patch
+  int get weekday => _parts[_WEEKDAY_INDEX];
+
+  @patch
+  int get month => _parts[_MONTH_INDEX];
+
+  @patch
+  int get year => _parts[_YEAR_INDEX];
+
+  /**
+   * Returns the amount of microseconds in UTC that represent the same values
+   * as [this].
+   *
+   * Say `t` is the result of this function, then
+   * * `this.year == new DateTime.fromMicrosecondsSinceEpoch(t, true).year`,
+   * * `this.month == new DateTime.fromMicrosecondsSinceEpoch(t, true).month`,
+   * * `this.day == new DateTime.fromMicrosecondsSinceEpoch(t, true).day`,
+   * * `this.hour == new DateTime.fromMicrosecondsSinceEpoch(t, true).hour`,
+   * * ...
+   *
+   * Daylight savings is computed as if the date was computed in [1970..2037].
+   * If [this] lies outside this range then it is a year with similar
+   * properties (leap year, weekdays) is used instead.
+   */
+  int get _localDateInUtcMicros {
+    int micros = _value;
+    if (isUtc) return micros;
+    int offset =
+        _timeZoneOffsetInSeconds(micros) * Duration.microsecondsPerSecond;
+    return micros + offset;
+  }
+
+  static int _flooredDivision(int a, int b) {
+    return (a - (a < 0 ? b - 1 : 0)) ~/ b;
+  }
+
+  // Returns the days since 1970 for the start of the given [year].
+  // [year] may be before epoch.
+  static int _dayFromYear(int year) {
+    return 365 * (year - 1970) +
+        _flooredDivision(year - 1969, 4) -
+        _flooredDivision(year - 1901, 100) +
+        _flooredDivision(year - 1601, 400);
+  }
+
+  static bool _isLeapYear(int y) {
+    // (y % 16 == 0) matches multiples of 400, and is faster than % 400.
+    return (y % 4 == 0) && ((y % 16 == 0) || (y % 100 != 0));
+  }
+
+  /// Converts the given broken down date to microseconds.
+  @patch
+  static int? _brokenDownDateToValue(int year, int month, int day, int hour,
+      int minute, int second, int millisecond, int microsecond, bool isUtc) {
+    // Simplify calculations by working with zero-based month.
+    --month;
+    // Deal with under and overflow.
+    if (month >= 12) {
+      year += month ~/ 12;
+      month = month % 12;
+    } else if (month < 0) {
+      int realMonth = month % 12;
+      year += (month - realMonth) ~/ 12;
+      month = realMonth;
+    }
+
+    // First compute the seconds in UTC, independent of the [isUtc] flag. If
+    // necessary we will add the time-zone offset later on.
+    int days = day - 1;
+    days += _DAYS_UNTIL_MONTH[_isLeapYear(year) ? 1 : 0][month];
+    days += _dayFromYear(year);
+    int microsecondsSinceEpoch = days * Duration.microsecondsPerDay +
+        hour * Duration.microsecondsPerHour +
+        minute * Duration.microsecondsPerMinute +
+        second * Duration.microsecondsPerSecond +
+        millisecond * Duration.microsecondsPerMillisecond +
+        microsecond;
+
+    if (!isUtc) {
+      // Since [_timeZoneOffsetInSeconds] will crash if the input is far out of
+      // the valid range we do a preliminary test that weeds out values that can
+      // not become valid even with timezone adjustments.
+      // The timezone adjustment is always less than a day, so adding a security
+      // margin of one day should be enough.
+      if (microsecondsSinceEpoch.abs() >
+          _maxMillisecondsSinceEpoch * Duration.microsecondsPerMillisecond +
+              Duration.microsecondsPerDay) {
+        return null;
+      }
+
+      microsecondsSinceEpoch -= _toLocalTimeOffset(microsecondsSinceEpoch);
+    }
+    if (microsecondsSinceEpoch.abs() >
+        _maxMillisecondsSinceEpoch * Duration.microsecondsPerMillisecond) {
+      return null;
+    }
+    return microsecondsSinceEpoch;
+  }
+
+  static int _weekDay(y) {
+    // 1/1/1970 was a Thursday.
+    return (_dayFromYear(y) + 4) % 7;
+  }
+
+  /**
+   * Returns a year in the range 2008-2035 matching
+   * * leap year, and
+   * * week day of first day.
+   *
+   * Leap seconds are ignored.
+   * Adapted from V8's date implementation. See ECMA 262 - 15.9.1.9.
+   */
+  static int _equivalentYear(int year) {
+    // Returns year y so that _weekDay(y) == _weekDay(year).
+    // _weekDay returns the week day (in range 0 - 6).
+    // 1/1/1956 was a Sunday (i.e. weekday 0). 1956 was a leap-year.
+    // 1/1/1967 was a Sunday (i.e. weekday 0).
+    // Without leap years a subsequent year has a week day + 1 (for example
+    // 1/1/1968 was a Monday). With leap-years it jumps over one week day
+    // (e.g. 1/1/1957 was a Tuesday).
+    // After 12 years the weekdays have advanced by 12 days + 3 leap days =
+    // 15 days. 15 % 7 = 1. So after 12 years the week day has always
+    // (now independently of leap-years) advanced by one.
+    // weekDay * 12 gives thus a year starting with the wanted weekDay.
+    int recentYear = (_isLeapYear(year) ? 1956 : 1967) + (_weekDay(year) * 12);
+    // Close to the year 2008 the calendar cycles every 4 * 7 years (4 for the
+    // leap years, 7 for the weekdays).
+    // Find the year in the range 2008..2037 that is equivalent mod 28.
+    return 2008 + (recentYear - 2008) % 28;
+  }
+
+  /**
+   * Returns the UTC year for the corresponding [secondsSinceEpoch].
+   * It is relatively fast for values in the range 0 to year 2098.
+   *
+   * Code is adapted from V8.
+   */
+  static int _yearsFromSecondsSinceEpoch(int secondsSinceEpoch) {
+    const int DAYS_IN_4_YEARS = 4 * 365 + 1;
+    const int DAYS_IN_100_YEARS = 25 * DAYS_IN_4_YEARS - 1;
+    const int DAYS_YEAR_2098 = DAYS_IN_100_YEARS + 6 * DAYS_IN_4_YEARS;
+
+    int days = secondsSinceEpoch ~/ Duration.secondsPerDay;
+    if (days > 0 && days < DAYS_YEAR_2098) {
+      // According to V8 this fast case works for dates from 1970 to 2099.
+      return 1970 + (4 * days + 2) ~/ DAYS_IN_4_YEARS;
+    }
+    int micros = secondsSinceEpoch * Duration.microsecondsPerSecond;
+    return _computeUpperPart(micros)[_YEAR_INDEX];
+  }
+
+  /**
+   * Returns a date in seconds that is equivalent to the given
+   * date in microseconds [microsecondsSinceEpoch]. An equivalent
+   * date has the same fields (`month`, `day`, etc.) as the given
+   * date, but the `year` is in the range [1901..2038].
+   *
+   * * The time since the beginning of the year is the same.
+   * * If the given date is in a leap year then the returned
+   *   seconds are in a leap year, too.
+   * * The week day of given date is the same as the one for the
+   *   returned date.
+   */
+  static int _equivalentSeconds(int microsecondsSinceEpoch) {
+    const int CUT_OFF_SECONDS = 0x7FFFFFFF;
+
+    int secondsSinceEpoch = _flooredDivision(
+        microsecondsSinceEpoch, Duration.microsecondsPerSecond);
+
+    if (secondsSinceEpoch.abs() > CUT_OFF_SECONDS) {
+      int year = _yearsFromSecondsSinceEpoch(secondsSinceEpoch);
+      int days = _dayFromYear(year);
+      int equivalentYear = _equivalentYear(year);
+      int equivalentDays = _dayFromYear(equivalentYear);
+      int diffDays = equivalentDays - days;
+      secondsSinceEpoch += diffDays * Duration.secondsPerDay;
+    }
+    return secondsSinceEpoch;
+  }
+
+  static int _timeZoneOffsetInSeconds(int microsecondsSinceEpoch) {
+    int equivalentSeconds = _equivalentSeconds(microsecondsSinceEpoch);
+    return _timeZoneOffsetInSecondsForClampedSeconds(equivalentSeconds);
+  }
+
+  static String _timeZoneName(int microsecondsSinceEpoch) {
+    int equivalentSeconds = _equivalentSeconds(microsecondsSinceEpoch);
+    return _timeZoneNameForClampedSeconds(equivalentSeconds);
+  }
+
+  /// Finds the local time corresponding to a UTC date and time.
+  ///
+  /// The [microsecondsSinceEpoch] represents a particular
+  /// calendar date and clock time in UTC.
+  /// This methods returns a (usually different) point in time
+  /// where the local time had the same calendar date and clock
+  /// time (if such a time exists, otherwise it finds the "best"
+  /// substitute).
+  ///
+  /// A valid result is a point in time `microsecondsSinceEpoch - offset`
+  /// where the local time zone offset is `+offset`.
+  ///
+  /// In some cases there are two valid results, due to a time zone
+  /// change setting the clock back (for example exiting from daylight
+  /// saving time). In that case, we return the *earliest* valid result.
+  ///
+  /// In some cases there are no valid results, due to a time zone
+  /// change setting the clock forward (for example entering daylight
+  /// saving time). In that case, we return the time which would have
+  /// been correct in the earlier time zone (so asking for 2:30 AM
+  /// when clocks move directly from 2:00 to 3:00 will give the
+  /// time that *would have been* 2:30 in the earlier time zone,
+  /// which is now 3:30 in the local time zone).
+  ///
+  /// Returns the point in time as a number of microseconds since epoch.
+  static int _toLocalTimeOffset(int microsecondsSinceEpoch) {
+    // Argument is the UTC time corresponding to the desired
+    // calendar date/wall time.
+    // We now need to find an UTC time where the difference
+    // from `microsecondsSinceEpoch` is the same as the
+    // local time offset at that time. That is, we want to
+    // find `adjustment` in microseconds such that:
+    //
+    //  _timeZoneOffsetInSeconds(microsecondsSinceEpoch - offset)
+    //      * Duration.microsecondsPerSecond == offset
+    //
+    // Such an offset might not exist, if that wall time
+    // is skipped when a time zone change moves the clock forwards.
+    // In that case we pick a time after the switch which would be
+    // correct in the previous time zone.
+    // Also, there might be more than one solution if a time zone
+    // change moves the clock backwards and the same wall clock
+    // time occurs twice in the same day.
+    // In that case we pick the one in the time zone prior to
+    // the switch.
+
+    // Start with the time zone at the current microseconds since
+    // epoch. It's within one day of the real time we're looking for.
+
+    int offset = _timeZoneOffsetInSeconds(microsecondsSinceEpoch) *
+        Duration.microsecondsPerSecond;
+
+    // If offset is 0 (we're right around the UTC+0, and)
+    // we have found one solution.
+    if (offset != 0) {
+      // If not, try to find an actual solution in the time zone
+      // we just discovered.
+      int offset2 = _timeZoneOffsetInSeconds(microsecondsSinceEpoch - offset) *
+          Duration.microsecondsPerSecond;
+      if (offset2 != offset) {
+        // Also not a solution. We have found a second time zone
+        // within the same day. We assume that's all there are.
+        // Try again with the new time zone.
+        int offset3 =
+            _timeZoneOffsetInSeconds(microsecondsSinceEpoch - offset2) *
+                Duration.microsecondsPerSecond;
+        // Either offset3 is a solution (equal to offset2),
+        // or we have found two different time zones and no solution.
+        // In the latter case we choose the lower offset (latter time).
+        return (offset2 <= offset3 ? offset2 : offset3);
+      }
+      // We have found one solution and one time zone.
+      offset = offset2;
+    }
+    // Try to see if there is an earlier time zone which also
+    // has a solution.
+    // Pretends time zone changes are always at most two hours.
+    // (Double daylight saving happened, fx, in part of Canada in 1988).
+    int offset4 = _timeZoneOffsetInSeconds(microsecondsSinceEpoch -
+            offset -
+            2 * Duration.microsecondsPerHour) *
+        Duration.microsecondsPerSecond;
+    if (offset4 > offset) {
+      // The time zone at the earlier time had a greater
+      // offset, so it's possible that the desired wall clock
+      // occurs in that time zone too.
+      if (offset4 == offset + 2 * Duration.microsecondsPerHour) {
+        // A second and earlier solution, so use that.
+        return offset4;
+      }
+      // The time zone differs one hour earlier, but not by one
+      // hour, so check again in that time zone.
+      int offset5 = _timeZoneOffsetInSeconds(microsecondsSinceEpoch - offset4) *
+          Duration.microsecondsPerSecond;
+      if (offset5 == offset4) {
+        // Found a second solution earlier than the first solution, so use that.
+        return offset4;
+      }
+    }
+    // Did not find a solution in the earlier time
+    // zone, so just use the original result.
+    return offset;
+  }
+}
diff --git a/sdk/lib/_internal/wasm/lib/developer.dart b/sdk/lib/_internal/wasm/lib/developer.dart
new file mode 100644
index 0000000..ba930b3
--- /dev/null
+++ b/sdk/lib/_internal/wasm/lib/developer.dart
@@ -0,0 +1,61 @@
+// 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.
+
+// This is a stub implementation of `dart:developer`.
+
+import "dart:_internal" show patch;
+
+import "dart:async" show Zone;
+
+// Stubs for `developer.dart`.
+
+@patch
+bool debugger({bool when: true, String? message}) => when;
+
+@patch
+Object? inspect(Object? object) => object;
+
+@patch
+void log(String message,
+    {DateTime? time,
+    int? sequenceNumber,
+    int level: 0,
+    String name: '',
+    Zone? zone,
+    Object? error,
+    StackTrace? stackTrace}) {}
+
+@patch
+void _postEvent(String eventKind, String eventData) {}
+
+@patch
+ServiceExtensionHandler? _lookupExtension(String method) => null;
+
+@patch
+_registerExtension(String method, ServiceExtensionHandler handler) {}
+
+// Stubs for `timeline.dart`.
+
+@patch
+bool _isDartStreamEnabled() => false;
+
+@patch
+int _getTraceClock() => _traceClock++;
+
+int _traceClock = 0;
+
+@patch
+int _getNextAsyncId() => 0;
+
+@patch
+void _reportTaskEvent(int taskId, String phase, String category, String name,
+    String argumentsAsJson) {}
+
+@patch
+void _reportFlowEvent(
+    String category, String name, int type, int id, String argumentsAsJson) {}
+
+@patch
+void _reportInstantEvent(
+    String category, String name, String argumentsAsJson) {}
diff --git a/sdk/lib/_internal/wasm/lib/double.dart b/sdk/lib/_internal/wasm/lib/double.dart
new file mode 100644
index 0000000..ea77a86
--- /dev/null
+++ b/sdk/lib/_internal/wasm/lib/double.dart
@@ -0,0 +1,290 @@
+// 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.
+
+// part of "core_patch.dart";
+
+@pragma("wasm:entry-point")
+class _BoxedDouble implements double {
+  // A boxed double contains an unboxed double.
+  @pragma("wasm:entry-point")
+  double value = 0.0;
+
+  static const int _signMask = 0x8000000000000000;
+  static const int _exponentMask = 0x7FF0000000000000;
+  static const int _mantissaMask = 0x000FFFFFFFFFFFFF;
+
+  int get hashCode {
+    int bits = doubleToIntBits(this);
+    if (bits == _signMask) bits = 0; // 0.0 == -0.0
+    return mix64(bits);
+  }
+
+  int get _identityHashCode => hashCode;
+
+  double operator +(num other) => this + other.toDouble(); // Intrinsic +
+  double operator -(num other) => this - other.toDouble(); // Intrinsic -
+  double operator *(num other) => this * other.toDouble(); // Intrinsic *
+  double operator /(num other) => this / other.toDouble(); // Intrinsic /
+
+  int operator ~/(num other) {
+    return _truncDiv(this, other.toDouble());
+  }
+
+  static int _truncDiv(double a, double b) {
+    return (a / b).toInt();
+  }
+
+  double operator %(num other) {
+    return _modulo(this, other.toDouble());
+  }
+
+  static double _modulo(double a, double b) {
+    double rem = a - (a / b).truncateToDouble() * b;
+    if (rem == 0.0) return 0.0;
+    if (rem < 0.0) {
+      if (b < 0.0) {
+        return rem - b;
+      } else {
+        return rem + b;
+      }
+    }
+    return rem;
+  }
+
+  double remainder(num other) {
+    return _remainder(this, other.toDouble());
+  }
+
+  static double _remainder(double a, double b) {
+    return a - (a / b).truncateToDouble() * b;
+  }
+
+  external double operator -();
+
+  bool operator ==(Object other) {
+    return other is double
+        ? this == other // Intrinsic ==
+        : other is int
+            ? this == other.toDouble() // Intrinsic ==
+            : false;
+  }
+
+  bool operator <(num other) => this < other.toDouble(); // Intrinsic <
+  bool operator >(num other) => this > other.toDouble(); // Intrinsic >
+  bool operator >=(num other) => this >= other.toDouble(); // Intrinsic >=
+  bool operator <=(num other) => this <= other.toDouble(); // Intrinsic <=
+
+  bool get isNegative {
+    int bits = doubleToIntBits(this);
+    return (bits & _signMask) != 0;
+  }
+
+  bool get isInfinite {
+    int bits = doubleToIntBits(this);
+    return (bits & _exponentMask) == _exponentMask &&
+        (bits & _mantissaMask) == 0;
+  }
+
+  bool get isNaN {
+    int bits = doubleToIntBits(this);
+    return (bits & _exponentMask) == _exponentMask &&
+        (bits & _mantissaMask) != 0;
+  }
+
+  bool get isFinite {
+    int bits = doubleToIntBits(this);
+    return (bits & _exponentMask) != _exponentMask;
+  }
+
+  double abs() {
+    // Handle negative 0.0.
+    if (this == 0.0) return 0.0;
+    return this < 0.0 ? -this : this;
+  }
+
+  double get sign {
+    if (this > 0.0) return 1.0;
+    if (this < 0.0) return -1.0;
+    return this; // +/-0.0 or NaN.
+  }
+
+  int round() => roundToDouble().toInt();
+  int floor() => floorToDouble().toInt();
+  int ceil() => ceilToDouble().toInt();
+  int truncate() => truncateToDouble().toInt();
+
+  external double roundToDouble();
+  external double floorToDouble();
+  external double ceilToDouble();
+  external double truncateToDouble();
+
+  num clamp(num lowerLimit, num upperLimit) {
+    if (lowerLimit.compareTo(upperLimit) > 0) {
+      throw new ArgumentError(lowerLimit);
+    }
+    if (lowerLimit.isNaN) return lowerLimit;
+    if (this.compareTo(lowerLimit) < 0) return lowerLimit;
+    if (this.compareTo(upperLimit) > 0) return upperLimit;
+    return this;
+  }
+
+  external int toInt();
+
+  double toDouble() {
+    return this;
+  }
+
+  static const int CACHE_SIZE_LOG2 = 3;
+  static const int CACHE_LENGTH = 1 << (CACHE_SIZE_LOG2 + 1);
+  static const int CACHE_MASK = CACHE_LENGTH - 1;
+  // Each key (double) followed by its toString result.
+  static final List _cache = new List.filled(CACHE_LENGTH, null);
+  static int _cacheEvictIndex = 0;
+
+  external String _toString();
+
+  String toString() {
+    // TODO(koda): Consider starting at most recently inserted.
+    for (int i = 0; i < CACHE_LENGTH; i += 2) {
+      // Need 'identical' to handle negative zero, etc.
+      if (identical(_cache[i], this)) {
+        return _cache[i + 1];
+      }
+    }
+    // TODO(koda): Consider optimizing all small integral values.
+    if (identical(0.0, this)) {
+      return "0.0";
+    }
+    String result = _toString();
+    // Replace the least recently inserted entry.
+    _cache[_cacheEvictIndex] = this;
+    _cache[_cacheEvictIndex + 1] = result;
+    _cacheEvictIndex = (_cacheEvictIndex + 2) & CACHE_MASK;
+    return result;
+  }
+
+  String toStringAsFixed(int fractionDigits) {
+    // See ECMAScript-262, 15.7.4.5 for details.
+
+    // Step 2.
+    if (fractionDigits < 0 || fractionDigits > 20) {
+      throw new RangeError.range(fractionDigits, 0, 20, "fractionDigits");
+    }
+
+    // Step 3.
+    double x = this;
+
+    // Step 4.
+    if (isNaN) return "NaN";
+
+    // Step 5 and 6 skipped. Will be dealt with by native function.
+
+    // Step 7.
+    if (x >= 1e21 || x <= -1e21) {
+      return x.toString();
+    }
+
+    return _toStringAsFixed(fractionDigits);
+  }
+
+  external String _toStringAsFixed(int fractionDigits);
+
+  String toStringAsExponential([int? fractionDigits]) {
+    // See ECMAScript-262, 15.7.4.6 for details.
+
+    // The EcmaScript specification checks for NaN and Infinity before looking
+    // at the fractionDigits. In Dart we are consistent with toStringAsFixed and
+    // look at the fractionDigits first.
+
+    // Step 7.
+    if (fractionDigits != null) {
+      if (fractionDigits < 0 || fractionDigits > 20) {
+        throw new RangeError.range(fractionDigits, 0, 20, "fractionDigits");
+      }
+    }
+
+    if (isNaN) return "NaN";
+    if (this == double.infinity) return "Infinity";
+    if (this == -double.infinity) return "-Infinity";
+
+    // The dart function prints the shortest representation when fractionDigits
+    // equals null. The native function wants -1 instead.
+    fractionDigits = (fractionDigits == null) ? -1 : fractionDigits;
+
+    return _toStringAsExponential(fractionDigits);
+  }
+
+  external String _toStringAsExponential(int fractionDigits);
+
+  String toStringAsPrecision(int precision) {
+    // See ECMAScript-262, 15.7.4.7 for details.
+
+    // The EcmaScript specification checks for NaN and Infinity before looking
+    // at the fractionDigits. In Dart we are consistent with toStringAsFixed and
+    // look at the fractionDigits first.
+
+    // Step 8.
+    if (precision < 1 || precision > 21) {
+      throw new RangeError.range(precision, 1, 21, "precision");
+    }
+
+    if (isNaN) return "NaN";
+    if (this == double.infinity) return "Infinity";
+    if (this == -double.infinity) return "-Infinity";
+
+    return _toStringAsPrecision(precision);
+  }
+
+  external String _toStringAsPrecision(int fractionDigits);
+
+  // Order is: NaN > Infinity > ... > 0.0 > -0.0 > ... > -Infinity.
+  int compareTo(num other) {
+    const int EQUAL = 0, LESS = -1, GREATER = 1;
+    if (this < other) {
+      return LESS;
+    } else if (this > other) {
+      return GREATER;
+    } else if (this == other) {
+      if (this == 0.0) {
+        bool thisIsNegative = isNegative;
+        bool otherIsNegative = other.isNegative;
+        if (thisIsNegative == otherIsNegative) {
+          return EQUAL;
+        }
+        return thisIsNegative ? LESS : GREATER;
+      } else if (other is int) {
+        // Compare as integers as it is more precise if the integer value is
+        // outside of MIN_EXACT_INT_TO_DOUBLE..MAX_EXACT_INT_TO_DOUBLE range.
+        const int MAX_EXACT_INT_TO_DOUBLE = 9007199254740992; // 2^53.
+        const int MIN_EXACT_INT_TO_DOUBLE = -MAX_EXACT_INT_TO_DOUBLE;
+        if ((MIN_EXACT_INT_TO_DOUBLE <= other) &&
+            (other <= MAX_EXACT_INT_TO_DOUBLE)) {
+          return EQUAL;
+        }
+        const bool limitIntsTo64Bits = ((1 << 64) == 0);
+        if (limitIntsTo64Bits) {
+          // With integers limited to 64 bits, double.toInt() clamps
+          // double value to fit into the MIN_INT64..MAX_INT64 range.
+          // MAX_INT64 is not precisely representable as double, so
+          // integers near MAX_INT64 compare as equal to (MAX_INT64 + 1) when
+          // represented as doubles.
+          // There is no similar problem with MIN_INT64 as it is precisely
+          // representable as double.
+          const double maxInt64Plus1AsDouble = 9223372036854775808.0;
+          if (this >= maxInt64Plus1AsDouble) {
+            return GREATER;
+          }
+        }
+        return toInt().compareTo(other);
+      } else {
+        return EQUAL;
+      }
+    } else if (isNaN) {
+      return other.isNaN ? EQUAL : GREATER;
+    } else {
+      // Other is NaN.
+      return LESS;
+    }
+  }
+}
diff --git a/sdk/lib/_internal/wasm/lib/expando_patch.dart b/sdk/lib/_internal/wasm/lib/expando_patch.dart
new file mode 100644
index 0000000..2be5189
--- /dev/null
+++ b/sdk/lib/_internal/wasm/lib/expando_patch.dart
@@ -0,0 +1,13 @@
+// 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.
+
+import "dart:_internal" show patch;
+
+// Stub Expando implementation to make the Expando class compile.
+
+@patch
+class Expando<T> {
+  @patch
+  Expando([String? name]) : name = name;
+}
diff --git a/sdk/lib/_internal/wasm/lib/function.dart b/sdk/lib/_internal/wasm/lib/function.dart
new file mode 100644
index 0000000..b698573
--- /dev/null
+++ b/sdk/lib/_internal/wasm/lib/function.dart
@@ -0,0 +1,12 @@
+// 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.
+
+/// Base class for closure objects.
+@pragma("wasm:entry-point")
+class _Function {
+  @pragma("wasm:entry-point")
+  WasmDataRef context;
+
+  _Function._(this.context);
+}
diff --git a/sdk/lib/_internal/wasm/lib/growable_list.dart b/sdk/lib/_internal/wasm/lib/growable_list.dart
new file mode 100644
index 0000000..7b4912b
--- /dev/null
+++ b/sdk/lib/_internal/wasm/lib/growable_list.dart
@@ -0,0 +1,305 @@
+// 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.
+
+// part of "core_patch.dart";
+
+@pragma("wasm:entry-point")
+class _GrowableList<E> extends _ModifiableList<E> {
+  void insert(int index, E element) {
+    if ((index < 0) || (index > length)) {
+      throw new RangeError.range(index, 0, length);
+    }
+    int oldLength = this.length;
+    add(element);
+    if (index == oldLength) {
+      return;
+    }
+    Lists.copy(this, index, this, index + 1, oldLength - index);
+    this[index] = element;
+  }
+
+  E removeAt(int index) {
+    var result = this[index];
+    int newLength = this.length - 1;
+    if (index < newLength) {
+      Lists.copy(this, index + 1, this, index, newLength - index);
+    }
+    this.length = newLength;
+    return result;
+  }
+
+  bool remove(Object? element) {
+    for (int i = 0; i < this.length; i++) {
+      if (this[i] == element) {
+        removeAt(i);
+        return true;
+      }
+    }
+    return false;
+  }
+
+  void insertAll(int index, Iterable<E> iterable) {
+    if (index < 0 || index > length) {
+      throw new RangeError.range(index, 0, length);
+    }
+    if (iterable is! _ListBase) {
+      // Read out all elements before making room to ensure consistency of the
+      // modified list in case the iterator throws.
+      iterable = _List.of(iterable);
+    }
+    int insertionLength = iterable.length;
+    int capacity = _capacity;
+    int newLength = length + insertionLength;
+    if (newLength > capacity) {
+      do {
+        capacity = _nextCapacity(capacity);
+      } while (newLength > capacity);
+      _grow(capacity);
+    }
+    _setLength(newLength);
+    setRange(index + insertionLength, this.length, this, index);
+    setAll(index, iterable);
+  }
+
+  void removeRange(int start, int end) {
+    RangeError.checkValidRange(start, end, this.length);
+    Lists.copy(this, end, this, start, this.length - end);
+    this.length = this.length - (end - start);
+  }
+
+  _GrowableList._(int length, int capacity) : super(length, capacity);
+
+  factory _GrowableList(int length) {
+    return _GrowableList<E>._(length, length);
+  }
+
+  factory _GrowableList.withCapacity(int capacity) {
+    return _GrowableList<E>._(0, capacity);
+  }
+
+  // Specialization of List.empty constructor for growable == true.
+  // Used by pkg/vm/lib/transformations/list_factory_specializer.dart.
+  factory _GrowableList.empty() => _GrowableList(0);
+
+  // Specialization of List.filled constructor for growable == true.
+  // Used by pkg/vm/lib/transformations/list_factory_specializer.dart.
+  factory _GrowableList.filled(int length, E fill) {
+    final result = _GrowableList<E>(length);
+    if (fill != null) {
+      for (int i = 0; i < result.length; i++) {
+        result[i] = fill;
+      }
+    }
+    return result;
+  }
+
+  // Specialization of List.generate constructor for growable == true.
+  // Used by pkg/vm/lib/transformations/list_factory_specializer.dart.
+  factory _GrowableList.generate(int length, E generator(int index)) {
+    final result = _GrowableList<E>(length);
+    for (int i = 0; i < result.length; ++i) {
+      result[i] = generator(i);
+    }
+    return result;
+  }
+
+  // Specialization of List.of constructor for growable == true.
+  factory _GrowableList.of(Iterable<E> elements) {
+    if (elements is _ListBase) {
+      return _GrowableList._ofListBase(unsafeCast(elements));
+    }
+    if (elements is EfficientLengthIterable) {
+      return _GrowableList._ofEfficientLengthIterable(unsafeCast(elements));
+    }
+    return _GrowableList._ofOther(elements);
+  }
+
+  factory _GrowableList._ofListBase(_ListBase<E> elements) {
+    final int length = elements.length;
+    final list = _GrowableList<E>(length);
+    for (int i = 0; i < length; i++) {
+      list[i] = elements[i];
+    }
+    return list;
+  }
+
+  factory _GrowableList._ofEfficientLengthIterable(
+      EfficientLengthIterable<E> elements) {
+    final int length = elements.length;
+    final list = _GrowableList<E>(length);
+    if (length > 0) {
+      int i = 0;
+      for (var element in elements) {
+        list[i++] = element;
+      }
+      if (i != length) throw ConcurrentModificationError(elements);
+    }
+    return list;
+  }
+
+  factory _GrowableList._ofOther(Iterable<E> elements) {
+    final list = _GrowableList<E>(0);
+    for (var elements in elements) {
+      list.add(elements);
+    }
+    return list;
+  }
+
+  _GrowableList._withData(WasmObjectArray<Object?> data)
+      : super._withData(data.length, data);
+
+  int get _capacity => _data.length;
+
+  void set length(int new_length) {
+    if (new_length > length) {
+      // Verify that element type is nullable.
+      null as E;
+      if (new_length > _capacity) {
+        _grow(new_length);
+      }
+      _setLength(new_length);
+      return;
+    }
+    final int new_capacity = new_length;
+    // We are shrinking. Pick the method which has fewer writes.
+    // In the shrink-to-fit path, we write |new_capacity + new_length| words
+    // (null init + copy).
+    // In the non-shrink-to-fit path, we write |length - new_length| words
+    // (null overwrite).
+    final bool shouldShrinkToFit =
+        (new_capacity + new_length) < (length - new_length);
+    if (shouldShrinkToFit) {
+      _shrink(new_capacity, new_length);
+    } else {
+      for (int i = new_length; i < length; i++) {
+        _data.write(i, null);
+      }
+    }
+    _setLength(new_length);
+  }
+
+  void _setLength(int new_length) {
+    _length = new_length;
+  }
+
+  void add(E value) {
+    var len = length;
+    if (len == _capacity) {
+      _growToNextCapacity();
+    }
+    _setLength(len + 1);
+    this[len] = value;
+  }
+
+  void addAll(Iterable<E> iterable) {
+    var len = length;
+    if (iterable is EfficientLengthIterable) {
+      if (identical(iterable, this)) {
+        throw new ConcurrentModificationError(this);
+      }
+      var cap = _capacity;
+      // Pregrow if we know iterable.length.
+      var iterLen = iterable.length;
+      if (iterLen == 0) {
+        return;
+      }
+      var newLen = len + iterLen;
+      if (newLen > cap) {
+        do {
+          cap = _nextCapacity(cap);
+        } while (newLen > cap);
+        _grow(cap);
+      }
+    }
+    Iterator it = iterable.iterator;
+    if (!it.moveNext()) return;
+    do {
+      while (len < _capacity) {
+        int newLen = len + 1;
+        this._setLength(newLen);
+        this[len] = it.current;
+        if (!it.moveNext()) return;
+        if (this.length != newLen) throw new ConcurrentModificationError(this);
+        len = newLen;
+      }
+      _growToNextCapacity();
+    } while (true);
+  }
+
+  E removeLast() {
+    var len = length - 1;
+    var elem = this[len];
+    this.length = len;
+    return elem;
+  }
+
+  // Shared array used as backing for new empty growable lists.
+  static final WasmObjectArray<Object?> _emptyData =
+      WasmObjectArray<Object?>(0);
+
+  static WasmObjectArray<Object?> _allocateData(int capacity) {
+    if (capacity == 0) {
+      // Use shared empty list as backing.
+      return _emptyData;
+    }
+    return new WasmObjectArray<Object?>(capacity);
+  }
+
+  // Grow from 0 to 3, and then double + 1.
+  int _nextCapacity(int old_capacity) => (old_capacity * 2) | 3;
+
+  void _grow(int new_capacity) {
+    var newData = WasmObjectArray<Object?>(new_capacity);
+    for (int i = 0; i < length; i++) {
+      newData.write(i, this[i]);
+    }
+    _data = newData;
+  }
+
+  void _growToNextCapacity() {
+    _grow(_nextCapacity(_capacity));
+  }
+
+  void _shrink(int new_capacity, int new_length) {
+    var newData = _allocateData(new_capacity);
+    for (int i = 0; i < new_length; i++) {
+      newData.write(i, this[i]);
+    }
+    _data = newData;
+  }
+
+  Iterator<E> get iterator {
+    return new _GrowableListIterator<E>(this);
+  }
+}
+
+// Iterator for growable lists.
+class _GrowableListIterator<E> implements Iterator<E> {
+  final _GrowableList<E> _list;
+  final int _length; // Cache list length for modification check.
+  int _index;
+  E? _current;
+
+  _GrowableListIterator(_GrowableList<E> list)
+      : _list = list,
+        _length = list.length,
+        _index = 0 {
+    assert(list is _List<E> || list is _ImmutableList<E>);
+  }
+
+  E get current => _current as E;
+
+  bool moveNext() {
+    if (_list.length != _length) {
+      throw ConcurrentModificationError(_list);
+    }
+    if (_index >= _length) {
+      _current = null;
+      return false;
+    }
+    _current = unsafeCast(_list._data.read(_index));
+    _index++;
+    return true;
+  }
+}
diff --git a/sdk/lib/_internal/wasm/lib/hash_factories.dart b/sdk/lib/_internal/wasm/lib/hash_factories.dart
new file mode 100644
index 0000000..4217e38
--- /dev/null
+++ b/sdk/lib/_internal/wasm/lib/hash_factories.dart
@@ -0,0 +1,51 @@
+// 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.
+
+@patch
+class LinkedHashMap<K, V> {
+  @patch
+  factory LinkedHashMap(
+      {bool equals(K key1, K key2)?,
+      int hashCode(K key)?,
+      bool isValidKey(potentialKey)?}) {
+    if (isValidKey == null) {
+      if (identical(identityHashCode, hashCode) &&
+          identical(identical, equals)) {
+        return new _CompactLinkedIdentityHashMap<K, V>();
+      }
+    }
+    hashCode ??= _defaultHashCode;
+    equals ??= _defaultEquals;
+    return new _CompactLinkedCustomHashMap<K, V>(equals, hashCode, isValidKey);
+  }
+
+  @pragma("wasm:entry-point")
+  factory LinkedHashMap._default() =>
+      _CompactLinkedCustomHashMap<K, V>(_defaultEquals, _defaultHashCode, null);
+
+  @patch
+  factory LinkedHashMap.identity() => new _CompactLinkedIdentityHashMap<K, V>();
+}
+
+@patch
+class LinkedHashSet<E> {
+  @patch
+  factory LinkedHashSet(
+      {bool equals(E e1, E e2)?,
+      int hashCode(E e)?,
+      bool isValidKey(potentialKey)?}) {
+    if (isValidKey == null) {
+      if (identical(identityHashCode, hashCode) &&
+          identical(identical, equals)) {
+        return new _CompactLinkedIdentityHashSet<E>();
+      }
+    }
+    hashCode ??= _defaultHashCode;
+    equals ??= _defaultEquals;
+    return new _CompactLinkedCustomHashSet<E>(equals, hashCode, isValidKey);
+  }
+
+  @patch
+  factory LinkedHashSet.identity() => new _CompactLinkedIdentityHashSet<E>();
+}
diff --git a/sdk/lib/_internal/wasm/lib/identical_patch.dart b/sdk/lib/_internal/wasm/lib/identical_patch.dart
new file mode 100644
index 0000000..614bef6
--- /dev/null
+++ b/sdk/lib/_internal/wasm/lib/identical_patch.dart
@@ -0,0 +1,12 @@
+// 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.
+
+// part of "core_patch.dart";
+
+@patch
+external bool identical(Object? a, Object? b);
+
+@patch
+int identityHashCode(Object? object) =>
+    object == null ? Null._HASH_CODE : object._identityHashCode;
diff --git a/sdk/lib/_internal/wasm/lib/immutable_map.dart b/sdk/lib/_internal/wasm/lib/immutable_map.dart
new file mode 100644
index 0000000..74127f4
--- /dev/null
+++ b/sdk/lib/_internal/wasm/lib/immutable_map.dart
@@ -0,0 +1,221 @@
+// 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.
+
+// part of "core_patch.dart";
+
+/// Immutable map class for compiler generated map literals.
+// TODO(lrn): Extend MapBase with UnmodifiableMapMixin when mixins
+// support forwarding const constructors.
+@pragma("wasm:entry-point")
+class _ImmutableMap<K, V> implements Map<K, V> {
+  final _ImmutableList _kvPairs;
+
+  @pragma("wasm:entry-point")
+  const _ImmutableMap._create(_ImmutableList keyValuePairs)
+      : _kvPairs = keyValuePairs;
+
+  Map<K2, V2> cast<K2, V2>() => Map.castFrom<K, V, K2, V2>(this);
+
+  V? operator [](Object? key) {
+    // To preserve the key-value order of the map literal, the keys are
+    // not sorted. Need to do linear search or implement an additional
+    // lookup table.
+    for (int i = 0; i < _kvPairs.length - 1; i += 2) {
+      if (key == _kvPairs[i]) {
+        return _kvPairs[i + 1];
+      }
+    }
+    return null;
+  }
+
+  bool get isEmpty {
+    return _kvPairs.length == 0;
+  }
+
+  bool get isNotEmpty => !isEmpty;
+
+  int get length {
+    return _kvPairs.length ~/ 2;
+  }
+
+  void forEach(void f(K key, V value)) {
+    for (int i = 0; i < _kvPairs.length; i += 2) {
+      f(_kvPairs[i], _kvPairs[i + 1]);
+    }
+  }
+
+  Iterable<K> get keys {
+    return new _ImmutableMapKeyIterable<K>(this);
+  }
+
+  Iterable<V> get values {
+    return new _ImmutableMapValueIterable<V>(this);
+  }
+
+  bool containsKey(Object? key) {
+    for (int i = 0; i < _kvPairs.length; i += 2) {
+      if (key == _kvPairs[i]) {
+        return true;
+      }
+    }
+    return false;
+  }
+
+  bool containsValue(Object? value) {
+    for (int i = 1; i < _kvPairs.length; i += 2) {
+      if (value == _kvPairs[i]) {
+        return true;
+      }
+    }
+    return false;
+  }
+
+  void operator []=(K key, V value) {
+    throw new UnsupportedError("Cannot set value in unmodifiable Map");
+  }
+
+  void addAll(Map<K, V> other) {
+    throw new UnsupportedError("Cannot set value in unmodifiable Map");
+  }
+
+  V putIfAbsent(K key, V ifAbsent()) {
+    throw new UnsupportedError("Cannot set value in unmodifiable Map");
+  }
+
+  void clear() {
+    throw new UnsupportedError("Cannot clear unmodifiable Map");
+  }
+
+  V? remove(Object? key) {
+    throw new UnsupportedError("Cannot remove from unmodifiable Map");
+  }
+
+  Iterable<MapEntry<K, V>> get entries =>
+      new _ImmutableMapEntryIterable<K, V>(this);
+
+  Map<K2, V2> map<K2, V2>(MapEntry<K2, V2> f(K key, V value)) {
+    var result = <K2, V2>{};
+    for (int i = 0; i < _kvPairs.length; i += 2) {
+      var entry = f(_kvPairs[i], _kvPairs[i + 1]);
+      result[entry.key] = entry.value;
+    }
+    return result;
+  }
+
+  void addEntries(Iterable<MapEntry<K, V>> newEntries) {
+    throw new UnsupportedError("Cannot modify an unmodifiable Map");
+  }
+
+  V update(K key, V update(V value), {V ifAbsent()?}) {
+    throw new UnsupportedError("Cannot modify an unmodifiable Map");
+  }
+
+  void updateAll(V update(K key, V value)) {
+    throw new UnsupportedError("Cannot modify an unmodifiable Map");
+  }
+
+  void removeWhere(bool predicate(K key, V value)) {
+    throw new UnsupportedError("Cannot modify an unmodifiable Map");
+  }
+
+  String toString() => MapBase.mapToString(this);
+}
+
+class _ImmutableMapKeyIterable<E> extends EfficientLengthIterable<E> {
+  final _ImmutableMap _map;
+  _ImmutableMapKeyIterable(this._map);
+
+  Iterator<E> get iterator {
+    return new _ImmutableMapKeyIterator<E>(_map);
+  }
+
+  int get length => _map.length;
+}
+
+class _ImmutableMapValueIterable<E> extends EfficientLengthIterable<E> {
+  final _ImmutableMap _map;
+  _ImmutableMapValueIterable(this._map);
+
+  Iterator<E> get iterator {
+    return new _ImmutableMapValueIterator<E>(_map);
+  }
+
+  int get length => _map.length;
+}
+
+class _ImmutableMapEntryIterable<K, V>
+    extends EfficientLengthIterable<MapEntry<K, V>> {
+  final _ImmutableMap _map;
+  _ImmutableMapEntryIterable(this._map);
+
+  Iterator<MapEntry<K, V>> get iterator {
+    return new _ImmutableMapEntryIterator<K, V>(_map);
+  }
+
+  int get length => _map.length;
+}
+
+class _ImmutableMapKeyIterator<E> implements Iterator<E> {
+  _ImmutableMap _map;
+  int _nextIndex = 0;
+  E? _current;
+
+  _ImmutableMapKeyIterator(this._map);
+
+  bool moveNext() {
+    int newIndex = _nextIndex;
+    if (newIndex < _map.length) {
+      _nextIndex = newIndex + 1;
+      _current = _map._kvPairs[newIndex * 2];
+      return true;
+    }
+    _current = null;
+    return false;
+  }
+
+  E get current => _current as E;
+}
+
+class _ImmutableMapValueIterator<E> implements Iterator<E> {
+  _ImmutableMap _map;
+  int _nextIndex = 0;
+  E? _current;
+
+  _ImmutableMapValueIterator(this._map);
+
+  bool moveNext() {
+    int newIndex = _nextIndex;
+    if (newIndex < _map.length) {
+      _nextIndex = newIndex + 1;
+      _current = _map._kvPairs[newIndex * 2 + 1];
+      return true;
+    }
+    _current = null;
+    return false;
+  }
+
+  E get current => _current as E;
+}
+
+class _ImmutableMapEntryIterator<K, V> implements Iterator<MapEntry<K, V>> {
+  _ImmutableMap _map;
+  int _nextIndex = 0;
+  MapEntry<K, V>? _current;
+
+  _ImmutableMapEntryIterator(this._map);
+
+  bool moveNext() {
+    int newIndex = _nextIndex;
+    if (newIndex < _map.length) {
+      _nextIndex = newIndex + 1;
+      _current = new MapEntry<K, V>(
+          _map._kvPairs[newIndex * 2], _map._kvPairs[newIndex * 2 + 1]);
+      return true;
+    }
+    _current = null;
+    return false;
+  }
+
+  MapEntry<K, V> get current => _current as MapEntry<K, V>;
+}
diff --git a/sdk/lib/_internal/wasm/lib/int.dart b/sdk/lib/_internal/wasm/lib/int.dart
new file mode 100644
index 0000000..e41f186
--- /dev/null
+++ b/sdk/lib/_internal/wasm/lib/int.dart
@@ -0,0 +1,610 @@
+// 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.
+
+// part of "core_patch.dart";
+
+@pragma("wasm:entry-point")
+class _BoxedInt implements int {
+  // A boxed int contains an unboxed int.
+  @pragma("wasm:entry-point")
+  int value = 0;
+
+  external num operator +(num other);
+  external num operator -(num other);
+  external num operator *(num other);
+
+  double operator /(num other) {
+    return this.toDouble() / other.toDouble();
+  }
+
+  int operator ~/(num other) => other is int
+      ? this ~/ other
+      : _BoxedDouble._truncDiv(toDouble(), unsafeCast<double>(other));
+
+  num operator %(num other) => other is int
+      ? _modulo(this, other)
+      : _BoxedDouble._modulo(toDouble(), unsafeCast<double>(other));
+
+  static int _modulo(int a, int b) {
+    int rem = a - (a ~/ b) * b;
+    if (rem < 0) {
+      if (b < 0) {
+        return rem - b;
+      } else {
+        return rem + b;
+      }
+    }
+    return rem;
+  }
+
+  num remainder(num other) => other is int
+      ? this - (this ~/ other) * other
+      : _BoxedDouble._remainder(toDouble(), unsafeCast<double>(other));
+
+  external int operator -();
+
+  external int operator &(int other);
+  external int operator |(int other);
+  external int operator ^(int other);
+
+  external int operator >>(int other);
+  external int operator >>>(int other);
+  external int operator <<(int other);
+
+  external bool operator <(num other);
+  external bool operator >(num other);
+  external bool operator >=(num other);
+  external bool operator <=(num other);
+
+  bool operator ==(Object other) {
+    return other is int
+        ? this == other // Intrinsic ==
+        : other is double
+            ? this.toDouble() == other // Intrinsic ==
+            : false;
+  }
+
+  int abs() {
+    return this < 0 ? -this : this;
+  }
+
+  int get sign {
+    return (this > 0)
+        ? 1
+        : (this < 0)
+            ? -1
+            : 0;
+  }
+
+  bool get isEven => (this & 1) == 0;
+  bool get isOdd => (this & 1) != 0;
+  bool get isNaN => false;
+  bool get isNegative => this < 0;
+  bool get isInfinite => false;
+  bool get isFinite => true;
+
+  int toUnsigned(int width) {
+    return this & ((1 << width) - 1);
+  }
+
+  int toSigned(int width) {
+    // The value of binary number weights each bit by a power of two.  The
+    // twos-complement value weights the sign bit negatively.  We compute the
+    // value of the negative weighting by isolating the sign bit with the
+    // correct power of two weighting and subtracting it from the value of the
+    // lower bits.
+    int signMask = 1 << (width - 1);
+    return (this & (signMask - 1)) - (this & signMask);
+  }
+
+  int compareTo(num other) {
+    const int EQUAL = 0, LESS = -1, GREATER = 1;
+    if (other is double) {
+      const int MAX_EXACT_INT_TO_DOUBLE = 9007199254740992; // 2^53.
+      const int MIN_EXACT_INT_TO_DOUBLE = -MAX_EXACT_INT_TO_DOUBLE;
+      // With int limited to 64 bits, double.toInt() clamps
+      // double value to fit into the MIN_INT64..MAX_INT64 range.
+      // Check if the double value is outside of this range.
+      // This check handles +/-infinity as well.
+      const double minInt64AsDouble = -9223372036854775808.0;
+      // MAX_INT64 is not precisely representable in doubles, so
+      // check against (MAX_INT64 + 1).
+      const double maxInt64Plus1AsDouble = 9223372036854775808.0;
+      if (other < minInt64AsDouble) {
+        return GREATER;
+      } else if (other >= maxInt64Plus1AsDouble) {
+        return LESS;
+      }
+      if (other.isNaN) {
+        return LESS;
+      }
+      if (MIN_EXACT_INT_TO_DOUBLE <= this && this <= MAX_EXACT_INT_TO_DOUBLE) {
+        // Let the double implementation deal with -0.0.
+        return -(other.compareTo(this.toDouble()));
+      } else {
+        // If abs(other) > MAX_EXACT_INT_TO_DOUBLE, then other has an integer
+        // value (no bits below the decimal point).
+        other = other.toInt();
+      }
+    }
+    if (this < other) {
+      return LESS;
+    } else if (this > other) {
+      return GREATER;
+    } else {
+      return EQUAL;
+    }
+  }
+
+  int round() {
+    return this;
+  }
+
+  int floor() {
+    return this;
+  }
+
+  int ceil() {
+    return this;
+  }
+
+  int truncate() {
+    return this;
+  }
+
+  double roundToDouble() {
+    return this.toDouble();
+  }
+
+  double floorToDouble() {
+    return this.toDouble();
+  }
+
+  double ceilToDouble() {
+    return this.toDouble();
+  }
+
+  double truncateToDouble() {
+    return this.toDouble();
+  }
+
+  num clamp(num lowerLimit, num upperLimit) {
+    // Special case for integers.
+    if (lowerLimit is int && upperLimit is int && lowerLimit <= upperLimit) {
+      if (this < lowerLimit) return lowerLimit;
+      if (this > upperLimit) return upperLimit;
+      return this;
+    }
+    // Generic case involving doubles, and invalid integer ranges.
+    if (lowerLimit.compareTo(upperLimit) > 0) {
+      throw new ArgumentError(lowerLimit);
+    }
+    if (lowerLimit.isNaN) return lowerLimit;
+    // Note that we don't need to care for -0.0 for the lower limit.
+    if (this < lowerLimit) return lowerLimit;
+    if (this.compareTo(upperLimit) > 0) return upperLimit;
+    return this;
+  }
+
+  int toInt() {
+    return this;
+  }
+
+  external double toDouble();
+
+  String toStringAsFixed(int fractionDigits) {
+    return this.toDouble().toStringAsFixed(fractionDigits);
+  }
+
+  String toStringAsExponential([int? fractionDigits]) {
+    return this.toDouble().toStringAsExponential(fractionDigits);
+  }
+
+  String toStringAsPrecision(int precision) {
+    return this.toDouble().toStringAsPrecision(precision);
+  }
+
+  static const _digits = "0123456789abcdefghijklmnopqrstuvwxyz";
+
+  String toRadixString(int radix) {
+    if (radix < 2 || 36 < radix) {
+      throw new RangeError.range(radix, 2, 36, "radix");
+    }
+    if (radix & (radix - 1) == 0) {
+      return _toPow2String(radix);
+    }
+    if (radix == 10) return this.toString();
+    final bool isNegative = this < 0;
+    int value = isNegative ? -this : this;
+    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(radix);
+    }
+    var temp = <int>[];
+    do {
+      int digit = value % radix;
+      value ~/= radix;
+      temp.add(_digits.codeUnitAt(digit));
+    } while (value > 0);
+    if (isNegative) temp.add(0x2d); // '-'.
+
+    _OneByteString string = _OneByteString._allocate(temp.length);
+    for (int i = 0, j = temp.length; j > 0; i++) {
+      string._setAt(i, temp[--j]);
+    }
+    return string;
+  }
+
+  String _toPow2String(int radix) {
+    int value = this;
+    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(radix);
+      }
+    }
+    // Integer division, rounding up, to find number of _digits.
+    length += (value.bitLength + bitsPerDigit - 1) ~/ bitsPerDigit;
+    _OneByteString string = _OneByteString._allocate(length);
+    string._setAt(0, 0x2d); // '-'. Is overwritten if not negative.
+    var mask = radix - 1;
+    do {
+      string._setAt(--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 radix) {
+    var temp = <int>[];
+    int value = this;
+    assert(value < 0);
+    do {
+      int digit = -unsafeCast<int>(value.remainder(radix));
+      value ~/= radix;
+      temp.add(_digits.codeUnitAt(digit));
+    } while (value != 0);
+    temp.add(0x2d); // '-'.
+
+    _OneByteString string = _OneByteString._allocate(temp.length);
+    for (int i = 0, j = temp.length; j > 0; i++) {
+      string._setAt(i, temp[--j]);
+    }
+    return string;
+  }
+
+  // Returns pow(this, e) % m.
+  int modPow(int e, int m) {
+    if (e < 0) throw new RangeError.range(e, 0, null, "exponent");
+    if (m <= 0) throw new RangeError.range(m, 1, null, "modulus");
+    if (e == 0) return 1;
+
+    // This is floor(sqrt(2^63)).
+    const int maxValueThatCanBeSquaredWithoutTruncation = 3037000499;
+    if (m > maxValueThatCanBeSquaredWithoutTruncation) {
+      // Use BigInt version to avoid truncation in multiplications below.
+      return BigInt.from(this).modPow(BigInt.from(e), BigInt.from(m)).toInt();
+    }
+
+    int b = this;
+    if (b < 0 || b > m) {
+      b %= m;
+    }
+    int r = 1;
+    while (e > 0) {
+      if (e.isOdd) {
+        r = (r * b) % m;
+      }
+      e >>= 1;
+      b = (b * b) % m;
+    }
+    return r;
+  }
+
+  // If inv is false, returns gcd(x, y).
+  // If inv is true and gcd(x, y) = 1, returns d, so that c*x + d*y = 1.
+  // If inv is true and gcd(x, y) != 1, throws Exception("Not coprime").
+  static int _binaryGcd(int x, int y, bool inv) {
+    int s = 0;
+    if (!inv) {
+      while (x.isEven && y.isEven) {
+        x >>= 1;
+        y >>= 1;
+        s++;
+      }
+      if (y.isOdd) {
+        var t = x;
+        x = y;
+        y = t;
+      }
+    }
+    final bool ac = x.isEven;
+    int u = x;
+    int v = y;
+    int a = 1, b = 0, c = 0, d = 1;
+    do {
+      while (u.isEven) {
+        u >>= 1;
+        if (ac) {
+          if (!a.isEven || !b.isEven) {
+            a += y;
+            b -= x;
+          }
+          a >>= 1;
+        } else if (!b.isEven) {
+          b -= x;
+        }
+        b >>= 1;
+      }
+      while (v.isEven) {
+        v >>= 1;
+        if (ac) {
+          if (!c.isEven || !d.isEven) {
+            c += y;
+            d -= x;
+          }
+          c >>= 1;
+        } else if (!d.isEven) {
+          d -= x;
+        }
+        d >>= 1;
+      }
+      if (u >= v) {
+        u -= v;
+        if (ac) a -= c;
+        b -= d;
+      } else {
+        v -= u;
+        if (ac) c -= a;
+        d -= b;
+      }
+    } while (u != 0);
+    if (!inv) return v << s;
+    if (v != 1) {
+      throw new Exception("Not coprime");
+    }
+    if (d < 0) {
+      d += x;
+      if (d < 0) d += x;
+    } else if (d > x) {
+      d -= x;
+      if (d > x) d -= x;
+    }
+    return d;
+  }
+
+  // Returns 1/this % m, with m > 0.
+  int modInverse(int m) {
+    if (m <= 0) throw new RangeError.range(m, 1, null, "modulus");
+    if (m == 1) return 0;
+    int t = this;
+    if ((t < 0) || (t >= m)) t %= m;
+    if (t == 1) return 1;
+    if ((t == 0) || (t.isEven && m.isEven)) {
+      throw new Exception("Not coprime");
+    }
+    return _binaryGcd(m, t, true);
+  }
+
+  // Returns gcd of abs(this) and abs(other).
+  int gcd(int other) {
+    int x = this.abs();
+    int y = other.abs();
+    if (x == 0) return y;
+    if (y == 0) return x;
+    if ((x == 1) || (y == 1)) return 1;
+    return _binaryGcd(x, y, false);
+  }
+
+  int get hashCode => this;
+  int get _identityHashCode => this;
+
+  external int operator ~();
+  external int get bitLength;
+
+  /**
+   * 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]`.
+   */
+  static 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.
+   */
+  static 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.
+  static const int _POW_10_7 = 10000000;
+  static const int _POW_10_8 = 100000000;
+  static 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.
+  static 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 toString() {
+    if (this < 100 && this > -100) {
+      // Issue(https://dartbug.com/39639): The analyzer incorrectly reports the
+      // result type as `num`.
+      return _smallLookupTable[unsafeCast<int>(this + 99)];
+    }
+    if (this < 0) return _negativeToString(this);
+    // 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(this);
+    _OneByteString result = _OneByteString._allocate(length);
+    int index = length - 1;
+    int smi = this;
+    do {
+      // Two digits at a time.
+      final int twoDigits = smi.remainder(100);
+      smi = smi ~/ 100;
+      int digitIndex = twoDigits * 2;
+      result._setAt(index, _digitTable[digitIndex + 1]);
+      result._setAt(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`.
+      result._setAt(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;
+      result._setAt(index, _digitTable[digitIndex + 1]);
+      result._setAt(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.
+  static 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.
+  static String _negativeToString(int negSmi) {
+    // Character code for '-'
+    const int MINUS_SIGN = 0x2d;
+    // Character code for '0'.
+    const int DIGIT_ZERO = 0x30;
+    if (negSmi > -10) {
+      return _OneByteString._allocate(2)
+        .._setAt(0, MINUS_SIGN)
+        .._setAt(1, DIGIT_ZERO - negSmi);
+    }
+    if (negSmi > -100) {
+      int digitIndex = 2 * -negSmi;
+      return _OneByteString._allocate(3)
+        .._setAt(0, MINUS_SIGN)
+        .._setAt(1, _digitTable[digitIndex])
+        .._setAt(2, _digitTable[digitIndex + 1]);
+    }
+    // Number of digits, not including minus.
+    int digitCount = _negativeBase10Length(negSmi);
+    _OneByteString result = _OneByteString._allocate(digitCount + 1);
+    result._setAt(0, MINUS_SIGN); // '-'.
+    int index = digitCount;
+    do {
+      int twoDigits = unsafeCast<int>(negSmi.remainder(100));
+      negSmi = negSmi ~/ 100;
+      int digitIndex = -twoDigits * 2;
+      result._setAt(index, _digitTable[digitIndex + 1]);
+      result._setAt(index - 1, _digitTable[digitIndex]);
+      index -= 2;
+    } while (negSmi <= -100);
+    if (negSmi > -10) {
+      result._setAt(index, DIGIT_ZERO - negSmi);
+    } else {
+      // No remainder necessary for this case.
+      int digitIndex = -negSmi * 2;
+      result._setAt(index, _digitTable[digitIndex + 1]);
+      result._setAt(index - 1, _digitTable[digitIndex]);
+    }
+    return result;
+  }
+}
diff --git a/sdk/lib/_internal/wasm/lib/internal_patch.dart b/sdk/lib/_internal/wasm/lib/internal_patch.dart
new file mode 100644
index 0000000..3cd8846
--- /dev/null
+++ b/sdk/lib/_internal/wasm/lib/internal_patch.dart
@@ -0,0 +1,128 @@
+// 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.
+
+import "dart:typed_data" show Uint8List;
+
+/// The returned string is a [_OneByteString] with uninitialized content.
+external String allocateOneByteString(int length);
+
+/// The [string] must be a [_OneByteString]. The [index] must be valid.
+external void writeIntoOneByteString(String string, int index, int codePoint);
+
+/// It is assumed that [from] is a native [Uint8List] class and [to] is a
+/// [_OneByteString]. The [fromStart] and [toStart] indices together with the
+/// [length] must specify ranges within the bounds of the list / string.
+void copyRangeFromUint8ListToOneByteString(
+    Uint8List from, String to, int fromStart, int toStart, int length) {
+  for (int i = 0; i < length; i++) {
+    writeIntoOneByteString(to, toStart + i, from[fromStart + i]);
+  }
+}
+
+/// The returned string is a [_TwoByteString] with uninitialized content.
+external String allocateTwoByteString(int length);
+
+/// The [string] must be a [_TwoByteString]. The [index] must be valid.
+external void writeIntoTwoByteString(String string, int index, int codePoint);
+
+// 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")
+String _stringAllocate1(double length) {
+  return allocateOneByteString(length.toInt());
+}
+
+@pragma("wasm:export", "\$stringWrite1")
+void _stringWrite1(String string, double index, double codePoint) {
+  writeIntoOneByteString(string, index.toInt(), codePoint.toInt());
+}
+
+@pragma("wasm:export", "\$stringAllocate2")
+String _stringAllocate2(double length) {
+  return allocateTwoByteString(length.toInt());
+}
+
+@pragma("wasm:export", "\$stringWrite2")
+void _stringWrite2(String string, double index, double codePoint) {
+  writeIntoTwoByteString(string, index.toInt(), codePoint.toInt());
+}
+
+const bool has63BitSmis = false;
+
+class Lists {
+  static void copy(List src, int srcStart, List dst, int dstStart, int count) {
+    // TODO(askesc): Intrinsify for efficient copying
+    if (srcStart < dstStart) {
+      for (int i = srcStart + count - 1, j = dstStart + count - 1;
+          i >= srcStart;
+          i--, j--) {
+        dst[j] = src[i];
+      }
+    } else {
+      for (int i = srcStart, j = dstStart; i < srcStart + count; i++, j++) {
+        dst[j] = src[i];
+      }
+    }
+  }
+}
+
+// This function can be used to skip implicit or explicit checked down casts in
+// the parts of the core library implementation where we know by construction
+// the type of a value.
+//
+// Important: this is unsafe and must be used with care.
+external T unsafeCast<T>(Object? v);
+
+// Thomas Wang 64-bit mix.
+// https://gist.github.com/badboy/6267743
+int mix64(int n) {
+  n = (~n) + (n << 21); // n = (n << 21) - n - 1;
+  n = n ^ (n >>> 24);
+  n = n * 265; // n = (n + (n << 3)) + (n << 8);
+  n = n ^ (n >>> 14);
+  n = n * 21; // n = (n + (n << 2)) + (n << 4);
+  n = n ^ (n >>> 28);
+  n = n + (n << 31);
+  return n;
+}
+
+external int floatToIntBits(double value);
+external double intBitsToFloat(int value);
+external int doubleToIntBits(double value);
+external double intBitsToDouble(int value);
+
+// Exported call stubs to enable JS to call Dart closures. Since all closure
+// parameters and returns are boxed (their Wasm type is #Top) the Wasm type of
+// the closure will be the same as with all parameters and returns as dynamic.
+// Thus, the unsafeCast succeeds, and as long as the passed argumnets have the
+// correct types, the argument casts inside the closure will also succeed.
+
+@pragma("wasm:export", "\$call0")
+dynamic _callClosure0(dynamic closure) {
+  return unsafeCast<dynamic Function()>(closure)();
+}
+
+@pragma("wasm:export", "\$call1")
+dynamic _callClosure1(dynamic closure, dynamic arg1) {
+  return unsafeCast<dynamic Function(dynamic)>(closure)(arg1);
+}
+
+@pragma("wasm:export", "\$call2")
+dynamic _callClosure2(dynamic closure, dynamic arg1, dynamic arg2) {
+  return unsafeCast<dynamic Function(dynamic, dynamic)>(closure)(arg1, arg2);
+}
+
+// Schedule a callback from JS via setTimeout.
+@pragma("wasm:import", "dart2wasm.scheduleCallback")
+external void scheduleCallback(double millis, dynamic Function() callback);
diff --git a/sdk/lib/_internal/wasm/lib/list.dart b/sdk/lib/_internal/wasm/lib/list.dart
new file mode 100644
index 0000000..13eeff3
--- /dev/null
+++ b/sdk/lib/_internal/wasm/lib/list.dart
@@ -0,0 +1,223 @@
+// 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.
+
+// part of "core_patch.dart";
+
+@pragma("wasm:entry-point")
+abstract class _ListBase<E> extends ListBase<E> {
+  @pragma("wasm:entry-point")
+  int _length;
+  @pragma("wasm:entry-point")
+  WasmObjectArray<Object?> _data;
+
+  _ListBase(int length, int capacity)
+      : _length = length,
+        _data = WasmObjectArray<Object?>(capacity);
+
+  _ListBase._withData(this._length, this._data);
+
+  E operator [](int index) {
+    return unsafeCast(_data.read(index));
+  }
+
+  int get length => _length;
+
+  List<E> sublist(int start, [int? end]) {
+    final int listLength = this.length;
+    final int actualEnd = RangeError.checkValidRange(start, end, listLength);
+    int length = actualEnd - start;
+    if (length == 0) return <E>[];
+    return _GrowableList<E>(length)..setRange(0, length, this);
+  }
+
+  void forEach(f(E element)) {
+    final length = this.length;
+    for (int i = 0; i < length; i++) {
+      f(this[i]);
+    }
+  }
+
+  List<E> toList({bool growable: true}) {
+    return List.from(this, growable: growable);
+  }
+}
+
+@pragma("wasm:entry-point")
+abstract class _ModifiableList<E> extends _ListBase<E> {
+  _ModifiableList(int length, int capacity) : super(length, capacity);
+
+  _ModifiableList._withData(int length, WasmObjectArray<Object?> data)
+      : super._withData(length, data);
+
+  void operator []=(int index, E value) {
+    _data.write(index, value);
+  }
+
+  // List interface.
+  void setRange(int start, int end, Iterable<E> iterable, [int skipCount = 0]) {
+    if (start < 0 || start > this.length) {
+      throw new RangeError.range(start, 0, this.length);
+    }
+    if (end < start || end > this.length) {
+      throw new RangeError.range(end, start, this.length);
+    }
+    int length = end - start;
+    if (length == 0) return;
+    if (identical(this, iterable)) {
+      Lists.copy(this, skipCount, this, start, length);
+    } else if (iterable is List<E>) {
+      Lists.copy(iterable, skipCount, this, start, length);
+    } else {
+      Iterator<E> it = iterable.iterator;
+      while (skipCount > 0) {
+        if (!it.moveNext()) return;
+        skipCount--;
+      }
+      for (int i = start; i < end; i++) {
+        if (!it.moveNext()) return;
+        this[i] = it.current;
+      }
+    }
+  }
+
+  void setAll(int index, Iterable<E> iterable) {
+    if (index < 0 || index > this.length) {
+      throw new RangeError.range(index, 0, this.length, "index");
+    }
+    List<E> iterableAsList;
+    if (identical(this, iterable)) {
+      iterableAsList = this;
+    } else if (iterable is List<E>) {
+      iterableAsList = iterable;
+    } else {
+      for (var value in iterable) {
+        this[index++] = value;
+      }
+      return;
+    }
+    int length = iterableAsList.length;
+    if (index + length > this.length) {
+      throw new RangeError.range(index + length, 0, this.length);
+    }
+    Lists.copy(iterableAsList, 0, this, index, length);
+  }
+}
+
+@pragma("wasm:entry-point")
+class _List<E> extends _ModifiableList<E> with FixedLengthListMixin<E> {
+  _List._(int length) : super(length, length);
+
+  factory _List(int length) => _List._(length);
+
+  // Specialization of List.empty constructor for growable == false.
+  // Used by pkg/vm/lib/transformations/list_factory_specializer.dart.
+  factory _List.empty() => _List<E>(0);
+
+  // Specialization of List.filled constructor for growable == false.
+  // Used by pkg/vm/lib/transformations/list_factory_specializer.dart.
+  factory _List.filled(int length, E fill) {
+    final result = _List<E>(length);
+    if (fill != null) {
+      for (int i = 0; i < result.length; i++) {
+        result[i] = fill;
+      }
+    }
+    return result;
+  }
+
+  // Specialization of List.generate constructor for growable == false.
+  // Used by pkg/vm/lib/transformations/list_factory_specializer.dart.
+  factory _List.generate(int length, E generator(int index)) {
+    final result = _List<E>(length);
+    for (int i = 0; i < result.length; ++i) {
+      result[i] = generator(i);
+    }
+    return result;
+  }
+
+  // Specialization of List.of constructor for growable == false.
+  factory _List.of(Iterable<E> elements) {
+    if (elements is _ListBase) {
+      return _List._ofListBase(unsafeCast(elements));
+    }
+    if (elements is EfficientLengthIterable) {
+      return _List._ofEfficientLengthIterable(unsafeCast(elements));
+    }
+    return _List._ofOther(elements);
+  }
+
+  factory _List._ofListBase(_ListBase<E> elements) {
+    final int length = elements.length;
+    final list = _List<E>(length);
+    for (int i = 0; i < length; i++) {
+      list[i] = elements[i];
+    }
+    return list;
+  }
+
+  factory _List._ofEfficientLengthIterable(
+      EfficientLengthIterable<E> elements) {
+    final int length = elements.length;
+    final list = _List<E>(length);
+    if (length > 0) {
+      int i = 0;
+      for (var element in elements) {
+        list[i++] = element;
+      }
+      if (i != length) throw ConcurrentModificationError(elements);
+    }
+    return list;
+  }
+
+  factory _List._ofOther(Iterable<E> elements) {
+    // The static type of `makeListFixedLength` is `List<E>`, not `_List<E>`,
+    // but we know that is what it does.  `makeListFixedLength` is too generally
+    // typed since it is available on the web platform which has different
+    // system List types.
+    return unsafeCast(makeListFixedLength(_GrowableList<E>._ofOther(elements)));
+  }
+
+  Iterator<E> get iterator {
+    return new _FixedSizeListIterator<E>(this);
+  }
+}
+
+@pragma("wasm:entry-point")
+class _ImmutableList<E> extends _ListBase<E> with UnmodifiableListMixin<E> {
+  factory _ImmutableList._uninstantiable() {
+    throw new UnsupportedError(
+        "_ImmutableList can only be allocated by the runtime");
+  }
+
+  Iterator<E> get iterator {
+    return new _FixedSizeListIterator<E>(this);
+  }
+}
+
+// Iterator for lists with fixed size.
+class _FixedSizeListIterator<E> implements Iterator<E> {
+  final _ListBase<E> _list;
+  final int _length; // Cache list length for faster access.
+  int _index;
+  E? _current;
+
+  _FixedSizeListIterator(_ListBase<E> list)
+      : _list = list,
+        _length = list.length,
+        _index = 0 {
+    assert(list is _List<E> || list is _ImmutableList<E>);
+  }
+
+  E get current => _current as E;
+
+  bool moveNext() {
+    if (_index >= _length) {
+      _current = null;
+      return false;
+    }
+    _current = unsafeCast(_list._data.read(_index));
+    _index++;
+    return true;
+  }
+}
diff --git a/sdk/lib/_internal/wasm/lib/math_patch.dart b/sdk/lib/_internal/wasm/lib/math_patch.dart
new file mode 100644
index 0000000..b26fd6d
--- /dev/null
+++ b/sdk/lib/_internal/wasm/lib/math_patch.dart
@@ -0,0 +1,262 @@
+// 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.
+
+import "dart:_internal" show mix64, patch;
+
+import "dart:typed_data" show Uint32List;
+
+/// There are no parts of this patch library.
+
+@patch
+T min<T extends num>(T a, T b) {
+  if (a > b) return b;
+  if (a < b) return a;
+  if (b is double) {
+    // Special case for NaN and -0.0. If one argument is NaN return NaN.
+    // [min] must also distinguish between -0.0 and 0.0.
+    if (a is double) {
+      if (a == 0.0) {
+        // a is either 0.0 or -0.0. b is either 0.0, -0.0 or NaN.
+        // The following returns -0.0 if either a or b is -0.0, and it
+        // returns NaN if b is NaN.
+        num n = (a + b) * a * b;
+        return n as T;
+      }
+    }
+    // Check for NaN and b == -0.0.
+    if (a == 0 && b.isNegative || b.isNaN) return b;
+    return a;
+  }
+  return a;
+}
+
+@patch
+T max<T extends num>(T a, T b) {
+  if (a > b) return a;
+  if (a < b) return b;
+  if (b is double) {
+    // Special case for NaN and -0.0. If one argument is NaN return NaN.
+    // [max] must also distinguish between -0.0 and 0.0.
+    if (a is double) {
+      if (a == 0.0) {
+        // a is either 0.0 or -0.0. b is either 0.0, -0.0, or NaN.
+        // The following returns 0.0 if either a or b is 0.0, and it
+        // returns NaN if b is NaN.
+        num n = a + b;
+        return n as T;
+      }
+    }
+    // Check for NaN.
+    if (b.isNaN) return b;
+    return a;
+  }
+  // max(-0.0, 0) must return 0.
+  if (b == 0 && a.isNegative) return b;
+  return a;
+}
+
+// If [x] is an [int] and [exponent] is a non-negative [int], the result is
+// an [int], otherwise the result is a [double].
+@patch
+num pow(num x, num exponent) {
+  if ((x is int) && (exponent is int) && (exponent >= 0)) {
+    return _intPow(x, exponent);
+  }
+  return _doublePow(x.toDouble(), exponent.toDouble());
+}
+
+@pragma("wasm:import", "Math.pow")
+external double _doublePow(double base, double exponent);
+
+int _intPow(int base, int exponent) {
+  // Exponentiation by squaring.
+  int result = 1;
+  while (exponent != 0) {
+    if ((exponent & 1) == 1) {
+      result *= base;
+    }
+    exponent >>= 1;
+    // Skip unnecessary operation (can overflow to Mint).
+    if (exponent != 0) {
+      base *= base;
+    }
+  }
+  return result;
+}
+
+@patch
+double atan2(num a, num b) => _atan2(a.toDouble(), b.toDouble());
+@patch
+double sin(num radians) => _sin(radians.toDouble());
+@patch
+double cos(num radians) => _cos(radians.toDouble());
+@patch
+double tan(num radians) => _tan(radians.toDouble());
+@patch
+double acos(num x) => _acos(x.toDouble());
+@patch
+double asin(num x) => _asin(x.toDouble());
+@patch
+double atan(num x) => _atan(x.toDouble());
+@patch
+double sqrt(num x) => _sqrt(x.toDouble());
+@patch
+double exp(num x) => _exp(x.toDouble());
+@patch
+double log(num x) => _log(x.toDouble());
+
+@pragma("wasm:import", "Math.atan2")
+external double _atan2(double a, double b);
+@pragma("wasm:import", "Math.sin")
+external double _sin(double x);
+@pragma("wasm:import", "Math.cos")
+external double _cos(double x);
+@pragma("wasm:import", "Math.tan")
+external double _tan(double x);
+@pragma("wasm:import", "Math.acos")
+external double _acos(double x);
+@pragma("wasm:import", "Math.asin")
+external double _asin(double x);
+@pragma("wasm:import", "Math.atan")
+external double _atan(double x);
+@pragma("wasm:import", "Math.sqrt")
+external double _sqrt(double x);
+@pragma("wasm:import", "Math.exp")
+external double _exp(double x);
+@pragma("wasm:import", "Math.log")
+external double _log(double x);
+
+// TODO(iposva): Handle patch methods within a patch class correctly.
+@patch
+class Random {
+  static final Random _secureRandom = _SecureRandom();
+
+  @patch
+  factory Random([int? seed]) {
+    var state = _Random._setupSeed((seed == null) ? _Random._nextSeed() : seed);
+    // Crank a couple of times to distribute the seed bits a bit further.
+    return new _Random._withState(state)
+      .._nextState()
+      .._nextState()
+      .._nextState()
+      .._nextState();
+  }
+
+  @patch
+  factory Random.secure() => _secureRandom;
+}
+
+class _Random implements Random {
+  // Internal state of the random number generator.
+  int _state;
+
+  int get _stateLow => _state & 0xFFFFFFFF;
+  int get _stateHigh => _state >>> 32;
+
+  _Random._withState(this._state);
+
+  // The algorithm used here is Multiply with Carry (MWC) with a Base b = 2^32.
+  // http://en.wikipedia.org/wiki/Multiply-with-carry
+  // The constant A is selected from "Numerical Recipes 3rd Edition" p.348 B1.
+
+  // Implements:
+  //   const _A = 0xffffda61;
+  //   var state =
+  //       ((_A * (_state[_kSTATE_LO])) + _state[_kSTATE_HI]) & ((1 << 64) - 1);
+  //   _state[_kSTATE_LO] = state & ((1 << 32) - 1);
+  //   _state[_kSTATE_HI] = state >> 32;
+  // This is a native to prevent 64-bit operations in Dart, which
+  // fail with --throw_on_javascript_int_overflow.
+  // TODO(regis): Implement in Dart and remove Random_nextState in math.cc.
+  void _nextState() {
+    const _A = 0xffffda61;
+    _state = _A * _stateLow + _stateHigh;
+  }
+
+  int nextInt(int max) {
+    if (max <= 0 || max > _POW2_32) {
+      throw new RangeError.range(
+          max, 1, _POW2_32, "max", "Must be positive and <= 2^32");
+    }
+    if ((max & -max) == max) {
+      // Fast case for powers of two.
+      _nextState();
+      return _state & (max - 1);
+    }
+
+    int rnd32;
+    int result;
+    do {
+      _nextState();
+      rnd32 = _stateLow;
+      result = rnd32 % max;
+    } while ((rnd32 - result + max) > _POW2_32);
+    return result;
+  }
+
+  double nextDouble() {
+    return ((nextInt(1 << 26) * _POW2_27_D) + nextInt(1 << 27)) / _POW2_53_D;
+  }
+
+  bool nextBool() {
+    return nextInt(2) == 0;
+  }
+
+  // Constants used by the algorithm.
+  static const _POW2_32 = 1 << 32;
+  static const _POW2_53_D = 1.0 * (1 << 53);
+  static const _POW2_27_D = 1.0 * (1 << 27);
+
+  // Use a singleton Random object to get a new seed if no seed was passed.
+  static final _prng = new _Random._withState(_initialSeed());
+
+  static int _setupSeed(int seed) => mix64(seed);
+
+  // TODO: Make this actually random
+  static int _initialSeed() => 0xCAFEBABEDEADBEEF;
+
+  static int _nextSeed() {
+    // Trigger the PRNG once to change the internal state.
+    _prng._nextState();
+    return _prng._stateLow;
+  }
+}
+
+class _SecureRandom implements Random {
+  _SecureRandom() {
+    // Throw early in constructor if entropy source is not hooked up.
+    _getBytes(1);
+  }
+
+  // Return count bytes of entropy as a positive integer; count <= 8.
+  external static int _getBytes(int count);
+
+  int nextInt(int max) {
+    RangeError.checkValueInInterval(
+        max, 1, _POW2_32, "max", "Must be positive and <= 2^32");
+    final byteCount = ((max - 1).bitLength + 7) >> 3;
+    if (byteCount == 0) {
+      return 0; // Not random if max == 1.
+    }
+    var rnd;
+    var result;
+    do {
+      rnd = _getBytes(byteCount);
+      result = rnd % max;
+    } while ((rnd - result + max) > (1 << (byteCount << 3)));
+    return result;
+  }
+
+  double nextDouble() {
+    return (_getBytes(7) >> 3) / _POW2_53_D;
+  }
+
+  bool nextBool() {
+    return _getBytes(1).isEven;
+  }
+
+  // Constants used by the algorithm.
+  static const _POW2_32 = 1 << 32;
+  static const _POW2_53_D = 1.0 * (1 << 53);
+}
diff --git a/sdk/lib/_internal/wasm/lib/object_patch.dart b/sdk/lib/_internal/wasm/lib/object_patch.dart
new file mode 100644
index 0000000..d38ca14
--- /dev/null
+++ b/sdk/lib/_internal/wasm/lib/object_patch.dart
@@ -0,0 +1,49 @@
+// 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.
+
+// part of "core_patch.dart";
+
+// Access hidden identity hash code field
+external int _getHash(Object obj);
+external void _setHash(Object obj, int hash);
+
+@patch
+class Object {
+  @patch
+  external bool operator ==(Object other);
+
+  // Random number generator used to generate identity hash codes.
+  static final _hashCodeRnd = new Random();
+
+  static int _objectHashCode(Object obj) {
+    var result = _getHash(obj);
+    if (result == 0) {
+      // We want the hash to be a Smi value greater than 0.
+      do {
+        result = _hashCodeRnd.nextInt(0x40000000);
+      } while (result == 0);
+
+      _setHash(obj, result);
+      return result;
+    }
+    return result;
+  }
+
+  @patch
+  int get hashCode => _objectHashCode(this);
+  int get _identityHashCode => _objectHashCode(this);
+
+  @patch
+  String toString() => _toString(this);
+  // A statically dispatched version of Object.toString.
+  static String _toString(obj) => "Instance of '${obj.runtimeType}'";
+
+  @patch
+  dynamic noSuchMethod(Invocation invocation) {
+    throw new NoSuchMethodError.withInvocation(this, invocation);
+  }
+
+  @patch
+  external Type get runtimeType;
+}
diff --git a/sdk/lib/_internal/wasm/lib/patch.dart b/sdk/lib/_internal/wasm/lib/patch.dart
new file mode 100644
index 0000000..f69ddc4
--- /dev/null
+++ b/sdk/lib/_internal/wasm/lib/patch.dart
@@ -0,0 +1,9 @@
+// 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.
+
+class _Patch {
+  const _Patch();
+}
+
+const _Patch patch = const _Patch();
diff --git a/sdk/lib/_internal/wasm/lib/print_patch.dart b/sdk/lib/_internal/wasm/lib/print_patch.dart
new file mode 100644
index 0000000..e08b7e2
--- /dev/null
+++ b/sdk/lib/_internal/wasm/lib/print_patch.dart
@@ -0,0 +1,9 @@
+// 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.
+
+// part of "internal_patch.dart";
+
+@patch
+@pragma("wasm:import", "dart2wasm.printToConsole")
+external void printToConsole(String line);
diff --git a/sdk/lib/_internal/wasm/lib/string_buffer_patch.dart b/sdk/lib/_internal/wasm/lib/string_buffer_patch.dart
new file mode 100644
index 0000000..ac22cba
--- /dev/null
+++ b/sdk/lib/_internal/wasm/lib/string_buffer_patch.dart
@@ -0,0 +1,212 @@
+// 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.
+
+// part of "core_patch.dart";
+
+// This file is identical to the VM `string_buffer_patch.dart` except for the
+// implementation of `StringBuffer._create`.
+// TODO(askesc): Share this file with the VM when the patching mechanism gains
+// support for patching an external member from a patch in a separate patch.
+
+@patch
+class StringBuffer {
+  static const int _BUFFER_SIZE = 64;
+  static const int _PARTS_TO_COMPACT = 128;
+  static const int _PARTS_TO_COMPACT_SIZE_LIMIT = _PARTS_TO_COMPACT * 8;
+
+  /**
+   * When strings are written to the string buffer, we add them to a
+   * list of string parts.
+   */
+  List<String>? _parts;
+
+  /**
+    * Total number of code units in the string parts. Does not include
+    * the code units added to the buffer.
+    */
+  int _partsCodeUnits = 0;
+
+  /**
+   * To preserve memory, we sometimes compact the parts. This combines
+   * several smaller parts into a single larger part to cut down on the
+   * cost that comes from the per-object memory overhead. We keep track
+   * of the last index where we ended our compaction and the number of
+   * code units added since the last compaction.
+   */
+  int _partsCompactionIndex = 0;
+  int _partsCodeUnitsSinceCompaction = 0;
+
+  /**
+   * The buffer is used to build up a string from code units. It is
+   * used when writing short strings or individual char codes to the
+   * buffer. The buffer is allocated on demand.
+   */
+  Uint16List? _buffer;
+  int _bufferPosition = 0;
+
+  /**
+   * Collects the approximate maximal magnitude of the code units added
+   * to the buffer.
+   *
+   * The value of each added code unit is or'ed with this variable, so the
+   * most significant bit set in any code unit is also set in this value.
+   * If below 256, the string in the buffer is a Latin-1 string.
+   */
+  int _bufferCodeUnitMagnitude = 0;
+
+  /// Creates the string buffer with an initial content.
+  @patch
+  StringBuffer([Object content = ""]) {
+    write(content);
+  }
+
+  @patch
+  int get length => _partsCodeUnits + _bufferPosition;
+
+  @patch
+  void write(Object? obj) {
+    String str = "$obj";
+    if (str.isEmpty) return;
+    _consumeBuffer();
+    _addPart(str);
+  }
+
+  @patch
+  void writeCharCode(int charCode) {
+    if (charCode <= 0xFFFF) {
+      if (charCode < 0) {
+        throw new RangeError.range(charCode, 0, 0x10FFFF);
+      }
+      _ensureCapacity(1);
+      final localBuffer = _buffer!;
+      localBuffer[_bufferPosition++] = charCode;
+      _bufferCodeUnitMagnitude |= charCode;
+    } else {
+      if (charCode > 0x10FFFF) {
+        throw new RangeError.range(charCode, 0, 0x10FFFF);
+      }
+      _ensureCapacity(2);
+      int bits = charCode - 0x10000;
+      final localBuffer = _buffer!;
+      localBuffer[_bufferPosition++] = 0xD800 | (bits >> 10);
+      localBuffer[_bufferPosition++] = 0xDC00 | (bits & 0x3FF);
+      _bufferCodeUnitMagnitude |= 0xFFFF;
+    }
+  }
+
+  @patch
+  void writeAll(Iterable objects, [String separator = ""]) {
+    Iterator iterator = objects.iterator;
+    if (!iterator.moveNext()) return;
+    if (separator.isEmpty) {
+      do {
+        write(iterator.current);
+      } while (iterator.moveNext());
+    } else {
+      write(iterator.current);
+      while (iterator.moveNext()) {
+        write(separator);
+        write(iterator.current);
+      }
+    }
+  }
+
+  @patch
+  void writeln([Object? obj = ""]) {
+    write(obj);
+    write("\n");
+  }
+
+  /** Makes the buffer empty. */
+  @patch
+  void clear() {
+    _parts = null;
+    _partsCodeUnits = _bufferPosition = _bufferCodeUnitMagnitude = 0;
+  }
+
+  /** Returns the contents of buffer as a string. */
+  @patch
+  String toString() {
+    _consumeBuffer();
+    final localParts = _parts;
+    return (_partsCodeUnits == 0 || localParts == null)
+        ? ""
+        : _StringBase._concatRange(localParts, 0, localParts.length);
+  }
+
+  /** Ensures that the buffer has enough capacity to add n code units. */
+  void _ensureCapacity(int n) {
+    final localBuffer = _buffer;
+    if (localBuffer == null) {
+      _buffer = new Uint16List(_BUFFER_SIZE);
+    } else if (_bufferPosition + n > localBuffer.length) {
+      _consumeBuffer();
+    }
+  }
+
+  /**
+   * Consumes the content of the buffer by turning it into a string
+   * and adding it as a part. After calling this the buffer position
+   * will be reset to zero.
+   */
+  void _consumeBuffer() {
+    if (_bufferPosition == 0) return;
+    bool isLatin1 = _bufferCodeUnitMagnitude <= 0xFF;
+    String str = _create(_buffer!, _bufferPosition, isLatin1);
+    _bufferPosition = _bufferCodeUnitMagnitude = 0;
+    _addPart(str);
+  }
+
+  /**
+   * Adds a new part to this string buffer and keeps track of how
+   * many code units are contained in the parts.
+   */
+  void _addPart(String str) {
+    final localParts = _parts;
+    int length = str.length;
+    _partsCodeUnits += length;
+    _partsCodeUnitsSinceCompaction += length;
+
+    if (localParts == null) {
+      // Empirically this is a good capacity to minimize total bytes allocated.
+      _parts = new _GrowableList.withCapacity(10)..add(str);
+    } else {
+      localParts.add(str);
+      int partsSinceCompaction = localParts.length - _partsCompactionIndex;
+      if (partsSinceCompaction == _PARTS_TO_COMPACT) {
+        _compact();
+      }
+    }
+  }
+
+  /**
+   * Compacts the last N parts if their average size allows us to save a
+   * lot of memory by turning them all into a single part.
+   */
+  void _compact() {
+    final localParts = _parts!;
+    if (_partsCodeUnitsSinceCompaction < _PARTS_TO_COMPACT_SIZE_LIMIT) {
+      String compacted = _StringBase._concatRange(
+          localParts,
+          _partsCompactionIndex, // Start
+          _partsCompactionIndex + _PARTS_TO_COMPACT // End
+          );
+      localParts.length = localParts.length - _PARTS_TO_COMPACT;
+      localParts.add(compacted);
+    }
+    _partsCodeUnitsSinceCompaction = 0;
+    _partsCompactionIndex = localParts.length;
+  }
+
+  /**
+   * Create a [String] from the UFT-16 code units in buffer.
+   */
+  static String _create(Uint16List buffer, int length, bool isLatin1) {
+    if (isLatin1) {
+      return _StringBase._createOneByteString(buffer, 0, length);
+    } else {
+      return _TwoByteString._allocateFromTwoByteList(buffer, 0, length);
+    }
+  }
+}
diff --git a/sdk/lib/_internal/wasm/lib/string_patch.dart b/sdk/lib/_internal/wasm/lib/string_patch.dart
new file mode 100644
index 0000000..a9c0222
--- /dev/null
+++ b/sdk/lib/_internal/wasm/lib/string_patch.dart
@@ -0,0 +1,1391 @@
+// 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.
+
+// part of "core_patch.dart";
+
+// Much of this patch file is similar to the VM `string_patch.dart`. It may make
+// sense to share some of the code when the patching mechanism supports patching
+// the same class in multiple patch files.
+
+const int _maxAscii = 0x7f;
+const int _maxLatin1 = 0xff;
+const int _maxUtf16 = 0xffff;
+const int _maxUnicode = 0x10ffff;
+
+@patch
+class String {
+  @patch
+  factory String.fromCharCodes(Iterable<int> charCodes,
+      [int start = 0, int? end]) {
+    return _StringBase.createFromCharCodes(charCodes, start, end, null);
+  }
+
+  @patch
+  factory String.fromCharCode(int charCode) {
+    if (charCode >= 0) {
+      if (charCode <= 0xff) {
+        return _OneByteString._allocate(1).._setAt(0, charCode);
+      }
+      if (charCode <= 0xffff) {
+        return _TwoByteString._allocate(1).._setAt(0, charCode);
+      }
+      if (charCode <= 0x10ffff) {
+        var low = 0xDC00 | (charCode & 0x3ff);
+        int bits = charCode - 0x10000;
+        var high = 0xD800 | (bits >> 10);
+        return _StringBase._createFromCodePoints(
+            new _List(2)
+              ..[0] = high
+              ..[1] = low,
+            0,
+            2);
+      }
+    }
+    throw new RangeError.range(charCode, 0, 0x10ffff);
+  }
+
+  @patch
+  external const factory String.fromEnvironment(String name,
+      {String defaultValue = ""});
+
+  bool get _isOneByte;
+  String _substringUnchecked(int startIndex, int endIndex);
+}
+
+/**
+ * [_StringBase] contains common methods used by concrete String
+ * implementations, e.g., _OneByteString.
+ */
+abstract class _StringBase implements String {
+  bool _isWhitespace(int codeUnit);
+
+  // Constants used by replaceAll encoding of string slices between matches.
+  // A string slice (start+length) is encoded in a single Smi to save memory
+  // overhead in the common case.
+  // We use fewer bits for length (11 bits) than for the start index (19+ bits).
+  // For long strings, it's possible to have many large indices,
+  // but it's unlikely to have many long lengths since slices don't overlap.
+  // If there are few matches in a long string, then there are few long slices,
+  // and if there are many matches, there'll likely be many short slices.
+  //
+  // Encoding is: 0((start << _lengthBits) | length)
+
+  // Number of bits used by length.
+  // This is the shift used to encode and decode the start index.
+  static const int _lengthBits = 11;
+  // The maximal allowed length value in an encoded slice.
+  static const int _maxLengthValue = (1 << _lengthBits) - 1;
+  // Mask of length in encoded smi value.
+  static const int _lengthMask = _maxLengthValue;
+  static const int _startBits = _maxUnsignedSmiBits - _lengthBits;
+  // Maximal allowed start index value in an encoded slice.
+  static const int _maxStartValue = (1 << _startBits) - 1;
+  // We pick 30 as a safe lower bound on available bits in a negative smi.
+  // TODO(lrn): Consider allowing more bits for start on 64-bit systems.
+  static const int _maxUnsignedSmiBits = 30;
+
+  // For longer strings, calling into C++ to create the result of a
+  // [replaceAll] is faster than [_joinReplaceAllOneByteResult].
+  // TODO(lrn): See if this limit can be tweaked.
+  static const int _maxJoinReplaceOneByteStringLength = 500;
+
+  _StringBase._();
+
+  int get hashCode {
+    int hash = _getHash(this);
+    if (hash != 0) return hash;
+    hash = _computeHashCode();
+    _setHash(this, hash);
+    return hash;
+  }
+
+  int _computeHashCode();
+
+  int get _identityHashCode => hashCode;
+
+  bool get _isOneByte {
+    // Alternatively return false and override it on one-byte string classes.
+    return this is _OneByteString;
+  }
+
+  /**
+   * Create the most efficient string representation for specified
+   * [charCodes].
+   *
+   * Only uses the character codes between index [start] and index [end] of
+   * `charCodes`. They must satisfy `0 <= start <= end <= charCodes.length`.
+   *
+   * The [limit] is an upper limit on the character codes in the iterable.
+   * It's `null` if unknown.
+   */
+  static String createFromCharCodes(
+      Iterable<int> charCodes, int start, int? end, int? limit) {
+    // TODO(srdjan): Also skip copying of wide typed arrays.
+    if (charCodes is Uint8List) {
+      final actualEnd =
+          RangeError.checkValidRange(start, end, charCodes.length);
+      return _createOneByteString(charCodes, start, actualEnd - start);
+    } else if (charCodes is! Uint16List) {
+      return _createStringFromIterable(charCodes, start, end);
+    }
+    final int codeCount = charCodes.length;
+    final actualEnd = RangeError.checkValidRange(start, end, codeCount);
+    final len = actualEnd - start;
+    if (len == 0) return "";
+
+    final typedCharCodes = unsafeCast<List<int>>(charCodes);
+
+    final int actualLimit =
+        limit ?? _scanCodeUnits(typedCharCodes, start, actualEnd);
+    if (actualLimit < 0) {
+      throw new ArgumentError(typedCharCodes);
+    }
+    if (actualLimit <= _maxLatin1) {
+      return _createOneByteString(typedCharCodes, start, len);
+    }
+    if (actualLimit <= _maxUtf16) {
+      return _TwoByteString._allocateFromTwoByteList(
+          typedCharCodes, start, actualEnd);
+    }
+    // TODO(lrn): Consider passing limit to _createFromCodePoints, because
+    // the function is currently fully generic and doesn't know that its
+    // charCodes are not all Latin-1 or Utf-16.
+    return _createFromCodePoints(typedCharCodes, start, actualEnd);
+  }
+
+  static int _scanCodeUnits(List<int> charCodes, int start, int end) {
+    int bits = 0;
+    for (int i = start; i < end; i++) {
+      int code = charCodes[i];
+      bits |= code;
+    }
+    return bits;
+  }
+
+  static String _createStringFromIterable(
+      Iterable<int> charCodes, int start, int? end) {
+    // Treat charCodes as Iterable.
+    if (charCodes is EfficientLengthIterable) {
+      int length = charCodes.length;
+      final endVal = RangeError.checkValidRange(start, end, length);
+      final charCodeList = new List<int>.from(
+          charCodes.take(endVal).skip(start),
+          growable: false);
+      return createFromCharCodes(charCodeList, 0, charCodeList.length, null);
+    }
+    // Don't know length of iterable, so iterate and see if all the values
+    // are there.
+    if (start < 0) throw new RangeError.range(start, 0, charCodes.length);
+    var it = charCodes.iterator;
+    for (int i = 0; i < start; i++) {
+      if (!it.moveNext()) {
+        throw new RangeError.range(start, 0, i);
+      }
+    }
+    List<int> charCodeList;
+    int bits = 0; // Bitwise-or of all char codes in list.
+    final endVal = end;
+    if (endVal == null) {
+      var list = <int>[];
+      while (it.moveNext()) {
+        int code = it.current;
+        bits |= code;
+        list.add(code);
+      }
+      charCodeList = makeListFixedLength<int>(list);
+    } else {
+      if (endVal < start) {
+        throw new RangeError.range(endVal, start, charCodes.length);
+      }
+      int len = endVal - start;
+      charCodeList = new List<int>.generate(len, (int i) {
+        if (!it.moveNext()) {
+          throw new RangeError.range(endVal, start, start + i);
+        }
+        int code = it.current;
+        bits |= code;
+        return code;
+      });
+    }
+    int length = charCodeList.length;
+    if (bits < 0) {
+      throw new ArgumentError(charCodes);
+    }
+    bool isOneByteString = (bits <= _maxLatin1);
+    if (isOneByteString) {
+      return _createOneByteString(charCodeList, 0, length);
+    }
+    return createFromCharCodes(charCodeList, 0, length, bits);
+  }
+
+  // Inlining is disabled as a workaround to http://dartbug.com/37800.
+
+  static String _createOneByteString(List<int> charCodes, int start, int len) {
+    // It's always faster to do this in Dart than to call into the runtime.
+    var s = _OneByteString._allocate(len);
+
+    // Special case for native Uint8 typed arrays.
+    if (charCodes is Uint8List) {
+      copyRangeFromUint8ListToOneByteString(charCodes, s, start, 0, len);
+      return s;
+    }
+
+    // Fall through to normal case.
+    for (int i = 0; i < len; i++) {
+      s._setAt(i, charCodes[start + i]);
+    }
+    return s;
+  }
+
+  external static String _createFromCodePoints(
+      List<int> codePoints, int start, int end);
+
+  String operator [](int index) => String.fromCharCode(codeUnitAt(index));
+
+  int codeUnitAt(int index); // Implemented in the subclasses.
+
+  int get length; // Implemented in the subclasses.
+
+  bool get isEmpty {
+    return this.length == 0;
+  }
+
+  bool get isNotEmpty => !isEmpty;
+
+  String operator +(String other) => _interpolate([this, other]);
+
+  String toString() {
+    return this;
+  }
+
+  bool operator ==(Object other) {
+    if (identical(this, other)) {
+      return true;
+    }
+    if (other is String && this.length == other.length) {
+      final len = this.length;
+      for (int i = 0; i < len; i++) {
+        if (this.codeUnitAt(i) != other.codeUnitAt(i)) {
+          return false;
+        }
+      }
+      return true;
+    }
+    return false;
+  }
+
+  int compareTo(String other) {
+    int thisLength = this.length;
+    int otherLength = other.length;
+    int len = (thisLength < otherLength) ? thisLength : otherLength;
+    for (int i = 0; i < len; i++) {
+      int thisCodeUnit = this.codeUnitAt(i);
+      int otherCodeUnit = other.codeUnitAt(i);
+      if (thisCodeUnit < otherCodeUnit) {
+        return -1;
+      }
+      if (thisCodeUnit > otherCodeUnit) {
+        return 1;
+      }
+    }
+    if (thisLength < otherLength) return -1;
+    if (thisLength > otherLength) return 1;
+    return 0;
+  }
+
+  bool _substringMatches(int start, String other) {
+    if (other.isEmpty) return true;
+    final len = other.length;
+    if ((start < 0) || (start + len > this.length)) {
+      return false;
+    }
+    for (int i = 0; i < len; i++) {
+      if (this.codeUnitAt(i + start) != other.codeUnitAt(i)) {
+        return false;
+      }
+    }
+    return true;
+  }
+
+  bool endsWith(String other) {
+    return _substringMatches(this.length - other.length, other);
+  }
+
+  bool startsWith(Pattern pattern, [int index = 0]) {
+    if ((index < 0) || (index > this.length)) {
+      throw new RangeError.range(index, 0, this.length);
+    }
+    if (pattern is String) {
+      return _substringMatches(index, pattern);
+    }
+    return pattern.matchAsPrefix(this, index) != null;
+  }
+
+  int indexOf(Pattern pattern, [int start = 0]) {
+    if ((start < 0) || (start > this.length)) {
+      throw new RangeError.range(start, 0, this.length, "start");
+    }
+    if (pattern is String) {
+      String other = pattern;
+      int maxIndex = this.length - other.length;
+      // TODO: Use an efficient string search (e.g. BMH).
+      for (int index = start; index <= maxIndex; index++) {
+        if (_substringMatches(index, other)) {
+          return index;
+        }
+      }
+      return -1;
+    }
+    for (int i = start; i <= this.length; i++) {
+      // TODO(11276); This has quadratic behavior because matchAsPrefix tries
+      // to find a later match too. Optimize matchAsPrefix to avoid this.
+      if (pattern.matchAsPrefix(this, i) != null) return i;
+    }
+    return -1;
+  }
+
+  int lastIndexOf(Pattern pattern, [int? start]) {
+    if (start == null) {
+      start = this.length;
+    } else if (start < 0 || start > this.length) {
+      throw new RangeError.range(start, 0, this.length);
+    }
+    if (pattern is String) {
+      String other = pattern;
+      int maxIndex = this.length - other.length;
+      if (maxIndex < start) start = maxIndex;
+      for (int index = start; index >= 0; index--) {
+        if (_substringMatches(index, other)) {
+          return index;
+        }
+      }
+      return -1;
+    }
+    for (int i = start; i >= 0; i--) {
+      // TODO(11276); This has quadratic behavior because matchAsPrefix tries
+      // to find a later match too. Optimize matchAsPrefix to avoid this.
+      if (pattern.matchAsPrefix(this, i) != null) return i;
+    }
+    return -1;
+  }
+
+  String substring(int startIndex, [int? endIndex]) {
+    endIndex = RangeError.checkValidRange(startIndex, endIndex, this.length);
+    return _substringUnchecked(startIndex, endIndex);
+  }
+
+  String _substringUnchecked(int startIndex, int endIndex) {
+    assert((startIndex >= 0) && (startIndex <= this.length));
+    assert((endIndex >= 0) && (endIndex <= this.length));
+    assert(startIndex <= endIndex);
+
+    if (startIndex == endIndex) {
+      return "";
+    }
+    if ((startIndex == 0) && (endIndex == this.length)) {
+      return this;
+    }
+    if ((startIndex + 1) == endIndex) {
+      return this[startIndex];
+    }
+    return _substringUncheckedNative(startIndex, endIndex);
+  }
+
+  external String _substringUncheckedNative(int startIndex, int endIndex);
+
+  // Checks for one-byte whitespaces only.
+  static bool _isOneByteWhitespace(int codeUnit) {
+    if (codeUnit <= 32) {
+      return ((codeUnit == 32) || // Space.
+          ((codeUnit <= 13) && (codeUnit >= 9))); // CR, LF, TAB, etc.
+    }
+    return (codeUnit == 0x85) || (codeUnit == 0xA0); // NEL, NBSP.
+  }
+
+  // Characters with Whitespace property (Unicode 6.3).
+  // 0009..000D    ; White_Space # Cc       <control-0009>..<control-000D>
+  // 0020          ; White_Space # Zs       SPACE
+  // 0085          ; White_Space # Cc       <control-0085>
+  // 00A0          ; White_Space # Zs       NO-BREAK SPACE
+  // 1680          ; White_Space # Zs       OGHAM SPACE MARK
+  // 2000..200A    ; White_Space # Zs       EN QUAD..HAIR SPACE
+  // 2028          ; White_Space # Zl       LINE SEPARATOR
+  // 2029          ; White_Space # Zp       PARAGRAPH SEPARATOR
+  // 202F          ; White_Space # Zs       NARROW NO-BREAK SPACE
+  // 205F          ; White_Space # Zs       MEDIUM MATHEMATICAL SPACE
+  // 3000          ; White_Space # Zs       IDEOGRAPHIC SPACE
+  //
+  // BOM: 0xFEFF
+  static bool _isTwoByteWhitespace(int codeUnit) {
+    if (codeUnit <= 32) {
+      return (codeUnit == 32) || ((codeUnit <= 13) && (codeUnit >= 9));
+    }
+    if (codeUnit < 0x85) return false;
+    if ((codeUnit == 0x85) || (codeUnit == 0xA0)) return true;
+    return (codeUnit <= 0x200A)
+        ? ((codeUnit == 0x1680) || (0x2000 <= codeUnit))
+        : ((codeUnit == 0x2028) ||
+            (codeUnit == 0x2029) ||
+            (codeUnit == 0x202F) ||
+            (codeUnit == 0x205F) ||
+            (codeUnit == 0x3000) ||
+            (codeUnit == 0xFEFF));
+  }
+
+  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;
+  }
+
+  String trim() {
+    final len = this.length;
+    int first = _firstNonWhitespace();
+    if (len == first) {
+      // String contains only whitespaces.
+      return "";
+    }
+    int last = _lastNonWhitespace() + 1;
+    if ((first == 0) && (last == len)) {
+      // Returns this string since it does not have leading or trailing
+      // whitespaces.
+      return this;
+    }
+    return _substringUnchecked(first, last);
+  }
+
+  String trimLeft() {
+    final len = this.length;
+    int first = 0;
+    for (; first < len; first++) {
+      if (!_isWhitespace(this.codeUnitAt(first))) {
+        break;
+      }
+    }
+    if (len == first) {
+      // String contains only whitespaces.
+      return "";
+    }
+    if (first == 0) {
+      // Returns this string since it does not have leading or trailing
+      // whitespaces.
+      return this;
+    }
+    return _substringUnchecked(first, len);
+  }
+
+  String trimRight() {
+    final len = this.length;
+    int last = len - 1;
+    for (; last >= 0; last--) {
+      if (!_isWhitespace(this.codeUnitAt(last))) {
+        break;
+      }
+    }
+    if (last == -1) {
+      // String contains only whitespaces.
+      return "";
+    }
+    if (last == (len - 1)) {
+      // Returns this string since it does not have trailing whitespaces.
+      return this;
+    }
+    return _substringUnchecked(0, last + 1);
+  }
+
+  String operator *(int times) {
+    if (times <= 0) return "";
+    if (times == 1) return this;
+    StringBuffer buffer = new StringBuffer(this);
+    for (int i = 1; i < times; i++) {
+      buffer.write(this);
+    }
+    return buffer.toString();
+  }
+
+  String padLeft(int width, [String padding = ' ']) {
+    int delta = width - this.length;
+    if (delta <= 0) return this;
+    StringBuffer buffer = new StringBuffer();
+    for (int i = 0; i < delta; i++) {
+      buffer.write(padding);
+    }
+    buffer.write(this);
+    return buffer.toString();
+  }
+
+  String padRight(int width, [String padding = ' ']) {
+    int delta = width - this.length;
+    if (delta <= 0) return this;
+    StringBuffer buffer = new StringBuffer(this);
+    for (int i = 0; i < delta; i++) {
+      buffer.write(padding);
+    }
+    return buffer.toString();
+  }
+
+  bool contains(Pattern pattern, [int startIndex = 0]) {
+    if (pattern is String) {
+      if (startIndex < 0 || startIndex > this.length) {
+        throw new RangeError.range(startIndex, 0, this.length);
+      }
+      return indexOf(pattern, startIndex) >= 0;
+    }
+    return pattern.allMatches(this.substring(startIndex)).isNotEmpty;
+  }
+
+  String replaceFirst(Pattern pattern, String replacement,
+      [int startIndex = 0]) {
+    RangeError.checkValueInInterval(startIndex, 0, this.length, "startIndex");
+    Iterator iterator = startIndex == 0
+        ? pattern.allMatches(this).iterator
+        : pattern.allMatches(this, startIndex).iterator;
+    if (!iterator.moveNext()) return this;
+    Match match = iterator.current;
+    return replaceRange(match.start, match.end, replacement);
+  }
+
+  String replaceRange(int start, int? end, String replacement) {
+    final length = this.length;
+    final localEnd = RangeError.checkValidRange(start, end, length);
+    bool replacementIsOneByte = replacement._isOneByte;
+    if (start == 0 && localEnd == length) return replacement;
+    int replacementLength = replacement.length;
+    int totalLength = start + (length - localEnd) + replacementLength;
+    if (replacementIsOneByte && this._isOneByte) {
+      var result = _OneByteString._allocate(totalLength);
+      int index = 0;
+      index = result._setRange(index, this, 0, start);
+      index = result._setRange(start, replacement, 0, replacementLength);
+      result._setRange(index, this, localEnd, length);
+      return result;
+    }
+    List slices = [];
+    _addReplaceSlice(slices, 0, start);
+    if (replacement.length > 0) slices.add(replacement);
+    _addReplaceSlice(slices, localEnd, length);
+    return _joinReplaceAllResult(
+        this, slices, totalLength, replacementIsOneByte);
+  }
+
+  static int _addReplaceSlice(List matches, int start, int end) {
+    int length = end - start;
+    if (length > 0) {
+      if (length <= _maxLengthValue && start <= _maxStartValue) {
+        matches.add(-((start << _lengthBits) | length));
+      } else {
+        matches.add(start);
+        matches.add(end);
+      }
+    }
+    return length;
+  }
+
+  String replaceAll(Pattern pattern, String replacement) {
+    int startIndex = 0;
+    // String fragments that replace the prefix [this] up to [startIndex].
+    List matches = [];
+    int length = 0; // Length of all fragments.
+    int replacementLength = replacement.length;
+
+    if (replacementLength == 0) {
+      for (Match match in pattern.allMatches(this)) {
+        length += _addReplaceSlice(matches, startIndex, match.start);
+        startIndex = match.end;
+      }
+    } else {
+      for (Match match in pattern.allMatches(this)) {
+        length += _addReplaceSlice(matches, startIndex, match.start);
+        matches.add(replacement);
+        length += replacementLength;
+        startIndex = match.end;
+      }
+    }
+    // No match, or a zero-length match at start with zero-length replacement.
+    if (startIndex == 0 && length == 0) return this;
+    length += _addReplaceSlice(matches, startIndex, this.length);
+    bool replacementIsOneByte = replacement._isOneByte;
+    if (replacementIsOneByte &&
+        length < _maxJoinReplaceOneByteStringLength &&
+        this._isOneByte) {
+      // TODO(lrn): Is there a cut-off point, or is runtime always faster?
+      return _joinReplaceAllOneByteResult(this, matches, length);
+    }
+    return _joinReplaceAllResult(this, matches, length, replacementIsOneByte);
+  }
+
+  /**
+   * As [_joinReplaceAllResult], but knowing that the result
+   * is always a [_OneByteString].
+   */
+  static String _joinReplaceAllOneByteResult(
+      String base, List matches, int length) {
+    _OneByteString result = _OneByteString._allocate(length);
+    int writeIndex = 0;
+    for (int i = 0; i < matches.length; i++) {
+      var entry = matches[i];
+      if (entry is _Smi) {
+        int sliceStart = entry;
+        int sliceEnd;
+        if (sliceStart < 0) {
+          int bits = -sliceStart;
+          int sliceLength = bits & _lengthMask;
+          sliceStart = bits >> _lengthBits;
+          sliceEnd = sliceStart + sliceLength;
+        } else {
+          i++;
+          // This function should only be called with valid matches lists.
+          // If the list is short, or sliceEnd is not an integer, one of
+          // the next few lines will throw anyway.
+          assert(i < matches.length);
+          sliceEnd = matches[i];
+        }
+        for (int j = sliceStart; j < sliceEnd; j++) {
+          result._setAt(writeIndex++, base.codeUnitAt(j));
+        }
+      } else {
+        // Replacement is a one-byte string.
+        String replacement = entry;
+        for (int j = 0; j < replacement.length; j++) {
+          result._setAt(writeIndex++, replacement.codeUnitAt(j));
+        }
+      }
+    }
+    assert(writeIndex == length);
+    return result;
+  }
+
+  /**
+   * Combine the results of a [replaceAll] match into a new string.
+   *
+   * The [matches] lists contains Smi index pairs representing slices of
+   * [base] and [String]s to be put in between the slices.
+   *
+   * The total [length] of the resulting string is known, as is
+   * whether the replacement strings are one-byte strings.
+   * If they are, then we have to check the base string slices to know
+   * whether the result must be a one-byte string.
+   */
+
+  external static String _joinReplaceAllResult(
+      String base, List matches, int length, bool replacementStringsAreOneByte);
+
+  String replaceAllMapped(Pattern pattern, String replace(Match match)) {
+    List matches = [];
+    int length = 0;
+    int startIndex = 0;
+    bool replacementStringsAreOneByte = true;
+    for (Match match in pattern.allMatches(this)) {
+      length += _addReplaceSlice(matches, startIndex, match.start);
+      var replacement = "${replace(match)}";
+      matches.add(replacement);
+      length += replacement.length;
+      replacementStringsAreOneByte =
+          replacementStringsAreOneByte && replacement._isOneByte;
+      startIndex = match.end;
+    }
+    if (matches.isEmpty) return this;
+    length += _addReplaceSlice(matches, startIndex, this.length);
+    if (replacementStringsAreOneByte &&
+        length < _maxJoinReplaceOneByteStringLength &&
+        this._isOneByte) {
+      return _joinReplaceAllOneByteResult(this, matches, length);
+    }
+    return _joinReplaceAllResult(
+        this, matches, length, replacementStringsAreOneByte);
+  }
+
+  String replaceFirstMapped(Pattern pattern, String replace(Match match),
+      [int startIndex = 0]) {
+    RangeError.checkValueInInterval(startIndex, 0, this.length, "startIndex");
+
+    var matches = pattern.allMatches(this, startIndex).iterator;
+    if (!matches.moveNext()) return this;
+    var match = matches.current;
+    var replacement = "${replace(match)}";
+    return replaceRange(match.start, match.end, replacement);
+  }
+
+  static String _matchString(Match match) => match[0]!;
+  static String _stringIdentity(String string) => string;
+
+  String _splitMapJoinEmptyString(
+      String onMatch(Match match), String onNonMatch(String nonMatch)) {
+    // Pattern is the empty string.
+    StringBuffer buffer = new StringBuffer();
+    int length = this.length;
+    int i = 0;
+    buffer.write(onNonMatch(""));
+    while (i < length) {
+      buffer.write(onMatch(new _StringMatch(i, this, "")));
+      // Special case to avoid splitting a surrogate pair.
+      int code = this.codeUnitAt(i);
+      if ((code & ~0x3FF) == 0xD800 && length > i + 1) {
+        // Leading surrogate;
+        code = this.codeUnitAt(i + 1);
+        if ((code & ~0x3FF) == 0xDC00) {
+          // Matching trailing surrogate.
+          buffer.write(onNonMatch(this.substring(i, i + 2)));
+          i += 2;
+          continue;
+        }
+      }
+      buffer.write(onNonMatch(this[i]));
+      i++;
+    }
+    buffer.write(onMatch(new _StringMatch(i, this, "")));
+    buffer.write(onNonMatch(""));
+    return buffer.toString();
+  }
+
+  String splitMapJoin(Pattern pattern,
+      {String onMatch(Match match)?, String onNonMatch(String nonMatch)?}) {
+    onMatch ??= _matchString;
+    onNonMatch ??= _stringIdentity;
+    if (pattern is String) {
+      String stringPattern = pattern;
+      if (stringPattern.isEmpty) {
+        return _splitMapJoinEmptyString(onMatch, onNonMatch);
+      }
+    }
+    StringBuffer buffer = new StringBuffer();
+    int startIndex = 0;
+    for (Match match in pattern.allMatches(this)) {
+      buffer.write(onNonMatch(this.substring(startIndex, match.start)));
+      buffer.write(onMatch(match).toString());
+      startIndex = match.end;
+    }
+    buffer.write(onNonMatch(this.substring(startIndex)));
+    return buffer.toString();
+  }
+
+  /**
+   * Convert all objects in [values] to strings and concat them
+   * into a result string.
+   * Modifies the input list if it contains non-`String` values.
+   */
+  @pragma("wasm:entry-point", "call")
+  static String _interpolate(final List values) {
+    final numValues = values.length;
+    int totalLength = 0;
+    int i = 0;
+    while (i < numValues) {
+      final e = values[i];
+      final s = e.toString();
+      values[i] = s;
+      if (s is _OneByteString) {
+        totalLength += s.length;
+        i++;
+      } else {
+        // Handle remaining elements without checking for one-byte-ness.
+        while (++i < numValues) {
+          final e = values[i];
+          values[i] = e.toString();
+        }
+        return _concatRangeNative(values, 0, numValues);
+      }
+    }
+    // All strings were one-byte strings.
+    return _OneByteString._concatAll(values, totalLength);
+  }
+
+  static ArgumentError _interpolationError(Object? o, Object? result) {
+    // Since Dart 2.0, [result] can only be null.
+    return new ArgumentError.value(
+        o, "object", "toString method returned 'null'");
+  }
+
+  Iterable<Match> allMatches(String string, [int start = 0]) {
+    if (start < 0 || start > string.length) {
+      throw new RangeError.range(start, 0, string.length, "start");
+    }
+    return new _StringAllMatchesIterable(string, this, start);
+  }
+
+  Match? matchAsPrefix(String string, [int start = 0]) {
+    if (start < 0 || start > string.length) {
+      throw new RangeError.range(start, 0, string.length);
+    }
+    if (start + this.length > string.length) return null;
+    for (int i = 0; i < this.length; i++) {
+      if (string.codeUnitAt(start + i) != this.codeUnitAt(i)) {
+        return null;
+      }
+    }
+    return new _StringMatch(start, string, this);
+  }
+
+  List<String> split(Pattern pattern) {
+    if ((pattern is String) && pattern.isEmpty) {
+      List<String> result =
+          new List<String>.generate(this.length, (int i) => this[i]);
+      return result;
+    }
+    int length = this.length;
+    Iterator iterator = pattern.allMatches(this).iterator;
+    if (length == 0 && iterator.moveNext()) {
+      // A matched empty string input returns the empty list.
+      return <String>[];
+    }
+    List<String> result = <String>[];
+    int startIndex = 0;
+    int previousIndex = 0;
+    // 'pattern' may not be implemented correctly and therefore we cannot
+    // call _substringUnhchecked unless it is a trustworthy type (e.g. String).
+    while (true) {
+      if (startIndex == length || !iterator.moveNext()) {
+        result.add(this.substring(previousIndex, length));
+        break;
+      }
+      Match match = iterator.current;
+      if (match.start == length) {
+        result.add(this.substring(previousIndex, length));
+        break;
+      }
+      int endIndex = match.end;
+      if (startIndex == endIndex && endIndex == previousIndex) {
+        ++startIndex; // empty match, advance and restart
+        continue;
+      }
+      result.add(this.substring(previousIndex, match.start));
+      startIndex = previousIndex = endIndex;
+    }
+    return result;
+  }
+
+  List<int> get codeUnits => new CodeUnits(this);
+
+  Runes get runes => new Runes(this);
+
+  external String toUpperCase();
+
+  external String toLowerCase();
+
+  // Concatenate ['start', 'end'[ elements of 'strings'.
+  static String _concatRange(List<String> strings, int start, int end) {
+    if ((end - start) == 1) {
+      return strings[start];
+    }
+    return _concatRangeNative(strings, start, end);
+  }
+
+  // Call this method if all elements of [strings] are known to be strings
+  // but not all are known to be OneByteString(s).
+  static String _concatRangeNative(List strings, int start, int end) {
+    int totalLength = 0;
+    for (int i = start; i < end; i++) {
+      totalLength += unsafeCast<_StringBase>(strings[i]).length;
+    }
+    _TwoByteString result = _TwoByteString._allocate(totalLength);
+    int offset = 0;
+    for (int i = start; i < end; i++) {
+      _StringBase s = unsafeCast<_StringBase>(strings[i]);
+      offset = s._copyIntoTwoByteString(result, offset);
+    }
+    return result;
+  }
+
+  int _copyIntoTwoByteString(_TwoByteString result, int offset);
+
+  static int _combineHashes(int hash, int other_hash) {
+    hash += other_hash;
+    hash += hash << 10;
+    hash ^= (hash & 0xFFFFFFFF) >>> 6;
+    return hash;
+  }
+
+  static int _finalizeHash(int hash) {
+    hash += hash << 3;
+    hash ^= (hash & 0xFFFFFFFF) >>> 11;
+    hash += hash << 15;
+    hash &= 0x3FFFFFFF;
+    return hash == 0 ? 1 : hash;
+  }
+}
+
+@pragma("wasm:entry-point")
+class _OneByteString extends _StringBase {
+  @pragma("wasm:entry-point")
+  WasmIntArray<WasmI8> _array;
+
+  _OneByteString._withLength(int length)
+      : _array = WasmIntArray<WasmI8>(length),
+        super._();
+
+  // Same hash as VM
+  int _computeHashCode() {
+    WasmIntArray<WasmI8> array = _array;
+    int length = array.length;
+    int hash = 0;
+    for (int i = 0; i < length; i++) {
+      hash = _StringBase._combineHashes(hash, array.readUnsigned(i));
+    }
+    return _StringBase._finalizeHash(hash);
+  }
+
+  int codeUnitAt(int index) => _array.readUnsigned(index);
+
+  int get length => _array.length;
+
+  bool _isWhitespace(int codeUnit) {
+    return _StringBase._isOneByteWhitespace(codeUnit);
+  }
+
+  bool operator ==(Object other) {
+    return super == other;
+  }
+
+  String _substringUncheckedNative(int startIndex, int endIndex) {
+    int length = endIndex - startIndex;
+    var result = _OneByteString._withLength(length);
+    for (int i = 0; i < length; i++) {
+      result._setAt(i, codeUnitAt(startIndex + i));
+    }
+    return result;
+  }
+
+  List<String> _splitWithCharCode(int charCode) {
+    final parts = <String>[];
+    int i = 0;
+    int start = 0;
+    for (i = 0; i < this.length; ++i) {
+      if (this.codeUnitAt(i) == charCode) {
+        parts.add(this._substringUnchecked(start, i));
+        start = i + 1;
+      }
+    }
+    parts.add(this._substringUnchecked(start, i));
+    return parts;
+  }
+
+  List<String> split(Pattern pattern) {
+    if (pattern is _OneByteString && pattern.length == 1) {
+      return _splitWithCharCode(pattern.codeUnitAt(0));
+    }
+    return super.split(pattern);
+  }
+
+  // All element of 'strings' must be OneByteStrings.
+  static _concatAll(List strings, int totalLength) {
+    final result = _OneByteString._allocate(totalLength);
+    final to = result._array;
+    final stringsLength = strings.length;
+    int j = 0;
+    for (int s = 0; s < stringsLength; s++) {
+      final _OneByteString e = unsafeCast<_OneByteString>(strings[s]);
+      final from = e._array;
+      final length = from.length;
+      for (int i = 0; i < length; i++) {
+        to.write(j++, from.readUnsigned(i));
+      }
+    }
+    return result;
+  }
+
+  int _copyIntoTwoByteString(_TwoByteString result, int offset) {
+    final from = _array;
+    final int length = from.length;
+    final to = result._array;
+    int j = offset;
+    for (int i = 0; i < length; i++) {
+      to.write(j++, from.readUnsigned(i));
+    }
+    return j;
+  }
+
+  int indexOf(Pattern pattern, [int start = 0]) {
+    final len = this.length;
+    // Specialize for single character pattern.
+    if (pattern is String && pattern.length == 1 && start >= 0 && start < len) {
+      final patternCu0 = pattern.codeUnitAt(0);
+      if (patternCu0 > 0xFF) {
+        return -1;
+      }
+      for (int i = start; i < len; i++) {
+        if (this.codeUnitAt(i) == patternCu0) {
+          return i;
+        }
+      }
+      return -1;
+    }
+    return super.indexOf(pattern, start);
+  }
+
+  bool contains(Pattern pattern, [int start = 0]) {
+    final len = this.length;
+    if (pattern is String && pattern.length == 1 && start >= 0 && start < len) {
+      final patternCu0 = pattern.codeUnitAt(0);
+      if (patternCu0 > 0xFF) {
+        return false;
+      }
+      for (int i = start; i < len; i++) {
+        if (this.codeUnitAt(i) == patternCu0) {
+          return true;
+        }
+      }
+      return false;
+    }
+    return super.contains(pattern, start);
+  }
+
+  String operator *(int times) {
+    if (times <= 0) return "";
+    if (times == 1) return this;
+    int length = this.length;
+    if (this.isEmpty) return this; // Don't clone empty string.
+    _OneByteString result = _OneByteString._allocate(length * times);
+    int index = 0;
+    for (int i = 0; i < times; i++) {
+      for (int j = 0; j < length; j++) {
+        result._setAt(index++, this.codeUnitAt(j));
+      }
+    }
+    return result;
+  }
+
+  String padLeft(int width, [String padding = ' ']) {
+    if (!padding._isOneByte) {
+      return super.padLeft(width, padding);
+    }
+    int length = this.length;
+    int delta = width - length;
+    if (delta <= 0) return this;
+    int padLength = padding.length;
+    int resultLength = padLength * delta + length;
+    _OneByteString result = _OneByteString._allocate(resultLength);
+    int index = 0;
+    if (padLength == 1) {
+      int padChar = padding.codeUnitAt(0);
+      for (int i = 0; i < delta; i++) {
+        result._setAt(index++, padChar);
+      }
+    } else {
+      for (int i = 0; i < delta; i++) {
+        for (int j = 0; j < padLength; j++) {
+          result._setAt(index++, padding.codeUnitAt(j));
+        }
+      }
+    }
+    for (int i = 0; i < length; i++) {
+      result._setAt(index++, this.codeUnitAt(i));
+    }
+    return result;
+  }
+
+  String padRight(int width, [String padding = ' ']) {
+    if (!padding._isOneByte) {
+      return super.padRight(width, padding);
+    }
+    int length = this.length;
+    int delta = width - length;
+    if (delta <= 0) return this;
+    int padLength = padding.length;
+    int resultLength = length + padLength * delta;
+    _OneByteString result = _OneByteString._allocate(resultLength);
+    int index = 0;
+    for (int i = 0; i < length; i++) {
+      result._setAt(index++, this.codeUnitAt(i));
+    }
+    if (padLength == 1) {
+      int padChar = padding.codeUnitAt(0);
+      for (int i = 0; i < delta; i++) {
+        result._setAt(index++, padChar);
+      }
+    } else {
+      for (int i = 0; i < delta; i++) {
+        for (int j = 0; j < padLength; j++) {
+          result._setAt(index++, padding.codeUnitAt(j));
+        }
+      }
+    }
+    return result;
+  }
+
+  // Lower-case conversion table for Latin-1 as string.
+  // Upper-case ranges: 0x41-0x5a ('A' - 'Z'), 0xc0-0xd6, 0xd8-0xde.
+  // Conversion to lower case performed by adding 0x20.
+  static const _LC_TABLE =
+      "\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f"
+      "\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f"
+      "\x20\x21\x22\x23\x24\x25\x26\x27\x28\x29\x2a\x2b\x2c\x2d\x2e\x2f"
+      "\x30\x31\x32\x33\x34\x35\x36\x37\x38\x39\x3a\x3b\x3c\x3d\x3e\x3f"
+      "\x40\x61\x62\x63\x64\x65\x66\x67\x68\x69\x6a\x6b\x6c\x6d\x6e\x6f"
+      "\x70\x71\x72\x73\x74\x75\x76\x77\x78\x79\x7a\x5b\x5c\x5d\x5e\x5f"
+      "\x60\x61\x62\x63\x64\x65\x66\x67\x68\x69\x6a\x6b\x6c\x6d\x6e\x6f"
+      "\x70\x71\x72\x73\x74\x75\x76\x77\x78\x79\x7a\x7b\x7c\x7d\x7e\x7f"
+      "\x80\x81\x82\x83\x84\x85\x86\x87\x88\x89\x8a\x8b\x8c\x8d\x8e\x8f"
+      "\x90\x91\x92\x93\x94\x95\x96\x97\x98\x99\x9a\x9b\x9c\x9d\x9e\x9f"
+      "\xa0\xa1\xa2\xa3\xa4\xa5\xa6\xa7\xa8\xa9\xaa\xab\xac\xad\xae\xaf"
+      "\xb0\xb1\xb2\xb3\xb4\xb5\xb6\xb7\xb8\xb9\xba\xbb\xbc\xbd\xbe\xbf"
+      "\xe0\xe1\xe2\xe3\xe4\xe5\xe6\xe7\xe8\xe9\xea\xeb\xec\xed\xee\xef"
+      "\xf0\xf1\xf2\xf3\xf4\xf5\xf6\xd7\xf8\xf9\xfa\xfb\xfc\xfd\xfe\xdf"
+      "\xe0\xe1\xe2\xe3\xe4\xe5\xe6\xe7\xe8\xe9\xea\xeb\xec\xed\xee\xef"
+      "\xf0\xf1\xf2\xf3\xf4\xf5\xf6\xf7\xf8\xf9\xfa\xfb\xfc\xfd\xfe\xff";
+
+  // Upper-case conversion table for Latin-1 as string.
+  // Lower-case ranges: 0x61-0x7a ('a' - 'z'), 0xe0-0xff.
+  // The characters 0xb5 (µ) and 0xff (ÿ) have upper case variants
+  // that are not Latin-1. These are both marked as 0x00 in the table.
+  // The German "sharp s" \xdf (ß) should be converted into two characters (SS),
+  // and is also marked with 0x00.
+  // Conversion to lower case performed by subtracting 0x20.
+  static const _UC_TABLE =
+      "\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f"
+      "\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f"
+      "\x20\x21\x22\x23\x24\x25\x26\x27\x28\x29\x2a\x2b\x2c\x2d\x2e\x2f"
+      "\x30\x31\x32\x33\x34\x35\x36\x37\x38\x39\x3a\x3b\x3c\x3d\x3e\x3f"
+      "\x40\x41\x42\x43\x44\x45\x46\x47\x48\x49\x4a\x4b\x4c\x4d\x4e\x4f"
+      "\x50\x51\x52\x53\x54\x55\x56\x57\x58\x59\x5a\x5b\x5c\x5d\x5e\x5f"
+      "\x60\x41\x42\x43\x44\x45\x46\x47\x48\x49\x4a\x4b\x4c\x4d\x4e\x4f"
+      "\x50\x51\x52\x53\x54\x55\x56\x57\x58\x59\x5a\x7b\x7c\x7d\x7e\x7f"
+      "\x80\x81\x82\x83\x84\x85\x86\x87\x88\x89\x8a\x8b\x8c\x8d\x8e\x8f"
+      "\x90\x91\x92\x93\x94\x95\x96\x97\x98\x99\x9a\x9b\x9c\x9d\x9e\x9f"
+      "\xa0\xa1\xa2\xa3\xa4\xa5\xa6\xa7\xa8\xa9\xaa\xab\xac\xad\xae\xaf"
+      "\xb0\xb1\xb2\xb3\xb4\x00\xb6\xb7\xb8\xb9\xba\xbb\xbc\xbd\xbe\xbf"
+      "\xc0\xc1\xc2\xc3\xc4\xc5\xc6\xc7\xc8\xc9\xca\xcb\xcc\xcd\xce\xcf"
+      "\xd0\xd1\xd2\xd3\xd4\xd5\xd6\xd7\xd8\xd9\xda\xdb\xdc\xdd\xde\x00"
+      "\xc0\xc1\xc2\xc3\xc4\xc5\xc6\xc7\xc8\xc9\xca\xcb\xcc\xcd\xce\xcf"
+      "\xd0\xd1\xd2\xd3\xd4\xd5\xd6\xf7\xd8\xd9\xda\xdb\xdc\xdd\xde\x00";
+
+  String toLowerCase() {
+    for (int i = 0; i < this.length; i++) {
+      final c = this.codeUnitAt(i);
+      if (c == _LC_TABLE.codeUnitAt(c)) continue;
+      // Upper-case character found.
+      final result = _allocate(this.length);
+      for (int j = 0; j < i; j++) {
+        result._setAt(j, this.codeUnitAt(j));
+      }
+      for (int j = i; j < this.length; j++) {
+        result._setAt(j, _LC_TABLE.codeUnitAt(this.codeUnitAt(j)));
+      }
+      return result;
+    }
+    return this;
+  }
+
+  String toUpperCase() {
+    for (int i = 0; i < this.length; i++) {
+      final c = this.codeUnitAt(i);
+      // Continue loop if character is unchanged by upper-case conversion.
+      if (c == _UC_TABLE.codeUnitAt(c)) continue;
+
+      // Check rest of string for characters that do not convert to
+      // single-characters in the Latin-1 range.
+      for (int j = i; j < this.length; j++) {
+        final c = this.codeUnitAt(j);
+        if ((_UC_TABLE.codeUnitAt(c) == 0x00) && (c != 0x00)) {
+          // We use the 0x00 value for characters other than the null character,
+          // that don't convert to a single Latin-1 character when upper-cased.
+          // In that case, call the generic super-class method.
+          return super.toUpperCase();
+        }
+      }
+      // Some lower-case characters found, but all upper-case to single Latin-1
+      // characters.
+      final result = _allocate(this.length);
+      for (int j = 0; j < i; j++) {
+        result._setAt(j, this.codeUnitAt(j));
+      }
+      for (int j = i; j < this.length; j++) {
+        result._setAt(j, _UC_TABLE.codeUnitAt(this.codeUnitAt(j)));
+      }
+      return result;
+    }
+    return this;
+  }
+
+  // Allocates a string of given length, expecting its content to be
+  // set using _setAt.
+
+  static _OneByteString _allocate(int length) {
+    return unsafeCast<_OneByteString>(allocateOneByteString(length));
+  }
+
+  external static _OneByteString _allocateFromOneByteList(
+      List<int> list, int start, int end);
+
+  // This is internal helper method. Code point value must be a valid
+  // Latin1 value (0..0xFF), index must be valid.
+
+  void _setAt(int index, int codePoint) {
+    writeIntoOneByteString(this, index, codePoint);
+  }
+
+  // Should be optimizable to a memory move.
+  // Accepts both _OneByteString and _ExternalOneByteString as argument.
+  // Returns index after last character written.
+  int _setRange(int index, String oneByteString, int start, int end) {
+    assert(oneByteString._isOneByte);
+    assert(0 <= start);
+    assert(start <= end);
+    assert(end <= oneByteString.length);
+    assert(0 <= index);
+    assert(index + (end - start) <= length);
+    for (int i = start; i < end; i++) {
+      _setAt(index, oneByteString.codeUnitAt(i));
+      index += 1;
+    }
+    return index;
+  }
+}
+
+@pragma("wasm:entry-point")
+class _TwoByteString extends _StringBase {
+  @pragma("wasm:entry-point")
+  WasmIntArray<WasmI16> _array;
+
+  _TwoByteString._withLength(int length)
+      : _array = WasmIntArray<WasmI16>(length),
+        super._();
+
+  // Same hash as VM
+  int _computeHashCode() {
+    WasmIntArray<WasmI16> array = _array;
+    int length = array.length;
+    int hash = 0;
+    for (int i = 0; i < length; i++) {
+      hash = _StringBase._combineHashes(hash, array.readUnsigned(i));
+    }
+    return _StringBase._finalizeHash(hash);
+  }
+
+  // Allocates a string of given length, expecting its content to be
+  // set using _setAt.
+
+  static _TwoByteString _allocate(int length) {
+    return unsafeCast<_TwoByteString>(allocateTwoByteString(length));
+  }
+
+  static String _allocateFromTwoByteList(List<int> list, int start, int end) {
+    final int length = end - start;
+    final s = _allocate(length);
+    final array = s._array;
+    for (int i = 0; i < length; i++) {
+      array.write(i, list[start + i]);
+    }
+    return s;
+  }
+
+  // This is internal helper method. Code point value must be a valid
+  // UTF-16 value (0..0xFFFF), index must be valid.
+
+  void _setAt(int index, int codePoint) {
+    writeIntoTwoByteString(this, index, codePoint);
+  }
+
+  bool _isWhitespace(int codeUnit) {
+    return _StringBase._isTwoByteWhitespace(codeUnit);
+  }
+
+  int codeUnitAt(int index) => _array.readUnsigned(index);
+
+  int get length => _array.length;
+
+  bool operator ==(Object other) {
+    return super == other;
+  }
+
+  int _copyIntoTwoByteString(_TwoByteString result, int offset) {
+    final from = _array;
+    final int length = from.length;
+    final to = result._array;
+    int j = offset;
+    for (int i = 0; i < length; i++) {
+      to.write(j++, from.readUnsigned(i));
+    }
+    return j;
+  }
+}
+
+class _StringMatch implements Match {
+  const _StringMatch(this.start, this.input, this.pattern);
+
+  int get end => start + pattern.length;
+  String operator [](int g) => group(g);
+  int get groupCount => 0;
+
+  String group(int group) {
+    if (group != 0) {
+      throw new RangeError.value(group);
+    }
+    return pattern;
+  }
+
+  List<String> groups(List<int> groups) {
+    List<String> result = <String>[];
+    for (int g in groups) {
+      result.add(group(g));
+    }
+    return result;
+  }
+
+  final int start;
+  final String input;
+  final String pattern;
+}
+
+class _StringAllMatchesIterable extends Iterable<Match> {
+  final String _input;
+  final String _pattern;
+  final int _index;
+
+  _StringAllMatchesIterable(this._input, this._pattern, this._index);
+
+  Iterator<Match> get iterator =>
+      new _StringAllMatchesIterator(_input, _pattern, _index);
+
+  Match get first {
+    int index = _input.indexOf(_pattern, _index);
+    if (index >= 0) {
+      return new _StringMatch(index, _input, _pattern);
+    }
+    throw IterableElementError.noElement();
+  }
+}
+
+class _StringAllMatchesIterator implements Iterator<Match> {
+  final String _input;
+  final String _pattern;
+  int _index;
+  Match? _current;
+
+  _StringAllMatchesIterator(this._input, this._pattern, this._index);
+
+  bool moveNext() {
+    if (_index + _pattern.length > _input.length) {
+      _current = null;
+      return false;
+    }
+    var index = _input.indexOf(_pattern, _index);
+    if (index < 0) {
+      _index = _input.length + 1;
+      _current = null;
+      return false;
+    }
+    int end = index + _pattern.length;
+    _current = new _StringMatch(index, _input, _pattern);
+    // Empty match, don't start at same location again.
+    if (end == _index) end++;
+    _index = end;
+    return true;
+  }
+
+  Match get current => _current as Match;
+}
diff --git a/sdk/lib/_internal/wasm/lib/timer_patch.dart b/sdk/lib/_internal/wasm/lib/timer_patch.dart
new file mode 100644
index 0000000..47c7f03
--- /dev/null
+++ b/sdk/lib/_internal/wasm/lib/timer_patch.dart
@@ -0,0 +1,82 @@
+// 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.
+
+// part of "async_patch.dart";
+
+// Implementation of `Timer` and `scheduleMicrotask` via the JS event loop.
+
+import 'dart:_internal' show patch, scheduleCallback;
+
+@patch
+class Timer {
+  @patch
+  static Timer _createTimer(Duration duration, void callback()) {
+    return _OneShotTimer(duration, callback);
+  }
+
+  @patch
+  static Timer _createPeriodicTimer(
+      Duration duration, void callback(Timer timer)) {
+    return _PeriodicTimer(duration, callback);
+  }
+}
+
+abstract class _Timer implements Timer {
+  final double milliseconds;
+  bool isActive;
+  int tick;
+
+  _Timer(Duration duration)
+      : milliseconds = duration.inMilliseconds.toDouble(),
+        isActive = true,
+        tick = 0 {
+    _schedule();
+  }
+
+  void _schedule() {
+    scheduleCallback(milliseconds, () {
+      if (isActive) {
+        tick++;
+        _run();
+      }
+    });
+  }
+
+  void _run();
+
+  @override
+  void cancel() {
+    isActive = false;
+  }
+}
+
+class _OneShotTimer extends _Timer {
+  final void Function() callback;
+
+  _OneShotTimer(Duration duration, this.callback) : super(duration);
+
+  void _run() {
+    isActive = false;
+    callback();
+  }
+}
+
+class _PeriodicTimer extends _Timer {
+  final void Function(Timer) callback;
+
+  _PeriodicTimer(Duration duration, this.callback) : super(duration);
+
+  void _run() {
+    _schedule();
+    callback(this);
+  }
+}
+
+@patch
+class _AsyncRun {
+  @patch
+  static void _scheduleImmediate(void callback()) {
+    scheduleCallback(0, callback);
+  }
+}
diff --git a/sdk/lib/_internal/wasm/lib/type.dart b/sdk/lib/_internal/wasm/lib/type.dart
new file mode 100644
index 0000000..6db370d
--- /dev/null
+++ b/sdk/lib/_internal/wasm/lib/type.dart
@@ -0,0 +1,47 @@
+// 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.
+
+// Representation of runtime types. Can only represent interface types so far,
+// and does not capture nullability.
+
+@pragma("wasm:entry-point")
+class _Type implements Type {
+  final int classId;
+  final List<_Type> typeArguments;
+
+  @pragma("wasm:entry-point")
+  const _Type(this.classId, [this.typeArguments = const []]);
+
+  bool operator ==(Object other) {
+    if (other is! _Type) return false;
+    if (classId != other.classId) return false;
+    for (int i = 0; i < typeArguments.length; i++) {
+      if (typeArguments[i] != other.typeArguments[i]) return false;
+    }
+    return true;
+  }
+
+  int get hashCode {
+    int hash = mix64(classId);
+    for (int i = 0; i < typeArguments.length; i++) {
+      hash = mix64(hash ^ typeArguments[i].hashCode);
+    }
+    return hash;
+  }
+
+  String toString() {
+    StringBuffer s = StringBuffer();
+    s.write("Type");
+    s.write(classId);
+    if (typeArguments.isNotEmpty) {
+      s.write("<");
+      for (int i = 0; i < typeArguments.length; i++) {
+        if (i > 0) s.write(",");
+        s.write(typeArguments[i]);
+      }
+      s.write(">");
+    }
+    return s.toString();
+  }
+}
diff --git a/sdk/lib/libraries.json b/sdk/lib/libraries.json
index 8573ca4..12aee71 100644
--- a/sdk/lib/libraries.json
+++ b/sdk/lib/libraries.json
@@ -154,6 +154,81 @@
       }
     }
   },
+  "wasm": {
+    "libraries": {
+      "_internal": {
+        "uri": "internal/internal.dart",
+        "patches": [
+          "_internal/wasm/lib/internal_patch.dart",
+          "_internal/wasm/lib/class_id.dart",
+          "_internal/wasm/lib/patch.dart",
+          "_internal/wasm/lib/print_patch.dart",
+          "_internal/vm/lib/symbol_patch.dart"
+        ]
+      },
+      "async": {
+        "uri": "async/async.dart",
+        "patches": "_internal/wasm/lib/timer_patch.dart"
+      },
+      "collection": {
+        "uri": "collection/collection.dart",
+        "patches": [
+          "_internal/vm/lib/collection_patch.dart",
+          "_internal/vm/lib/compact_hash.dart",
+          "_internal/wasm/lib/hash_factories.dart"
+        ]
+      },
+      "convert": {
+        "uri": "convert/convert.dart",
+        "patches": "_internal/vm/lib/convert_patch.dart"
+      },
+      "core": {
+        "uri": "core/core.dart",
+        "patches": [
+          "_internal/wasm/lib/core_patch.dart",
+          "_internal/vm/lib/array_patch.dart",
+          "_internal/wasm/lib/bool.dart",
+          "_internal/vm/lib/bool_patch.dart",
+          "_internal/wasm/lib/date_patch.dart",
+          "_internal/wasm/lib/double.dart",
+          "_internal/wasm/lib/expando_patch.dart",
+          "_internal/wasm/lib/function.dart",
+          "_internal/wasm/lib/growable_list.dart",
+          "_internal/wasm/lib/identical_patch.dart",
+          "_internal/wasm/lib/immutable_map.dart",
+          "_internal/wasm/lib/int.dart",
+          "_internal/vm/lib/integers_patch.dart",
+          "_internal/wasm/lib/list.dart",
+          "_internal/vm/lib/null_patch.dart",
+          "_internal/vm/lib/map_patch.dart",
+          "_internal/wasm/lib/object_patch.dart",
+          "_internal/wasm/lib/string_buffer_patch.dart",
+          "_internal/wasm/lib/string_patch.dart",
+          "_internal/wasm/lib/type.dart"
+        ]
+      },
+      "developer": {
+        "uri": "developer/developer.dart",
+        "patches": [
+          "_internal/wasm/lib/developer.dart"
+        ]
+      },
+      "isolate": {
+        "uri": "isolate/isolate.dart"
+      },
+      "math": {
+        "uri": "math/math.dart",
+        "patches": "_internal/wasm/lib/math_patch.dart"
+      },
+      "typed_data": {
+        "uri": "typed_data/typed_data.dart",
+        "patches": "_internal/vm/lib/typed_data_patch.dart"
+      },
+      "wasm": {
+        "uri": "wasm/wasm_types.dart"
+      }
+    }
+  },
   "dart2js": {
     "include": [
       {
diff --git a/sdk/lib/libraries.yaml b/sdk/lib/libraries.yaml
index dcf81c0..a67349c 100644
--- a/sdk/lib/libraries.yaml
+++ b/sdk/lib/libraries.yaml
@@ -152,6 +152,66 @@
     vmservice_io:
       uri: "_internal/vm/bin/vmservice_io.dart"
 
+wasm:
+  libraries:
+    _internal:
+      uri: internal/internal.dart
+      patches:
+      - _internal/wasm/lib/internal_patch.dart
+      - _internal/wasm/lib/class_id.dart
+      - _internal/wasm/lib/patch.dart
+      - _internal/wasm/lib/print_patch.dart
+      - _internal/vm/lib/symbol_patch.dart
+    async:
+      uri: async/async.dart
+      patches: _internal/wasm/lib/timer_patch.dart
+    collection:
+      uri: collection/collection.dart
+      patches:
+      - _internal/vm/lib/collection_patch.dart
+      - _internal/vm/lib/compact_hash.dart
+      - _internal/wasm/lib/hash_factories.dart
+    convert:
+      uri: convert/convert.dart
+      patches: _internal/vm/lib/convert_patch.dart
+    core:
+      uri: core/core.dart
+      patches:
+      - _internal/wasm/lib/core_patch.dart
+      - _internal/vm/lib/array_patch.dart
+      - _internal/wasm/lib/bool.dart
+      - _internal/vm/lib/bool_patch.dart
+      - _internal/wasm/lib/date_patch.dart
+      - _internal/wasm/lib/double.dart
+      - _internal/wasm/lib/expando_patch.dart
+      - _internal/wasm/lib/function.dart
+      - _internal/wasm/lib/growable_list.dart
+      - _internal/wasm/lib/identical_patch.dart
+      - _internal/wasm/lib/immutable_map.dart
+      - _internal/wasm/lib/int.dart
+      - _internal/vm/lib/integers_patch.dart
+      - _internal/wasm/lib/list.dart
+      - _internal/vm/lib/null_patch.dart
+      - _internal/vm/lib/map_patch.dart
+      - _internal/wasm/lib/object_patch.dart
+      - _internal/wasm/lib/string_buffer_patch.dart
+      - _internal/wasm/lib/string_patch.dart
+      - _internal/wasm/lib/type.dart
+    developer:
+      uri: developer/developer.dart
+      patches:
+      - _internal/wasm/lib/developer.dart
+    isolate:
+      uri: isolate/isolate.dart
+    math:
+      uri: math/math.dart
+      patches: _internal/wasm/lib/math_patch.dart
+    typed_data:
+      uri: typed_data/typed_data.dart
+      patches: _internal/vm/lib/typed_data_patch.dart
+    wasm:
+      uri: wasm/wasm_types.dart
+
 dart2js:
   include:
     - target: "_dart2js_common"
diff --git a/sdk/lib/wasm/wasm_types.dart b/sdk/lib/wasm/wasm_types.dart
new file mode 100644
index 0000000..1ec7bb3
--- /dev/null
+++ b/sdk/lib/wasm/wasm_types.dart
@@ -0,0 +1,90 @@
+// 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.
+
+library dart.wasm;
+
+// A collection a special Dart tytpes that are mapped directly to Wasm types
+// by the dart2wasm compiler. These types have a number of constraints:
+//
+// - They can only be used directly as types of local variables, fields, or
+//   parameter/return of static functions. No other uses of the types are valid.
+// - They are not assignable to or from any ordinary Dart types.
+// - The integer and float types can't be nullable.
+//
+// TODO(askesc): Give an error message if any of these constraints are violated.
+
+@pragma("wasm:entry-point")
+abstract class _WasmBase {}
+
+abstract class _WasmInt extends _WasmBase {}
+
+abstract class _WasmFloat extends _WasmBase {}
+
+/// The Wasm `anyref` type.
+@pragma("wasm:entry-point")
+class WasmAnyRef extends _WasmBase {}
+
+/// The Wasm `eqref` type.
+@pragma("wasm:entry-point")
+class WasmEqRef extends WasmAnyRef {}
+
+/// The Wasm `dataref` type.
+@pragma("wasm:entry-point")
+class WasmDataRef extends WasmEqRef {}
+
+abstract class _WasmArray extends WasmDataRef {
+  external int get length;
+}
+
+/// The Wasm `i8` storage type.
+@pragma("wasm:entry-point")
+class WasmI8 extends _WasmInt {}
+
+/// The Wasm `i16` storage type.
+@pragma("wasm:entry-point")
+class WasmI16 extends _WasmInt {}
+
+/// The Wasm `i32` type.
+@pragma("wasm:entry-point")
+class WasmI32 extends _WasmInt {}
+
+/// The Wasm `i64` type.
+@pragma("wasm:entry-point")
+class WasmI64 extends _WasmInt {}
+
+/// The Wasm `f32` type.
+@pragma("wasm:entry-point")
+class WasmF32 extends _WasmFloat {}
+
+/// The Wasm `f64` type.
+@pragma("wasm:entry-point")
+class WasmF64 extends _WasmFloat {}
+
+/// A Wasm array with integer element type.
+@pragma("wasm:entry-point")
+class WasmIntArray<T extends _WasmInt> extends _WasmArray {
+  external factory WasmIntArray(int length);
+
+  external int readSigned(int index);
+  external int readUnsigned(int index);
+  external void write(int index, int value);
+}
+
+/// A Wasm array with float element type.
+@pragma("wasm:entry-point")
+class WasmFloatArray<T extends _WasmFloat> extends _WasmArray {
+  external factory WasmFloatArray(int length);
+
+  external double read(int index);
+  external void write(int index, double value);
+}
+
+/// A Wasm array with reference element type, containing Dart objects.
+@pragma("wasm:entry-point")
+class WasmObjectArray<T extends Object?> extends _WasmArray {
+  external factory WasmObjectArray(int length);
+
+  external T read(int index);
+  external void write(int index, T value);
+}
diff --git a/tests/co19/co19-dart2js.status b/tests/co19/co19-dart2js.status
index 277a778..75ebe8c 100644
--- a/tests/co19/co19-dart2js.status
+++ b/tests/co19/co19-dart2js.status
@@ -42,6 +42,7 @@
 LanguageFeatures/Abstract-external-fields/static_analysis_external_A05_t03: SkipByDesign # Non-JS-interop external members are not supported
 LanguageFeatures/Abstract-external-fields/syntax_A01_t03: SkipByDesign # Non-JS-interop external members are not supported
 LanguageFeatures/Abstract-external-fields/syntax_A02_t03: SkipByDesign # Non-JS-interop external members are not supported
+LanguageFeatures/FinalizationRegistry/ffi/*: SkipByDesign # dart:ffi is not supported
 LibTest/core/DateTime/DateTime.fromMicrosecondsSinceEpoch_A01_t01: SkipByDesign # microseconds are not supported in JavaScript
 LibTest/core/DateTime/microsecond_A01_t01: SkipByDesign # microseconds are not supported in JavaScript
 LibTest/core/DateTime/microsecondsSinceEpoch_A01_t01: SkipByDesign # microseconds are not supported in JavaScript
diff --git a/tests/co19/co19-dartdevc.status b/tests/co19/co19-dartdevc.status
index 3458c5c..94d2a50 100644
--- a/tests/co19/co19-dartdevc.status
+++ b/tests/co19/co19-dartdevc.status
@@ -39,6 +39,7 @@
 LanguageFeatures/Abstract-external-fields/static_analysis_external_A05_t03: SkipByDesign # External variables are not supported
 LanguageFeatures/Abstract-external-fields/syntax_A01_t03: SkipByDesign # External variables are not supported
 LanguageFeatures/Abstract-external-fields/syntax_A02_t03: SkipByDesign # External variables are not supported
+LanguageFeatures/FinalizationRegistry/ffi/*: SkipByDesign # dart:ffi is not supported
 LibTest/core/DateTime/DateTime.fromMicrosecondsSinceEpoch_A01_t01: SkipByDesign # microseconds are not supported in JavaScript
 LibTest/core/DateTime/microsecond_A01_t01: SkipByDesign # microseconds are not supported in JavaScript
 LibTest/core/DateTime/microsecondsSinceEpoch_A01_t01: SkipByDesign # microseconds are not supported in JavaScript
diff --git a/tests/co19/co19-kernel.status b/tests/co19/co19-kernel.status
index 4e3c7d4..3254ec0 100644
--- a/tests/co19/co19-kernel.status
+++ b/tests/co19/co19-kernel.status
@@ -27,10 +27,16 @@
 LibTest/core/List/List_class_A01_t02: Slow, Pass
 LibTest/core/List/List_class_A01_t03: Slow, Pass
 
+[ $runtime == dart_precompiled && $system == windows ]
+LanguageFeatures/FinalizationRegistry/ffi/*: SkipByDesign # https://dartbug.com/40579 Dart C API symbols not available.
+
 [ $runtime == dart_precompiled && ($arch == simarm64 || $arch == simarm64c || $arch == simriscv32 || $arch == simriscv64) ]
 LibTest/collection/ListBase/ListBase_class_A01_t01: SkipSlow # Issue 43036
 LibTest/collection/ListMixin/ListMixin_class_A01_t01: SkipSlow # Issue 43036
 
+[ $arch == simarm || $arch == simarm64 ]
+LanguageFeatures/FinalizationRegistry/ffi/*: SkipByDesign # https://github.com/dart-lang/sdk/issues/37299
+
 # It makes no sense to run any test that uses spawnURI under the simulator
 # as that would involve running CFE (the front end) in simulator mode
 # to compile the URI file specified in spawnURI code.
diff --git a/tests/language/language_dart2wasm.status b/tests/language/language_dart2wasm.status
new file mode 100644
index 0000000..3dea313
--- /dev/null
+++ b/tests/language/language_dart2wasm.status
@@ -0,0 +1,10 @@
+# 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.
+# Sections in this file should contain "$compiler == dart2wasm".
+
+[ $compiler == dart2wasm ]
+vm/*: SkipByDesign # Tests for the VM.
+
+[ $compiler == dart2wasm && $runtime == d8 ]
+import/conditional_string_test: SkipByDesign # No XHR in d8
diff --git a/tools/VERSION b/tools/VERSION
index 17b79a3..9cab9b0 100644
--- a/tools/VERSION
+++ b/tools/VERSION
@@ -27,5 +27,5 @@
 MAJOR 2
 MINOR 17
 PATCH 0
-PRERELEASE 112
+PRERELEASE 113
 PRERELEASE_PATCH 0
\ No newline at end of file
diff --git a/tools/bots/test_matrix.json b/tools/bots/test_matrix.json
index 37803b4..724d094 100644
--- a/tools/bots/test_matrix.json
+++ b/tools/bots/test_matrix.json
@@ -185,6 +185,41 @@
       "xcodebuild/ReleaseX64/dart2js_platform.dill",
       "xcodebuild/ReleaseX64/dart2js_platform_unsound.dill"
     ],
+    "dart2wasm_hostasserts": [
+      ".packages",
+      ".dart_tool/package_config.json",
+      "out/ReleaseX64/dart",
+      "out/ReleaseX64/dart2wasm_platform.dill",
+      "pkg/",
+      "runtime/tests/",
+      "samples-dev/",
+      "samples/",
+      "sdk/",
+      "tests/.dart_tool/package_config.json",
+      "tests/angular/",
+      "tests/co19/co19-analyzer.status",
+      "tests/co19/co19-co19.status",
+      "tests/co19/co19-dart2js.status",
+      "tests/co19/co19-dartdevc.status",
+      "tests/co19/co19-kernel.status",
+      "tests/co19/co19-runtime.status",
+      "tests/corelib/",
+      "tests/web/",
+      "tests/dartdevc/",
+      "tests/language/",
+      "tests/language_2/",
+      "tests/lib/",
+      "tests/light_unittest.dart",
+      "tests/search/",
+      "tests/ffi/",
+      "third_party/d8/",
+      "third_party/pkg/",
+      "third_party/pkg_tested/",
+      "third_party/requirejs/",
+      "tools/",
+      "xcodebuild/ReleaseX64/dart",
+      "xcodebuild/ReleaseX64/dart2wasm_platform.dill"
+    ],
     "front-end": [
       ".packages",
       ".dart_tool/package_config.json",
@@ -705,6 +740,13 @@
         "builder-tag": "dart2js-strong"
       }
     },
+    "dart2wasm-hostasserts-linux-x64-d8": {
+      "options": {
+        "host-checked": true,
+        "timeout": 240,
+        "builder-tag": "dart2wasm"
+      }
+    },
     "dartkp-android-(debug|product|release)-arm_x64": {
       "options": {
         "builder-tag": "crossword",
@@ -2888,6 +2930,33 @@
     },
     {
       "builders": [
+        "dart2wasm-linux-x64-d8"
+      ],
+      "meta": {
+        "description": "dart2wasm tests."
+      },
+      "steps": [
+        {
+          "name": "build dart",
+          "script": "tools/build.py",
+          "arguments": [
+            "create_sdk"
+          ]
+        },
+        {
+          "name": "dart2wasm d8 tests",
+          "arguments": [
+            "-ndart2wasm-hostasserts-linux-x64-d8",
+            "language",
+            "corelib"
+          ],
+          "shards": 6,
+          "fileset": "dart2wasm_hostasserts"
+        }
+      ]
+    },
+    {
+      "builders": [
         "dart-sdk-linux"
       ],
       "meta": {