Version 2.18.0-74.0.dev

Merge commit '6055226a21de6c614afd0bc93ae2d2d41b6c367e' into 'dev'
diff --git a/pkg/dart2wasm/lib/class_info.dart b/pkg/dart2wasm/lib/class_info.dart
index 27986fc..ac2fdda 100644
--- a/pkg/dart2wasm/lib/class_info.dart
+++ b/pkg/dart2wasm/lib/class_info.dart
@@ -25,7 +25,7 @@
   static const hashBaseData = 4;
   static const closureContext = 2;
   static const closureFunction = 3;
-  static const typeTypeArguments = 3;
+  static const interfaceTypeTypeArguments = 4;
   static const typedListBaseLength = 2;
   static const typedListArray = 3;
   static const typedListViewTypedData = 3;
@@ -51,7 +51,8 @@
     check(translator.hashFieldBaseClass, "_index", FieldIndex.hashBaseIndex);
     check(translator.hashFieldBaseClass, "_data", FieldIndex.hashBaseData);
     check(translator.functionClass, "context", FieldIndex.closureContext);
-    check(translator.typeClass, "typeArguments", FieldIndex.typeTypeArguments);
+    check(translator.interfaceTypeClass, "typeArguments",
+        FieldIndex.interfaceTypeTypeArguments);
   }
 }
 
diff --git a/pkg/dart2wasm/lib/constants.dart b/pkg/dart2wasm/lib/constants.dart
index 412bd0b..6ed2e32 100644
--- a/pkg/dart2wasm/lib/constants.dart
+++ b/pkg/dart2wasm/lib/constants.dart
@@ -7,6 +7,7 @@
 
 import 'package:dart2wasm/class_info.dart';
 import 'package:dart2wasm/translator.dart';
+import 'package:dart2wasm/types.dart';
 
 import 'package:kernel/ast.dart';
 import 'package:kernel/type_algebra.dart' show substitute;
@@ -367,6 +368,7 @@
   ConstantCreator(this.constants);
 
   Translator get translator => constants.translator;
+  Types get types => translator.types;
   w.Module get m => constants.m;
   bool get lazyConstants => constants.lazyConstants;
 
@@ -682,36 +684,36 @@
 
   @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";
+    DartType type = constant.type;
+    assert(type is! TypeParameterType);
 
-    ListConstant typeArgs = ListConstant(
-        InterfaceType(translator.typeClass, Nullability.nonNullable),
-        type.typeArguments.map((t) => TypeLiteralConstant(t)).toList());
-    ensureConstant(typeArgs);
-
-    ClassInfo info = constants.typeInfo;
+    ClassInfo info = translator.classInfo[types.classForType(type)]!;
     translator.functions.allocateClass(info.classId);
-    return createConstant(constant, info.nonNullableType, (function, b) {
-      ClassInfo typeInfo = translator.classInfo[type.classNode]!;
-      w.ValueType typeListExpectedType =
-          info.struct.fields[FieldIndex.typeTypeArguments].type.unpacked;
+    if (type is InterfaceType) {
+      ListConstant typeArgs = ListConstant(
+          InterfaceType(translator.typeClass, Nullability.nonNullable),
+          type.typeArguments.map((t) => TypeLiteralConstant(t)).toList());
+      ensureConstant(typeArgs);
+      return createConstant(constant, info.nonNullableType, (function, b) {
+        ClassInfo typeInfo = translator.classInfo[type.classNode]!;
+        w.ValueType typeListExpectedType = info
+            .struct.fields[FieldIndex.interfaceTypeTypeArguments].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);
-    });
+        b.i32_const(info.classId);
+        b.i32_const(initialIdentityHash);
+        b.i64_const(typeInfo.classId);
+        b.i32_const(types.isNullable(type) ? 1 : 0);
+        constants.instantiateConstant(
+            function, b, typeArgs, typeListExpectedType);
+        translator.struct_new(b, info);
+      });
+    } else {
+      // TODO(joshualitt): Real implementation for complex types.
+      return createConstant(constant, info.nonNullableType, (function, b) {
+        b.i32_const(info.classId);
+        b.i32_const(initialIdentityHash);
+        translator.struct_new(b, info);
+      });
+    }
   }
 }
diff --git a/pkg/dart2wasm/lib/intrinsics.dart b/pkg/dart2wasm/lib/intrinsics.dart
index 84535f6..755ac27 100644
--- a/pkg/dart2wasm/lib/intrinsics.dart
+++ b/pkg/dart2wasm/lib/intrinsics.dart
@@ -639,6 +639,8 @@
           codeGen.wrap(stackTrace, stackTraceType);
           b.throw_(translator.exceptionTag);
           return codeGen.voidMarker;
+        case "_getSubtypeMap":
+          return translator.types.makeSubtypeMap(b);
       }
     }
 
@@ -971,19 +973,23 @@
     }
 
     // Object.runtimeType
+    // TODO(joshualitt): Implement this correctly for [FunctionType] and
+    // [InterfaceType].
     if (member.enclosingClass == translator.coreTypes.objectClass &&
         name == "runtimeType") {
       w.Local receiver = paramLocals[0];
-      ClassInfo info = translator.classInfo[translator.typeClass]!;
+      ClassInfo info = translator.classInfo[translator.interfaceTypeClass]!;
       translator.functions.allocateClass(info.classId);
-      w.ValueType typeListExpectedType =
-          info.struct.fields[FieldIndex.typeTypeArguments].type.unpacked;
+      w.ValueType typeListExpectedType = info
+          .struct.fields[FieldIndex.interfaceTypeTypeArguments].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();
+      // Runtime types are never nullable.
+      b.i32_const(0);
       // TODO(askesc): Type arguments
       b.global_get(translator.constants.emptyTypeList);
       translator.convertType(function,
diff --git a/pkg/dart2wasm/lib/translator.dart b/pkg/dart2wasm/lib/translator.dart
index fa258cf..e019025 100644
--- a/pkg/dart2wasm/lib/translator.dart
+++ b/pkg/dart2wasm/lib/translator.dart
@@ -89,6 +89,14 @@
   late final Class oneByteStringClass;
   late final Class twoByteStringClass;
   late final Class typeClass;
+  late final Class neverTypeClass;
+  late final Class dynamicTypeClass;
+  late final Class voidTypeClass;
+  late final Class nullTypeClass;
+  late final Class futureOrTypeClass;
+  late final Class interfaceTypeClass;
+  late final Class functionTypeClass;
+  late final Class genericFunctionTypeClass;
   late final Class stackTraceClass;
   late final Class ffiCompoundClass;
   late final Class ffiPointerClass;
@@ -97,6 +105,7 @@
   late final Class typedListViewClass;
   late final Class byteDataViewClass;
   late final Class typeErrorClass;
+  late final Class typeUniverseClass;
   late final Procedure wasmFunctionCall;
   late final Procedure stackTraceCurrent;
   late final Procedure stringEquals;
@@ -109,6 +118,8 @@
   late final Procedure setFactory;
   late final Procedure setAdd;
   late final Procedure hashImmutableIndexNullable;
+  // TODO(joshualitt): Wire up runtime type checks.
+  late final Procedure isSubtype;
   late final Map<Class, w.StorageType> builtinTypes;
   late final Map<w.ValueType, Class> boxedClasses;
 
@@ -157,6 +168,7 @@
     classInfoCollector = ClassInfoCollector(this);
     dispatchTable = DispatchTable(this);
     functions = FunctionCollector(this);
+    types = Types(this);
 
     Class Function(String) makeLookup(String libraryName) {
       Library library =
@@ -192,7 +204,16 @@
     oneByteStringClass = lookupCore("_OneByteString");
     twoByteStringClass = lookupCore("_TwoByteString");
     typeClass = lookupCore("_Type");
+    neverTypeClass = lookupCore("_NeverType");
+    dynamicTypeClass = lookupCore("_DynamicType");
+    voidTypeClass = lookupCore("_VoidType");
+    nullTypeClass = lookupCore("_NullType");
+    futureOrTypeClass = lookupCore("_FutureOrType");
+    interfaceTypeClass = lookupCore("_InterfaceType");
+    functionTypeClass = lookupCore("_FunctionType");
+    genericFunctionTypeClass = lookupCore("_GenericFunctionType");
     stackTraceClass = lookupCore("StackTrace");
+    typeUniverseClass = lookupCore("_TypeUniverse");
     ffiCompoundClass = lookupFfi("_Compound");
     ffiPointerClass = lookupFfi("Pointer");
     typeErrorClass = lookupCore("_TypeError");
@@ -229,6 +250,10 @@
     hashImmutableIndexNullable = lookupCollection("_HashAbstractImmutableBase")
         .procedures
         .firstWhere((p) => p.name.text == "_indexNullable");
+    isSubtype = component.libraries
+        .firstWhere((l) => l.name == "dart.core")
+        .procedures
+        .firstWhere((p) => p.name.text == "_isSubtype");
     builtinTypes = {
       coreTypes.boolClass: w.NumType.i32,
       coreTypes.intClass: w.NumType.i64,
@@ -271,7 +296,6 @@
 
     globals = Globals(this);
     constants = Constants(this);
-    types = Types(this);
 
     dispatchTable.build();
 
diff --git a/pkg/dart2wasm/lib/types.dart b/pkg/dart2wasm/lib/types.dart
index f9ee6a4..f517453 100644
--- a/pkg/dart2wasm/lib/types.dart
+++ b/pkg/dart2wasm/lib/types.dart
@@ -16,10 +16,46 @@
 
   Types(this.translator);
 
-  List<Class> _getConcreteSubtypes(Class cls) => translator.subtypes
-      .getSubtypesOf(cls)
-      .where((c) => !c.isAbstract)
-      .toList();
+  Iterable<Class> _getConcreteSubtypes(Class cls) =>
+      translator.subtypes.getSubtypesOf(cls).where((c) => !c.isAbstract);
+
+  /// Build a [Map<int, List<int>>] to store subtype information.
+  Map<int, List<int>> _buildSubtypeMap() {
+    List<ClassInfo> classes = translator.classes;
+    Map<int, List<int>> subtypeMap = {};
+    for (ClassInfo classInfo in classes) {
+      if (classInfo.cls == null) continue;
+      List<int> classIds = _getConcreteSubtypes(classInfo.cls!)
+          .map((cls) => translator.classInfo[cls]!.classId)
+          .where((classId) => classId != classInfo.classId)
+          .toList();
+
+      if (classIds.isEmpty) continue;
+      subtypeMap[classInfo.classId] = classIds;
+    }
+    return subtypeMap;
+  }
+
+  /// Builds the subtype map and pushes it onto the stack.
+  w.ValueType makeSubtypeMap(w.Instructions b) {
+    // Instantiate subtype map constant.
+    Map<int, List<int>> subtypeMap = _buildSubtypeMap();
+    ClassInfo immutableMapInfo =
+        translator.classInfo[translator.immutableMapClass]!;
+    w.ValueType expectedType = immutableMapInfo.nonNullableType;
+    DartType mapAndSetKeyType = translator.coreTypes.intNonNullableRawType;
+    DartType mapValueType = InterfaceType(translator.immutableListClass,
+        Nullability.nonNullable, [mapAndSetKeyType]);
+    List<ConstantMapEntry> entries = subtypeMap.entries.map((mapEntry) {
+      return ConstantMapEntry(
+          IntConstant(mapEntry.key),
+          ListConstant(mapAndSetKeyType,
+              mapEntry.value.map((i) => IntConstant(i)).toList()));
+    }).toList();
+    translator.constants.instantiateConstant(null, b,
+        MapConstant(mapAndSetKeyType, mapValueType, entries), expectedType);
+    return expectedType;
+  }
 
   bool _isTypeConstant(DartType type) {
     return type is DynamicType ||
@@ -30,7 +66,32 @@
         type is InterfaceType && type.typeArguments.every(_isTypeConstant);
   }
 
+  Class classForType(DartType type) {
+    if (type is DynamicType) {
+      return translator.dynamicTypeClass;
+    } else if (type is VoidType) {
+      return translator.voidTypeClass;
+    } else if (type is NeverType) {
+      return translator.neverTypeClass;
+    } else if (type is NullType) {
+      return translator.nullTypeClass;
+    } else if (type is FutureOrType) {
+      return translator.futureOrTypeClass;
+    } else if (type is InterfaceType) {
+      return translator.interfaceTypeClass;
+    } else if (type is FunctionType) {
+      if (type.typeParameters.isEmpty) {
+        return translator.functionTypeClass;
+      } else {
+        return translator.genericFunctionTypeClass;
+      }
+    }
+    throw "Unexpected DartType: $type";
+  }
+
   /// Makes a `_Type` object on the stack.
+  /// TODO(joshualitt): Refactor this logic to remove the dependency on
+  /// CodeGenerator.
   w.ValueType makeType(CodeGenerator codeGen, DartType type, TreeNode node) {
     w.ValueType typeType =
         translator.classInfo[translator.typeClass]!.nullableType;
@@ -62,23 +123,25 @@
       b.struct_get(info.struct, fieldIndex);
       return typeType;
     }
-    ClassInfo info = translator.classInfo[translator.typeClass]!;
+    ClassInfo info = translator.classInfo[classForType(type)]!;
     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) {
-      codeGen.unimplemented(node, type, [info.nullableType]);
-      return info.nullableType;
+      if (type is FutureOrType || type is FunctionType) {
+        // TODO(joshualitt): Finish RTI.
+        print("Not implemented: RTI ${type}");
+      }
+      b.i32_const(info.classId);
+      b.i32_const(initialIdentityHash);
+      translator.struct_new(b, info);
+      return info.nonNullableType;
     }
     ClassInfo typeInfo = translator.classInfo[type.classNode]!;
     w.ValueType typeListExpectedType =
-        info.struct.fields[FieldIndex.typeTypeArguments].type.unpacked;
+        info.struct.fields[FieldIndex.interfaceTypeTypeArguments].type.unpacked;
     b.i32_const(info.classId);
     b.i32_const(initialIdentityHash);
     b.i64_const(typeInfo.classId);
+    b.i32_const(isNullable(type) ? 1 : 0);
     w.DefinedFunction function = codeGen.function;
     if (type.typeArguments.isEmpty) {
       b.global_get(translator.constants.emptyTypeList);
@@ -99,11 +162,12 @@
       translator.convertType(function, listType, typeListExpectedType);
     }
     translator.struct_new(b, info);
-    return info.nullableType;
+    return info.nonNullableType;
   }
 
   /// 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.
+  /// TODO(joshualitt): Remove dependency on [CodeGenerator]
   void emitTypeTest(CodeGenerator codeGen, DartType type, DartType operandType,
       TreeNode node) {
     w.Instructions b = codeGen.b;
@@ -115,9 +179,9 @@
       b.i32_const(1);
       return;
     }
-    bool isNullable = operandType.isPotentiallyNullable;
+    bool isPotentiallyNullable = operandType.isPotentiallyNullable;
     w.Label? resultLabel;
-    if (isNullable) {
+    if (isPotentiallyNullable) {
       // Store operand in a temporary variable, since Binaryen does not support
       // block inputs.
       w.Local operand = codeGen.addLocal(translator.topInfo.nullableType);
@@ -140,7 +204,7 @@
             " at ${node.location}");
       }
     }
-    List<Class> concrete = _getConcreteSubtypes(type.classNode);
+    List<Class> concrete = _getConcreteSubtypes(type.classNode).toList();
     if (type.classNode == translator.coreTypes.functionClass) {
       ClassInfo functionInfo = translator.classInfo[translator.functionClass]!;
       translator.ref_test(b, functionInfo);
@@ -169,11 +233,19 @@
       b.i32_const(0);
       b.end(); // done
     }
-    if (isNullable) {
+    if (isPotentiallyNullable) {
       b.br(resultLabel!);
       b.end(); // nullLabel
-      b.i32_const(type.declaredNullability == Nullability.nullable ? 1 : 0);
+      b.i32_const(isNullable(type) ? 1 : 0);
       b.end(); // resultLabel
     }
   }
+
+  bool isNullable(InterfaceType type) {
+    Nullability nullability = type.declaredNullability;
+    // TODO(joshualitt): Enable assert when spurious 'legacy' values are fixed.
+    // assert(nullability == Nullability.nonNullable ||
+    //    nullability == Nullability.nullable);
+    return nullability == Nullability.nullable ? true : false;
+  }
 }
diff --git a/sdk/lib/_internal/wasm/lib/class_id.dart b/sdk/lib/_internal/wasm/lib/class_id.dart
index be91738..b9535e9 100644
--- a/sdk/lib/_internal/wasm/lib/class_id.dart
+++ b/sdk/lib/_internal/wasm/lib/class_id.dart
@@ -14,6 +14,26 @@
   external static int get cidUint8Array;
   @pragma("wasm:class-id", "dart.typed_data#_Uint8ArrayView")
   external static int get cidUint8ArrayView;
+  @pragma("wasm:class-id", "dart.core#Object")
+  external static int get cidObject;
+
+  // Class IDs for RTI Types.
+  @pragma("wasm:class-id", "dart.core#_NeverType")
+  external static int get cidNeverType;
+  @pragma("wasm:class-id", "dart.core#_DynamicType")
+  external static int get cidDynamicType;
+  @pragma("wasm:class-id", "dart.core#_VoidType")
+  external static int get cidVoidType;
+  @pragma("wasm:class-id", "dart.core#_NullType")
+  external static int get cidNullType;
+  @pragma("wasm:class-id", "dart.core#_FutureOrType")
+  external static int get cidFutureOrType;
+  @pragma("wasm:class-id", "dart.core#_InterfaceType")
+  external static int get cidInterfaceType;
+  @pragma("wasm:class-id", "dart.core#_FunctionType")
+  external static int get cidFunctionType;
+  @pragma("wasm:class-id", "dart.core#_GenericFunctionType")
+  external static int get cidGenericFunctionType;
 
   // 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
index 64b7d9d..29fe06c 100644
--- a/sdk/lib/_internal/wasm/lib/core_patch.dart
+++ b/sdk/lib/_internal/wasm/lib/core_patch.dart
@@ -9,6 +9,7 @@
         allocateOneByteString,
         allocateTwoByteString,
         CodeUnits,
+        ClassID,
         copyRangeFromUint8ListToOneByteString,
         doubleToIntBits,
         EfficientLengthIterable,
diff --git a/sdk/lib/_internal/wasm/lib/type.dart b/sdk/lib/_internal/wasm/lib/type.dart
index 6db370d..f775a22 100644
--- a/sdk/lib/_internal/wasm/lib/type.dart
+++ b/sdk/lib/_internal/wasm/lib/type.dart
@@ -2,37 +2,105 @@
 // 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.
+//import 'dart:_internal' show ClassID;
+
+// Representation of runtime types. Code in this file should avoid using `is` or
+// `as` entirely to avoid a dependency on any inline type checks.
+
+// TODO(joshualitt): Once we have RTI fully working, we'd like to explore
+// implementing [isSubtype] using inheritance.
+abstract class _Type implements Type {
+  const _Type();
+
+  bool _testID(int value) => ClassID.getID(this) == value;
+  bool get isNever => _testID(ClassID.cidNeverType);
+  bool get isDynamic => _testID(ClassID.cidDynamicType);
+  bool get isVoid => _testID(ClassID.cidVoidType);
+  bool get isFutureOr => _testID(ClassID.cidFutureOrType);
+  bool get isInterface => _testID(ClassID.cidInterfaceType);
+  bool get isFunction => _testID(ClassID.cidFunctionType);
+  bool get isGenericFunctionType => _testID(ClassID.cidGenericFunctionType);
+  bool get isNullable => false;
+
+  T as<T>() => unsafeCast<T>(this);
+
+  @override
+  bool operator ==(Object other) => ClassID.getID(this) == ClassID.getID(other);
+
+  @override
+  int get hashCode => mix64(ClassID.getID(this));
+}
 
 @pragma("wasm:entry-point")
-class _Type implements Type {
+class _NeverType extends _Type {
+  @override
+  String toString() => 'Never';
+}
+
+@pragma("wasm:entry-point")
+class _DynamicType extends _Type {
+  @override
+  String toString() => 'dynamic';
+}
+
+@pragma("wasm:entry-point")
+class _VoidType extends _Type {
+  @override
+  String toString() => 'void';
+}
+
+@pragma("wasm:entry-point")
+class _NullType extends _Type {
+  @override
+  String toString() => 'Null';
+}
+
+@pragma("wasm:entry-point")
+class _FutureOrType extends _Type {
+  // TODO(joshualitt): Implement.
+  @override
+  String toString() => 'FutureOr';
+}
+
+class _InterfaceType extends _Type {
   final int classId;
+  final bool declaredNullable;
   final List<_Type> typeArguments;
 
   @pragma("wasm:entry-point")
-  const _Type(this.classId, [this.typeArguments = const []]);
+  const _InterfaceType(this.classId, this.declaredNullable,
+      [this.typeArguments = const []]);
 
-  bool operator ==(Object other) {
-    if (other is! _Type) return false;
+  bool get isNullable => declaredNullable;
+
+  @override
+  bool operator ==(Object o) {
+    if (!(super == (o))) return false;
+    _InterfaceType other = unsafeCast<_InterfaceType>(o);
     if (classId != other.classId) return false;
+    if (isNullable != other.isNullable) return false;
+    assert(typeArguments.length == other.typeArguments.length);
     for (int i = 0; i < typeArguments.length; i++) {
       if (typeArguments[i] != other.typeArguments[i]) return false;
     }
     return true;
   }
 
+  @override
   int get hashCode {
-    int hash = mix64(classId);
+    int hash = super.hashCode;
+    hash = mix64(hash ^ classId);
+    hash = mix64(hash ^ (isNullable ? 1 : 0));
     for (int i = 0; i < typeArguments.length; i++) {
       hash = mix64(hash ^ typeArguments[i].hashCode);
     }
     return hash;
   }
 
+  @override
   String toString() {
     StringBuffer s = StringBuffer();
-    s.write("Type");
+    s.write("Interface");
     s.write(classId);
     if (typeArguments.isNotEmpty) {
       s.write("<");
@@ -42,6 +110,115 @@
       }
       s.write(">");
     }
+    if (isNullable) s.write("?");
     return s.toString();
   }
 }
+
+@pragma("wasm:entry-point")
+class _FunctionType extends _Type {
+  // TODO(joshualitt): Implement.
+  @override
+  String toString() => 'FunctionType';
+}
+
+@pragma("wasm:entry-point")
+class _GenericFunctionType extends _FunctionType {
+  // TODO(joshualitt): Implement.
+  @override
+  String toString() => 'GenericFunctionType';
+}
+
+external Map<int, List<int>> _getSubtypeMap();
+
+class _TypeUniverse {
+  /// 'Map' of classId to range of subclasses.
+  final Map<int, List<int>> _subtypeMap;
+
+  const _TypeUniverse._(this._subtypeMap);
+
+  factory _TypeUniverse.create() {
+    return _TypeUniverse._(_getSubtypeMap());
+  }
+
+  bool isObjectQuestionType(_Type t) {
+    if (!t.isInterface) return false;
+    _InterfaceType type = t.as<_InterfaceType>();
+    return type.classId == ClassID.cidObject && type.isNullable;
+  }
+
+  bool isTopType(_Type type) {
+    return isObjectQuestionType(type) || type.isDynamic || type.isVoid;
+  }
+
+  bool isBottomType(_Type type) {
+    return type.isNever;
+  }
+
+  bool isInterfaceSubtype(_InterfaceType s, _InterfaceType t) {
+    int sId = s.classId;
+    int tId = t.classId;
+    if (sId == tId) {
+      assert(s.typeArguments.length == t.typeArguments.length);
+      for (int i = 0; i < s.typeArguments.length; i++) {
+        if (!isSubtype(s.typeArguments[i], t.typeArguments[i])) {
+          return false;
+        }
+      }
+      return true;
+    }
+    List<int>? subtypes = _subtypeMap[tId];
+    if (subtypes == null) return false;
+    if (!subtypes.contains(sId)) return false;
+    // TODO(joshualitt): Compare type arguments.
+    return true;
+  }
+
+  // Subtype check based off of sdk/lib/_internal/js_runtime/lib/rti.dart.
+  // Returns true if [s] is a subtype of [t], false otherwise.
+  bool isSubtype(_Type s, _Type t) {
+    // Reflexivity:
+    if (identical(s, t)) return true;
+
+    // Right Top:
+    if (isTopType(t)) return true;
+
+    // Left Top:
+    if (isTopType(s)) return false;
+
+    // Left Bottom:
+    if (isBottomType(s)) return true;
+
+    // TODO(joshualitt): Implement missing cases.
+    // Left type variable bound 1:
+    // Left Null:
+    // Right Object:
+    // Left FuturOr:
+    // Left Nullable:
+    // Do we need to handle at runtime
+    //   Type Variable Reflexivity 1 && 2
+    //   Right Promoted Variable
+    // Right FutureOr:
+    // Right Nullable:
+    // Do we need to handle at runtime:
+    //   Left Promoted Variable
+    // Left Type Variable Bound 2:
+    // Function Type / Function:
+    // Positional Function Types + Named Function Types:
+
+    // Interface Compositionality + Super-Interface:
+    if (s.isInterface &&
+        t.isInterface &&
+        isInterfaceSubtype(s.as<_InterfaceType>(), t.as<_InterfaceType>())) {
+      return true;
+    }
+    return false;
+  }
+}
+
+_TypeUniverse _typeUniverse = _TypeUniverse.create();
+
+@pragma("wasm:entry-point")
+bool _isSubtype(Object? s, _Type t) {
+  return _typeUniverse.isSubtype(unsafeCast<_Type>(s.runtimeType), t);
+}
diff --git a/tools/VERSION b/tools/VERSION
index 9d184e4..92cff59 100644
--- a/tools/VERSION
+++ b/tools/VERSION
@@ -27,5 +27,5 @@
 MAJOR 2
 MINOR 18
 PATCH 0
-PRERELEASE 73
+PRERELEASE 74
 PRERELEASE_PATCH 0
\ No newline at end of file