[dart2wasm] Implement the three-pronged WasmGC type hierarchy.

This splits the type hierarchy into three separate hierarchies with
the top types `any`, `func` and `extern`.

Update d8 to 10.6.91, which switches to the new type hierarchy.

Also, all ref shorthands for abstract heap types are now nullable, so
the type emitter in the `wasm_builder` is updated to follow that scheme.

To reduce confusion about the nullability of abstract reference types,
these now all require the nullability to be specified explicitly.

Change-Id: I4774d08cbed18307e481c466b2e3402a8d8fb6bd
Cq-Include-Trybots: luci.dart.try:dart2wasm-linux-x64-d8-try
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/255060
Reviewed-by: Joshua Litt <joshualitt@google.com>
diff --git a/DEPS b/DEPS
index 52c78ae..b8815654 100644
--- a/DEPS
+++ b/DEPS
@@ -58,7 +58,7 @@
   # Checkout extra javascript engines for testing or benchmarking.
   # d8, the V8 shell, is always checked out.
   "checkout_javascript_engines": False,
-  "d8_tag": "version:10.6.51",
+  "d8_tag": "version:10.6.91",
   "jsshell_tag": "version:95.0",
 
   # As Flutter does, we use Fuchsia's GN and Clang toolchain. These revision
diff --git a/pkg/dart2wasm/lib/class_info.dart b/pkg/dart2wasm/lib/class_info.dart
index 4d6538e..204ab35 100644
--- a/pkg/dart2wasm/lib/class_info.dart
+++ b/pkg/dart2wasm/lib/class_info.dart
@@ -282,7 +282,7 @@
         if (field.isInstanceMember) {
           w.ValueType wasmType = translator.translateType(field.type);
           // TODO(askesc): Generalize this check for finer nullability control
-          if (wasmType != w.RefType.data()) {
+          if (wasmType != w.RefType.data(nullable: false)) {
             wasmType = wasmType.withNullability(true);
           }
           translator.fieldIndex[field] = info.struct.fields.length;
diff --git a/pkg/dart2wasm/lib/code_generator.dart b/pkg/dart2wasm/lib/code_generator.dart
index 426af54..ebd3a2d 100644
--- a/pkg/dart2wasm/lib/code_generator.dart
+++ b/pkg/dart2wasm/lib/code_generator.dart
@@ -339,11 +339,13 @@
       Class cls = member.enclosingClass!;
       ClassInfo info = translator.classInfo[cls]!;
       thisLocal = paramLocals[0];
+      assert(!thisLocal!.type.nullable);
       w.RefType thisType = info.nonNullableType;
       if (translator.needsConversion(paramLocals[0].type, thisType) &&
           !(cls == translator.objectInfo.cls ||
               cls == translator.ffiPointerClass ||
-              translator.isFfiCompound(cls))) {
+              translator.isFfiCompound(cls) ||
+              translator.isWasmType(cls))) {
         preciseThisLocal = addLocal(thisType);
         b.local_get(paramLocals[0]);
         b.ref_cast(info.struct);
@@ -1405,7 +1407,7 @@
 
   @override
   w.ValueType visitEqualsNull(EqualsNull node, w.ValueType expectedType) {
-    wrap(node.expression, const w.RefType.any());
+    wrap(node.expression, const w.RefType.any(nullable: true));
     b.ref_is_null();
     return w.NumType.i32;
   }
diff --git a/pkg/dart2wasm/lib/dispatch_table.dart b/pkg/dart2wasm/lib/dispatch_table.dart
index 1e00b2d..0c744c7 100644
--- a/pkg/dart2wasm/lib/dispatch_table.dart
+++ b/pkg/dart2wasm/lib/dispatch_table.dart
@@ -298,7 +298,7 @@
       }
     }
 
-    wasmTable = m.addTable(w.RefType.func(), table.length);
+    wasmTable = m.addTable(w.RefType.func(nullable: true), table.length);
   }
 
   void output() {
diff --git a/pkg/dart2wasm/lib/functions.dart b/pkg/dart2wasm/lib/functions.dart
index 09e3dfa..10bdcd5 100644
--- a/pkg/dart2wasm/lib/functions.dart
+++ b/pkg/dart2wasm/lib/functions.dart
@@ -28,7 +28,7 @@
   // allocation of that class is encountered
   final Map<int, List<Reference>> _pendingAllocation = {};
 
-  final w.ValueType asyncStackType = const w.RefType.any(nullable: true);
+  final w.ValueType asyncStackType = const w.RefType.extern(nullable: true);
 
   late final w.FunctionType asyncStubFunctionType = m.addFunctionType(
       [const w.RefType.data(nullable: false), asyncStackType],
diff --git a/pkg/dart2wasm/lib/intrinsics.dart b/pkg/dart2wasm/lib/intrinsics.dart
index 7a06523..098c19f 100644
--- a/pkg/dart2wasm/lib/intrinsics.dart
+++ b/pkg/dart2wasm/lib/intrinsics.dart
@@ -347,8 +347,7 @@
     }
 
     // WasmAnyRef.toObject
-    if (cls == translator.wasmAnyRefClass) {
-      assert(name == "toObject");
+    if (cls == translator.wasmAnyRefClass && name == "toObject") {
       w.Label succeed = b.block(const [], [translator.topInfo.nonNullableType]);
       w.Label fail = b.block(const [], const [w.RefType.any(nullable: false)]);
       codeGen.wrap(receiver, w.RefType.any(nullable: false));
@@ -957,21 +956,12 @@
       }
 
       // (WasmFuncRef|WasmFunction).fromRef constructors
-      if ((cls == translator.wasmFuncRefClass ||
-              cls == translator.wasmFunctionClass) &&
-          name == "fromRef") {
+      if (cls == translator.wasmFunctionClass && name == "fromFuncRef") {
         Expression ref = node.arguments.positional[0];
         w.RefType resultType = typeOfExp(node) as w.RefType;
         w.Label succeed = b.block(const [], [resultType]);
-        w.Label fail =
-            b.block(const [], const [w.RefType.any(nullable: false)]);
-        codeGen.wrap(ref, w.RefType.any(nullable: false));
-        b.br_on_non_func(fail);
-        if (cls == translator.wasmFunctionClass) {
-          b.br_on_cast_fail(fail, resultType.heapType as w.FunctionType);
-        }
-        b.br(succeed);
-        b.end(); // fail
+        codeGen.wrap(ref, w.RefType.func(nullable: false));
+        b.br_on_cast(succeed, resultType.heapType as w.FunctionType);
         codeGen.throwWasmRefError("a function with the expected signature");
         b.end(); // succeed
         return resultType;
diff --git a/pkg/dart2wasm/lib/translator.dart b/pkg/dart2wasm/lib/translator.dart
index 4647f1c..99dda3a 100644
--- a/pkg/dart2wasm/lib/translator.dart
+++ b/pkg/dart2wasm/lib/translator.dart
@@ -66,6 +66,7 @@
   late final Class wasmTypesBaseClass;
   late final Class wasmArrayBaseClass;
   late final Class wasmAnyRefClass;
+  late final Class wasmExternRefClass;
   late final Class wasmFuncRefClass;
   late final Class wasmEqRefClass;
   late final Class wasmDataRefClass;
@@ -191,6 +192,7 @@
     wasmTypesBaseClass = lookupWasm("_WasmBase");
     wasmArrayBaseClass = lookupWasm("_WasmArray");
     wasmAnyRefClass = lookupWasm("WasmAnyRef");
+    wasmExternRefClass = lookupWasm("WasmExternRef");
     wasmFuncRefClass = lookupWasm("WasmFuncRef");
     wasmEqRefClass = lookupWasm("WasmEqRef");
     wasmDataRefClass = lookupWasm("WasmDataRef");
@@ -289,6 +291,7 @@
       coreTypes.intClass: w.NumType.i64,
       coreTypes.doubleClass: w.NumType.f64,
       wasmAnyRefClass: const w.RefType.any(nullable: false),
+      wasmExternRefClass: const w.RefType.extern(nullable: false),
       wasmFuncRefClass: const w.RefType.func(nullable: false),
       wasmEqRefClass: const w.RefType.eq(nullable: false),
       wasmDataRefClass: const w.RefType.data(nullable: false),
@@ -500,7 +503,7 @@
         }
         if (isWasmType(cls)) {
           if (builtin.isPrimitive) throw "Wasm numeric types can't be nullable";
-          return (builtin as w.RefType).withNullability(true);
+          return (builtin as w.RefType).withNullability(nullable);
         }
         if (cls == ffiPointerClass) throw "FFI types can't be nullable";
         Class? boxedClass = boxedClasses[builtin];
@@ -602,7 +605,7 @@
 
   w.FunctionType closureFunctionType(int parameterCount) {
     return m.addFunctionType([
-      w.RefType.data(),
+      w.RefType.data(nullable: false),
       ...List<w.ValueType>.filled(parameterCount, topInfo.nullableType)
     ], [
       topInfo.nullableType
@@ -717,7 +720,6 @@
         }
         var heapType = (to as w.RefType).heapType;
         if (heapType is w.FunctionType) {
-          b.ref_as_func();
           b.ref_cast(heapType);
           return;
         }
diff --git a/pkg/wasm_builder/lib/src/instructions.dart b/pkg/wasm_builder/lib/src/instructions.dart
index 0e034f5..69c210b 100644
--- a/pkg/wasm_builder/lib/src/instructions.dart
+++ b/pkg/wasm_builder/lib/src/instructions.dart
@@ -223,7 +223,7 @@
   }
 
   ValueType get _topOfStack {
-    if (!reachable) return RefType.any();
+    if (!reachable) return RefType.top(nullable: true);
     if (_stackTypes.isEmpty) _reportError("Stack underflow");
     return _stackTypes.last;
   }
@@ -896,7 +896,8 @@
 
   /// Emit a `ref.is_null` instruction.
   void ref_is_null() {
-    assert(_verifyTypes(const [RefType.any()], const [NumType.i32],
+    assert(_verifyTypes(
+        const [RefType.top(nullable: true)], const [NumType.i32],
         trace: const ['ref.is_null']));
     writeByte(0xD1);
   }
@@ -911,16 +912,16 @@
 
   /// Emit a `ref.as_non_null` instruction.
   void ref_as_non_null() {
-    assert(_verifyTypes(
-        const [RefType.any()], [_topOfStack.withNullability(false)],
+    assert(_verifyTypes(const [RefType.top(nullable: true)],
+        [_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)],
+    assert(_verifyTypes(const [RefType.top(nullable: true)],
+        [_topOfStack.withNullability(false)],
         trace: ['br_on_null', label]));
     assert(_verifyBranchTypes(label, 1));
     writeByte(0xD4);
@@ -929,7 +930,9 @@
 
   /// Emit a `ref.eq` instruction.
   void ref_eq() {
-    assert(_verifyTypes(const [RefType.eq(), RefType.eq()], const [NumType.i32],
+    assert(_verifyTypes(
+        const [RefType.eq(nullable: true), RefType.eq(nullable: true)],
+        const [NumType.i32],
         trace: const ['ref.eq']));
     writeByte(0xD5);
   }
@@ -937,7 +940,7 @@
   /// 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 [],
+    assert(_verifyTypes(const [RefType.top(nullable: true)], const [],
         trace: ['br_on_non_null', label]));
     writeByte(0xD6);
     _writeLabel(label);
@@ -1104,21 +1107,24 @@
 
   /// Emit an `i31.new` instruction.
   void i31_new() {
-    assert(_verifyTypes(const [NumType.i32], const [RefType.i31()],
+    assert(_verifyTypes(
+        const [NumType.i32], const [RefType.i31(nullable: false)],
         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],
+    assert(_verifyTypes(
+        const [RefType.i31(nullable: false)], 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],
+    assert(_verifyTypes(
+        const [RefType.i31(nullable: false)], const [NumType.i32],
         trace: const ['i31.get_u']));
     writeBytes(const [0xFB, 0x22]);
   }
@@ -1175,63 +1181,41 @@
     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],
+    assert(_verifyTypes(
+        const [RefType.any(nullable: true)], 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],
+    assert(_verifyTypes(
+        const [RefType.any(nullable: true)], 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)],
+    assert(_verifyTypes(const [RefType.any(nullable: true)],
+        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)],
+    assert(_verifyTypes(const [RefType.any(nullable: true)],
+        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],
+    assert(_verifyTypes(const [RefType.any(nullable: true)], [_topOfStack],
         trace: ['br_on_data', label]));
     assert(_verifyBranchTypes(label, 1, const [RefType.data(nullable: false)]));
     writeBytes(const [0xFB, 0x61]);
@@ -1240,28 +1224,18 @@
 
   /// Emit a `br_on_i31` instruction.
   void br_on_i31(Label label) {
-    assert(_verifyTypes(const [RefType.any()], [_topOfStack],
+    assert(_verifyTypes(const [RefType.any(nullable: true)], [_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)],
+    assert(_verifyTypes(const [RefType.any(nullable: true)],
+        const [RefType.data(nullable: false)],
         trace: ['br_on_non_data', label]));
     writeBytes(const [0xFB, 0x64]);
     _writeLabel(label);
@@ -1270,8 +1244,8 @@
   /// 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)],
+    assert(_verifyTypes(const [RefType.any(nullable: true)],
+        const [RefType.i31(nullable: false)],
         trace: ['br_on_non_i31', label]));
     writeBytes(const [0xFB, 0x65]);
     _writeLabel(label);
diff --git a/pkg/wasm_builder/lib/src/module.dart b/pkg/wasm_builder/lib/src/module.dart
index 7187a39..ced6ed4 100644
--- a/pkg/wasm_builder/lib/src/module.dart
+++ b/pkg/wasm_builder/lib/src/module.dart
@@ -449,7 +449,7 @@
       : elements = List.filled(minSize, null);
 
   void setElement(int index, BaseFunction function) {
-    assert(type == RefType.func(),
+    assert(type == RefType.func(nullable: true),
         "Elements are only supported for funcref tables");
     elements[index] = function;
   }
diff --git a/pkg/wasm_builder/lib/src/types.dart b/pkg/wasm_builder/lib/src/types.dart
index d25ab81..498978d 100644
--- a/pkg/wasm_builder/lib/src/types.dart
+++ b/pkg/wasm_builder/lib/src/types.dart
@@ -143,25 +143,29 @@
 
   const RefType._(this.heapType, this.nullable);
 
+  /// Internal top type above any, func and extern. Not a real Wasm ref type.
+  const RefType.top({required bool nullable}) : this._(HeapType.top, nullable);
+
   /// A (possibly nullable) reference to the `any` heap type.
-  const RefType.any({bool nullable = AnyHeapType.defaultNullability})
-      : this._(HeapType.any, nullable);
+  const RefType.extern({required bool nullable})
+      : this._(HeapType.extern, nullable);
+
+  /// A (possibly nullable) reference to the `any` heap type.
+  const RefType.any({required bool nullable}) : this._(HeapType.any, nullable);
 
   /// A (possibly nullable) reference to the `eq` heap type.
-  const RefType.eq({bool nullable = EqHeapType.defaultNullability})
-      : this._(HeapType.eq, nullable);
+  const RefType.eq({required bool nullable}) : this._(HeapType.eq, nullable);
 
   /// A (possibly nullable) reference to the `func` heap type.
-  const RefType.func({bool nullable = FuncHeapType.defaultNullability})
+  const RefType.func({required bool nullable})
       : this._(HeapType.func, nullable);
 
   /// A (possibly nullable) reference to the `data` heap type.
-  const RefType.data({bool nullable = DataHeapType.defaultNullability})
+  const RefType.data({required bool nullable})
       : this._(HeapType.data, nullable);
 
   /// A (possibly nullable) reference to the `i31` heap type.
-  const RefType.i31({bool nullable = I31HeapType.defaultNullability})
-      : this._(HeapType.i31, nullable);
+  const RefType.i31({required bool nullable}) : this._(HeapType.i31, nullable);
 
   /// A (possibly nullable) reference to a custom heap type.
   RefType.def(DefType defType, {required bool nullable})
@@ -209,6 +213,12 @@
 abstract class HeapType implements Serializable {
   const HeapType();
 
+  /// Internal top type above any, func and extern. Not a real Wasm heap type.
+  static const top = TopHeapType._();
+
+  /// The `extern` heap type.
+  static const extern = ExternHeapType._();
+
   /// The `any` heap type.
   static const any = AnyHeapType._();
 
@@ -237,6 +247,46 @@
   bool isStructuralSubtypeOf(HeapType other) => isSubtypeOf(other);
 }
 
+/// Internal top type above any, func and extern. This is only used to specify
+/// input constraints for instructions that are polymorphic across the three
+/// type hierarchies. It's not a real Wasm heap type.
+class TopHeapType extends HeapType {
+  const TopHeapType._();
+
+  @override
+  bool? get nullableByDefault => null;
+
+  @override
+  bool isSubtypeOf(HeapType other) => other == HeapType.top;
+
+  @override
+  void serialize(Serializer s) =>
+      throw "Attempt to serialize internal top type";
+
+  @override
+  String toString() => "#top";
+}
+
+/// 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.top || other == HeapType.extern;
+
+  @override
+  void serialize(Serializer s) => s.writeByte(0x6F);
+
+  @override
+  String toString() => "extern";
+}
+
 /// The `any` heap type.
 class AnyHeapType extends HeapType {
   const AnyHeapType._();
@@ -247,10 +297,11 @@
   bool? get nullableByDefault => defaultNullability;
 
   @override
-  bool isSubtypeOf(HeapType other) => other == HeapType.any;
+  bool isSubtypeOf(HeapType other) =>
+      other == HeapType.top || other == HeapType.any;
 
   @override
-  void serialize(Serializer s) => s.writeByte(0x6F);
+  void serialize(Serializer s) => s.writeByte(0x6E);
 
   @override
   String toString() => "any";
@@ -267,7 +318,7 @@
 
   @override
   bool isSubtypeOf(HeapType other) =>
-      other == HeapType.any || other == HeapType.eq;
+      other == HeapType.top || other == HeapType.any || other == HeapType.eq;
 
   @override
   void serialize(Serializer s) => s.writeByte(0x6D);
@@ -287,7 +338,7 @@
 
   @override
   bool isSubtypeOf(HeapType other) =>
-      other == HeapType.any || other == HeapType.func;
+      other == HeapType.top || other == HeapType.func;
 
   @override
   void serialize(Serializer s) => s.writeByte(0x70);
@@ -300,14 +351,17 @@
 class DataHeapType extends HeapType {
   const DataHeapType._();
 
-  static const defaultNullability = false;
+  static const defaultNullability = true;
 
   @override
   bool? get nullableByDefault => defaultNullability;
 
   @override
   bool isSubtypeOf(HeapType other) =>
-      other == HeapType.any || other == HeapType.eq || other == HeapType.data;
+      other == HeapType.top ||
+      other == HeapType.any ||
+      other == HeapType.eq ||
+      other == HeapType.data;
 
   @override
   void serialize(Serializer s) => s.writeByte(0x67);
@@ -320,14 +374,17 @@
 class I31HeapType extends HeapType {
   const I31HeapType._();
 
-  static const defaultNullability = false;
+  static const defaultNullability = true;
 
   @override
   bool? get nullableByDefault => defaultNullability;
 
   @override
   bool isSubtypeOf(HeapType other) =>
-      other == HeapType.any || other == HeapType.eq || other == HeapType.i31;
+      other == HeapType.top ||
+      other == HeapType.any ||
+      other == HeapType.eq ||
+      other == HeapType.i31;
 
   @override
   void serialize(Serializer s) => s.writeByte(0x6A);
@@ -401,7 +458,7 @@
 
   @override
   bool isStructuralSubtypeOf(HeapType other) {
-    if (other == HeapType.any || other == HeapType.func) return true;
+    if (other == HeapType.top || 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;
@@ -455,7 +512,8 @@
 
   @override
   bool isStructuralSubtypeOf(HeapType other) {
-    if (other == HeapType.any ||
+    if (other == HeapType.top ||
+        other == HeapType.any ||
         other == HeapType.eq ||
         other == HeapType.data) {
       return true;
@@ -492,7 +550,8 @@
 
   @override
   bool isStructuralSubtypeOf(HeapType other) {
-    if (other == HeapType.any ||
+    if (other == HeapType.top ||
+        other == HeapType.any ||
         other == HeapType.eq ||
         other == HeapType.data) {
       return true;
diff --git a/sdk/lib/_internal/wasm/lib/async_patch.dart b/sdk/lib/_internal/wasm/lib/async_patch.dart
index 8d112bd..ba75660 100644
--- a/sdk/lib/_internal/wasm/lib/async_patch.dart
+++ b/sdk/lib/_internal/wasm/lib/async_patch.dart
@@ -64,7 +64,7 @@
 
 @pragma("wasm:export", "\$asyncBridge")
 WasmAnyRef? _asyncBridge(
-    WasmAnyRef? stack, WasmDataRef args, Completer<Object?> completer) {
+    WasmExternRef? stack, WasmDataRef args, Completer<Object?> completer) {
   try {
     Object? result = _asyncBridge2(args, stack);
     completer.complete(result);
@@ -73,7 +73,7 @@
   }
 }
 
-external Object? _asyncBridge2(WasmDataRef args, WasmAnyRef? stack);
+external Object? _asyncBridge2(WasmDataRef args, WasmExternRef? stack);
 
 class _FutureError {
   final Object exception;
@@ -83,10 +83,11 @@
 }
 
 @pragma("wasm:entry-point")
-Object? _awaitHelper(Object? operand, WasmAnyRef? stack) {
+Object? _awaitHelper(Object? operand, WasmExternRef? stack) {
   if (operand is! Future) return operand;
-  WasmAnyRef futureRef = WasmAnyRef.fromObject(operand);
-  Object? result = unsafeCastOpaque(_futurePromise(stack, futureRef));
+  WasmExternRef futureRef = WasmAnyRef.fromObject(operand).externalize();
+  Object? result =
+      unsafeCastOpaque(_futurePromise(stack, futureRef).internalize());
   if (result is _FutureError) {
     // TODO(askesc): Combine stack traces
     throw result.exception;
@@ -95,7 +96,8 @@
 }
 
 @pragma("wasm:import", "dart2wasm.futurePromise")
-external WasmAnyRef? _futurePromise(WasmAnyRef? stack, WasmAnyRef? future);
+external WasmExternRef? _futurePromise(
+    WasmExternRef? stack, WasmExternRef? future);
 
 @pragma("wasm:export", "\$awaitCallback")
 void _awaitCallback(Future<Object?> future, WasmAnyRef resolve) {
diff --git a/sdk/lib/wasm/wasm_types.dart b/sdk/lib/wasm/wasm_types.dart
index 8139271..0508a0a 100644
--- a/sdk/lib/wasm/wasm_types.dart
+++ b/sdk/lib/wasm/wasm_types.dart
@@ -34,18 +34,40 @@
   ///
   /// Will throw if the reference is not a Dart object.
   external Object toObject();
+
+  WasmExternRef externalize() => _externalizeNonNullable(this);
 }
 
+extension ExternalizeNullable on WasmAnyRef? {
+  WasmExternRef? externalize() => _externalizeNullable(this);
+}
+
+/// The Wasm `externref` type.
+@pragma("wasm:entry-point")
+class WasmExternRef extends _WasmBase {
+  WasmAnyRef internalize() => _internalizeNonNullable(this);
+}
+
+extension InternalizeNullable on WasmExternRef? {
+  WasmAnyRef? internalize() => _internalizeNullable(this);
+}
+
+// TODO(askesc): Add intrinsics for these when the `intern.externalize` and
+// `extern.internalize` instructions are implemented in V8.
+@pragma("wasm:import", "dart2wasm.roundtrip")
+external WasmExternRef _externalizeNonNullable(WasmAnyRef ref);
+@pragma("wasm:import", "dart2wasm.roundtrip")
+external WasmExternRef? _externalizeNullable(WasmAnyRef? ref);
+@pragma("wasm:import", "dart2wasm.roundtrip")
+external WasmAnyRef _internalizeNonNullable(WasmExternRef ref);
+@pragma("wasm:import", "dart2wasm.roundtrip")
+external WasmAnyRef? _internalizeNullable(WasmExternRef? ref);
+
 /// The Wasm `funcref` type.
 @pragma("wasm:entry-point")
-class WasmFuncRef extends WasmAnyRef {
+class WasmFuncRef extends _WasmBase {
   /// Upcast typed function reference to `funcref`
   external factory WasmFuncRef.fromWasmFunction(WasmFunction<Function> fun);
-
-  /// Downcast `anyref` to `funcref`.
-  ///
-  /// Will throw if the reference is not a `funcref`.
-  external factory WasmFuncRef.fromRef(WasmAnyRef ref);
 }
 
 /// The Wasm `eqref` type.
@@ -143,10 +165,10 @@
   /// parameters and no type parameters.
   external factory WasmFunction.fromFunction(F f);
 
-  /// Downcast `anyref` to a typed function reference.
+  /// Downcast `funcref` to a typed function reference.
   ///
   /// Will throw if the reference is not a function with the expected signature.
-  external factory WasmFunction.fromRef(WasmAnyRef ref);
+  external factory WasmFunction.fromFuncRef(WasmFuncRef ref);
 
   /// Call the function referred to by this typed function reference.
   @pragma("wasm:entry-point")
diff --git a/tests/web/wasm/table_test.dart b/tests/web/wasm/table_test.dart
index fb6c31c..df95963 100644
--- a/tests/web/wasm/table_test.dart
+++ b/tests/web/wasm/table_test.dart
@@ -25,12 +25,13 @@
   funcrefTable[2.toWasmI32()] = WasmFunction.fromFunction(f3);
 
   // Reading and calling functions in untyped function table
-  WasmFunction<void Function()>.fromRef(funcrefTable[0.toWasmI32()]!).call();
-  WasmFunction<void Function(int)>.fromRef(funcrefTable[1.toWasmI32()]!)
+  WasmFunction<void Function()>.fromFuncRef(funcrefTable[0.toWasmI32()]!)
+      .call();
+  WasmFunction<void Function(int)>.fromFuncRef(funcrefTable[1.toWasmI32()]!)
       .call(4);
   Expect.equals(
       6,
-      WasmFunction<int Function(int)>.fromRef(funcrefTable[2.toWasmI32()]!)
+      WasmFunction<int Function(int)>.fromFuncRef(funcrefTable[2.toWasmI32()]!)
           .call(5));
 
   // Calling functions in untyped function table with callIndirect
diff --git a/tests/web/wasm/wasm_types_test.dart b/tests/web/wasm/wasm_types_test.dart
index 5538605..0ce54a3 100644
--- a/tests/web/wasm/wasm_types_test.dart
+++ b/tests/web/wasm/wasm_types_test.dart
@@ -14,7 +14,7 @@
 
 @pragma("wasm:import", "Reflect.apply")
 external WasmAnyRef apply(
-    WasmAnyRef target, WasmAnyRef thisArgument, WasmAnyRef argumentsList);
+    WasmFuncRef target, WasmAnyRef thisArgument, WasmAnyRef argumentsList);
 
 WasmAnyRef? anyRef;
 WasmEqRef? eqRef;
@@ -78,18 +78,11 @@
   var ff = WasmFunction.fromFunction(fun);
   ff.call(dartObjectRef);
   apply(ff, createObject(null), singularArray(dartObjectRef));
-  Expect.isFalse(ff.isObject);
 
   // Cast a typed function reference to a `funcref` and back.
   WasmFuncRef funcref = WasmFuncRef.fromWasmFunction(ff);
-  var ff2 = WasmFunction<void Function(WasmEqRef)>.fromRef(funcref);
+  var ff2 = WasmFunction<void Function(WasmEqRef)>.fromFuncRef(funcref);
   ff2.call(dartObjectRef);
-  Expect.isFalse(ff2.isObject);
-
-  // Casting a non-function JS object to a typed function reference throws.
-  Expect.throws(() {
-    WasmFunction<double Function(double)>.fromRef(jsObject1);
-  }, (_) => true);
 
   // Create a typed function reference from an import and call it.
   var createObjectFun = WasmFunction.fromFunction(createObject);