Version 2.14.0-250.0.dev

Merge commit '58ad941a5a146e476e0279049d5e7ee81444807e' into 'dev'
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 94b1829..85e4d97 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,14 +2,6 @@
 
 ### Core libraries
 
-### `dart:html`
-
-*   [#44319][]: `convertNativeToDart_Dictionary()` now converts objects
-    recursively, this fixes APIs like MediaStreamTrack.getCapabilities
-    that convert between Maps and browser Dictionaries.
-
-[44319]: (https://github.com/dart-lang/sdk/issues/44319)
-
 #### `dart:async`
 
 *   The uncaught error handlers of `Zone`s are now run in the parent zone
@@ -38,6 +30,14 @@
 *   Adds the `DynamicLibrary.providesSymbol` function to check whether a symbol
     is available in a dynamic library.
 
+#### `dart:html`
+
+*   `convertNativeToDart_Dictionary()` now converts objects
+    recursively, this fixes APIs like MediaStreamTrack.getCapabilities
+    that convert between Maps and browser Dictionaries. [#44319]
+
+[#44319]: https://github.com/dart-lang/sdk/issues/44319
+
 #### `dart:io`
 
 *   BREAKING CHANGE (for pre-migrated null safe code):
diff --git a/pkg/compiler/lib/src/js_emitter/class_stub_generator.dart b/pkg/compiler/lib/src/js_emitter/class_stub_generator.dart
index 12c88aa..25cd71f 100644
--- a/pkg/compiler/lib/src/js_emitter/class_stub_generator.dart
+++ b/pkg/compiler/lib/src/js_emitter/class_stub_generator.dart
@@ -300,7 +300,7 @@
         return isIntercepted
             ? function(receiver) {
                 if (cache === null) cache = #createTearOffClass(parameters);
-                return new cache(this, receiver);
+                return new cache(receiver, this);
               }
             : function() {
                 if (cache === null) cache = #createTearOffClass(parameters);
@@ -339,7 +339,7 @@
     return new Function("parameters, createTearOffClass, cache",
         "return function tearOff_" + name + (functionCounter++) + "(receiver) {" +
           "if (cache === null) cache = createTearOffClass(parameters);" +
-            "return new cache(this, receiver);" +
+            "return new cache(receiver, this);" +
         "}")(parameters, #createTearOffClass, null);
   else
     return new Function("parameters, createTearOffClass, cache",
diff --git a/pkg/compiler/test/codegen/use_strict_test.dart b/pkg/compiler/test/codegen/use_strict_test.dart
index 4407f33..d6cbaf4 100644
--- a/pkg/compiler/test/codegen/use_strict_test.dart
+++ b/pkg/compiler/test/codegen/use_strict_test.dart
@@ -65,7 +65,7 @@
     List<RegExp> filters = [
       RegExp(r' *//'), // skip comments
       RegExp(r'"Intercepted function with no arguments."'),
-      RegExp(r'f.apply\(s\(this\), arguments\)'),
+      RegExp(r'f.apply\(receiverOf\(this\), arguments\)'),
       RegExp(r'Array.prototype.push.apply\(a, arguments\)'),
     ];
     String filtered = lines
diff --git a/pkg/test_runner/lib/src/compiler_configuration.dart b/pkg/test_runner/lib/src/compiler_configuration.dart
index ab31327..0a4ddd9 100644
--- a/pkg/test_runner/lib/src/compiler_configuration.dart
+++ b/pkg/test_runner/lib/src/compiler_configuration.dart
@@ -844,6 +844,7 @@
   Command computeAssembleCommand(String tempDir, List arguments,
       Map<String, String> environmentOverrides) {
     String cc, shared, ldFlags;
+    List<String> target;
     if (_isAndroid) {
       cc = "$ndkPath/toolchains/$abiTriple-4.9/prebuilt/"
           "$host-x86_64/bin/$abiTriple-gcc";
@@ -862,6 +863,10 @@
       shared = '-dynamiclib';
       // Tell Mac linker to give up generating eh_frame from dwarf.
       ldFlags = '-Wl,-no_compact_unwind';
+      if ({Architecture.arm64, Architecture.arm64c}
+          .contains(_configuration.architecture)) {
+        target = ['-arch', 'arm64'];
+      }
     } else {
       throw "Platform not supported: ${Platform.operatingSystem}";
     }
@@ -887,6 +892,7 @@
     }
 
     var args = [
+      if (target != null) ...target,
       if (ccFlags != null) ccFlags,
       if (ldFlags != null) ldFlags,
       shared,
diff --git a/sdk/lib/_internal/js_runtime/lib/js_helper.dart b/sdk/lib/_internal/js_runtime/lib/js_helper.dart
index cb46dfa..fa79c12 100644
--- a/sdk/lib/_internal/js_runtime/lib/js_helper.dart
+++ b/sdk/lib/_internal/js_runtime/lib/js_helper.dart
@@ -1843,15 +1843,6 @@
 /// All static, tear-off, function declaration and function expression closures
 /// extend this class.
 abstract class Closure implements Function {
-  // TODO(ahe): These constants must be in sync with
-  // reflection_data_parser.dart.
-  static const FUNCTION_INDEX = 0;
-  static const NAME_INDEX = 1;
-  static const CALL_NAME_INDEX = 2;
-  static const REQUIRED_PARAMETER_INDEX = 3;
-  static const OPTIONAL_PARAMETER_INDEX = 4;
-  static const DEFAULT_ARGUMENTS_INDEX = 5;
-
   /// Global counter to prevent reusing function code objects.
   ///
   /// V8 will share the underlying function code objects when the same string is
@@ -1881,8 +1872,7 @@
       var aBoundClosure = JS('BoundClosure', '0');
       var aString = JS('String', '0');
       BoundClosure.receiverOf(aBoundClosure);
-      BoundClosure.selfOf(aBoundClosure);
-      BoundClosure.evalRecipeIntercepted(aBoundClosure, aString);
+      BoundClosure.interceptorOf(aBoundClosure);
       BoundClosure.evalRecipe(aBoundClosure, aString);
       getType(JS('int', '0'));
     });
@@ -2037,19 +2027,8 @@
   static _computeSignatureFunctionNewRti(
       Object functionType, bool isStatic, bool isIntercepted) {
     if (JS('bool', 'typeof # == "number"', functionType)) {
-      // Index into types table.
-      //
-      // We cannot call [getTypeFromTypesTable] here, since the types-metadata
-      // might not be set yet. This is, because fromTearOff might be called for
-      // constants when the program isn't completely set up yet. We also want to
-      // avoid creating lots of types at startup.
-      return JS(
-          '',
-          '''(function(getType, t) {
-                 return function(){ return getType(t); };
-             })(#, #)''',
-          RAW_DART_FUNCTION_REF(newRti.getTypeFromTypesTable),
-          functionType);
+      // Index into types table. Handled in rti.dart.
+      return functionType;
     }
     if (JS('bool', 'typeof # == "string"', functionType)) {
       // A recipe to evaluate against the instance type.
@@ -2057,9 +2036,7 @@
         // TODO(sra): Recipe for static tearoff.
         throw 'Cannot compute signature for static tearoff.';
       }
-      var typeEvalMethod = isIntercepted
-          ? RAW_DART_FUNCTION_REF(BoundClosure.evalRecipeIntercepted)
-          : RAW_DART_FUNCTION_REF(BoundClosure.evalRecipe);
+      final typeEvalMethod = RAW_DART_FUNCTION_REF(BoundClosure.evalRecipe);
       return JS(
           '',
           '    function(recipe, evalOnReceiver) {'
@@ -2075,7 +2052,7 @@
 
   static cspForwardCall(
       int arity, bool needsDirectAccess, String? stubName, function) {
-    var getSelf = RAW_DART_FUNCTION_REF(BoundClosure.selfOf);
+    var getReceiver = RAW_DART_FUNCTION_REF(BoundClosure.receiverOf);
 
     // We have the target method (or an arity stub for the method) in
     // [function]. These fixed-arity forwarding stubs could use
@@ -2094,74 +2071,74 @@
       case 0:
         return JS(
             '',
-            'function(n,S){'
+            'function(entry, receiverOf){'
                 'return function(){'
-                'return S(this)[n]()'
+                'return receiverOf(this)[entry]()'
                 '}'
                 '}(#,#)',
             stubName,
-            getSelf);
+            getReceiver);
       case 1:
         return JS(
             '',
-            'function(n,S){'
+            'function(entry, receiverOf){'
                 'return function(a){'
-                'return S(this)[n](a)'
+                'return receiverOf(this)[entry](a)'
                 '}'
                 '}(#,#)',
             stubName,
-            getSelf);
+            getReceiver);
       case 2:
         return JS(
             '',
-            'function(n,S){'
-                'return function(a,b){'
-                'return S(this)[n](a,b)'
+            'function(entry, receiverOf){'
+                'return function(a, b){'
+                'return receiverOf(this)[entry](a, b)'
                 '}'
                 '}(#,#)',
             stubName,
-            getSelf);
+            getReceiver);
       case 3:
         return JS(
             '',
-            'function(n,S){'
-                'return function(a,b,c){'
-                'return S(this)[n](a,b,c)'
+            'function(entry, receiverOf){'
+                'return function(a, b, c){'
+                'return receiverOf(this)[entry](a, b, c)'
                 '}'
                 '}(#,#)',
             stubName,
-            getSelf);
+            getReceiver);
       case 4:
         return JS(
             '',
-            'function(n,S){'
-                'return function(a,b,c,d){'
-                'return S(this)[n](a,b,c,d)'
+            'function(entry, receiverOf){'
+                'return function(a, b, c, d){'
+                'return receiverOf(this)[entry](a, b, c, d)'
                 '}'
                 '}(#,#)',
             stubName,
-            getSelf);
+            getReceiver);
       case 5:
         return JS(
             '',
-            'function(n,S){'
-                'return function(a,b,c,d,e){'
-                'return S(this)[n](a,b,c,d,e)'
+            'function(entry, receiverOf){'
+                'return function(a, b, c, d, e){'
+                'return receiverOf(this)[entry](a, b, c, d, e)'
                 '}'
                 '}(#,#)',
             stubName,
-            getSelf);
+            getReceiver);
       default:
         // Here we use `Function.prototype.apply`.
         return JS(
             '',
-            'function(f,s){'
+            'function(f, receiverOf){'
                 'return function(){'
-                'return f.apply(s(this),arguments)'
+                'return f.apply(receiverOf(this), arguments)'
                 '}'
                 '}(#,#)',
             function,
-            getSelf);
+            getReceiver);
     }
   }
 
@@ -2184,7 +2161,7 @@
           '',
           '(new Function(#))()',
           'return function(){'
-              'var $selfName = this.${BoundClosure.selfFieldName()};'
+              'var $selfName = this.${BoundClosure.receiverFieldName()};'
               'return $selfName.$stubName();'
               '}');
     }
@@ -2196,106 +2173,106 @@
         '',
         '(new Function(#))()',
         'return function($arguments){'
-            'return this.${BoundClosure.selfFieldName()}.$stubName($arguments);'
+            'return this.${BoundClosure.receiverFieldName()}.$stubName($arguments);'
             '}');
   }
 
   static cspForwardInterceptedCall(
-      int arity, bool needsDirectAccess, String? name, function) {
-    var getSelf = RAW_DART_FUNCTION_REF(BoundClosure.selfOf);
+      int arity, bool needsDirectAccess, String? stubName, function) {
     var getReceiver = RAW_DART_FUNCTION_REF(BoundClosure.receiverOf);
+    var getInterceptor = RAW_DART_FUNCTION_REF(BoundClosure.interceptorOf);
     // Handle intercepted stub-names with the default slow case.
     if (needsDirectAccess) arity = -1;
     switch (arity) {
       case 0:
         // Intercepted functions always takes at least one argument (the
         // receiver).
-        throw new RuntimeError('Intercepted function with no arguments.');
+        throw RuntimeError('Intercepted function with no arguments.');
       case 1:
         return JS(
             '',
-            'function(n,s,r){'
+            'function(entry, interceptorOf, receiverOf){'
                 'return function(){'
-                'return s(this)[n](r(this))'
+                'return interceptorOf(this)[entry](receiverOf(this))'
                 '}'
                 '}(#,#,#)',
-            name,
-            getSelf,
+            stubName,
+            getInterceptor,
             getReceiver);
       case 2:
         return JS(
             '',
-            'function(n,s,r){'
+            'function(entry, interceptorOf, receiverOf){'
                 'return function(a){'
-                'return s(this)[n](r(this),a)'
+                'return interceptorOf(this)[entry](receiverOf(this), a)'
                 '}'
                 '}(#,#,#)',
-            name,
-            getSelf,
+            stubName,
+            getInterceptor,
             getReceiver);
       case 3:
         return JS(
             '',
-            'function(n,s,r){'
-                'return function(a,b){'
-                'return s(this)[n](r(this),a,b)'
+            'function(entry, interceptorOf, receiverOf){'
+                'return function(a, b){'
+                'return interceptorOf(this)[entry](receiverOf(this), a, b)'
                 '}'
                 '}(#,#,#)',
-            name,
-            getSelf,
+            stubName,
+            getInterceptor,
             getReceiver);
       case 4:
         return JS(
             '',
-            'function(n,s,r){'
-                'return function(a,b,c){'
-                'return s(this)[n](r(this),a,b,c)'
+            'function(entry, interceptorOf, receiverOf){'
+                'return function(a, b, c){'
+                'return interceptorOf(this)[entry](receiverOf(this), a, b, c)'
                 '}'
                 '}(#,#,#)',
-            name,
-            getSelf,
+            stubName,
+            getInterceptor,
             getReceiver);
       case 5:
         return JS(
             '',
-            'function(n,s,r){'
-                'return function(a,b,c,d){'
-                'return s(this)[n](r(this),a,b,c,d)'
+            'function(entry, interceptorOf, receiverOf){'
+                'return function(a, b, c, d){'
+                'return interceptorOf(this)[entry](receiverOf(this), a, b, c, d)'
                 '}'
                 '}(#,#,#)',
-            name,
-            getSelf,
+            stubName,
+            getInterceptor,
             getReceiver);
       case 6:
         return JS(
             '',
-            'function(n,s,r){'
-                'return function(a,b,c,d,e){'
-                'return s(this)[n](r(this),a,b,c,d,e)'
+            'function(entry, interceptorOf, receiverOf){'
+                'return function(a, b, c, d, e){'
+                'return interceptorOf(this)[entry](receiverOf(this), a, b, c, d, e)'
                 '}'
                 '}(#,#,#)',
-            name,
-            getSelf,
+            stubName,
+            getInterceptor,
             getReceiver);
       default:
         return JS(
             '',
-            'function(f,s,r,a){'
+            'function(f, interceptorOf, receiverOf){'
                 'return function(){'
-                'a=[r(this)];'
-                'Array.prototype.push.apply(a,arguments);'
-                'return f.apply(s(this),a)'
+                'var a = [receiverOf(this)];'
+                'Array.prototype.push.apply(a, arguments);'
+                'return f.apply(interceptorOf(this), a)'
                 '}'
                 '}(#,#,#)',
             function,
-            getSelf,
+            getInterceptor,
             getReceiver);
     }
   }
 
   static forwardInterceptedCallTo(
       String stubName, function, bool needsDirectAccess) {
-    String selfField = BoundClosure.selfFieldName();
+    String interceptorField = BoundClosure.interceptorFieldName();
     String receiverField = BoundClosure.receiverFieldName();
     int arity = JS('int', '#.length', function);
     bool isCsp = JS_GET_FLAG('USE_CONTENT_SECURITY_POLICY');
@@ -2309,7 +2286,7 @@
           '',
           '(new Function(#))()',
           'return function(){'
-              'return this.$selfField.$stubName(this.$receiverField);'
+              'return this.$interceptorField.$stubName(this.$receiverField);'
               '${functionCounter++}'
               '}');
     }
@@ -2322,7 +2299,7 @@
         '',
         '(new Function(#))()',
         'return function($arguments){'
-            'return this.$selfField.$stubName(this.$receiverField, $arguments);'
+            'return this.$interceptorField.$stubName(this.$receiverField, $arguments);'
             '${functionCounter++}'
             '}');
   }
@@ -2375,13 +2352,12 @@
 /// This is a base class that is extended to create a separate closure class for
 /// each instance method. The subclass is created at run time.
 class BoundClosure extends TearOffClosure {
-  /// The JavaScript receiver, which is the Dart receiver or the interceptor.
-  final _self;
-
-  /// The Dart receiver if [_target] is an intercepted method (in which case
-  /// [_self] is the interceptor), otherwise `null`.
+  /// The Dart receiver.
   final _receiver;
 
+  /// The JavaScript receiver when using the interceptor calling convention.
+  final _interceptor;
+
   /// The [_name] and [_target] of the bound closure are stored in the prototype
   /// of the closure class (i.e. the subclass of BoundClosure).
   static const nameProperty = r'$_name';
@@ -2393,72 +2369,55 @@
   /// The primary entry point for the instance method, used by `==`/`hashCode`.
   Object get _target => JS('', '#.#', this, targetProperty);
 
-  BoundClosure(this._self, this._receiver);
+  BoundClosure(this._receiver, this._interceptor);
 
-  bool operator ==(other) {
+  @override
+  bool operator ==(Object other) {
     if (identical(this, other)) return true;
     if (other is! BoundClosure) return false;
-    return JS('bool', '# === #', _self, other._self) &&
-        JS('bool', '# === #', _target, other._target) &&
+    return JS('bool', '# === #', _target, other._target) &&
         JS('bool', '# === #', _receiver, other._receiver);
   }
 
+  @override
   int get hashCode {
-    int receiverHashCode;
-    if (_receiver == null) {
-      // A bound closure on a regular Dart object, just use the
-      // identity hash code.
-      receiverHashCode = Primitives.objectHashCode(_self);
-    } else if (JS('String', 'typeof #', _receiver) != 'object') {
-      // A bound closure on a primitive JavaScript type. We
-      // use the hashCode method we define for those primitive types.
-      receiverHashCode = _receiver.hashCode;
-    } else {
-      // A bound closure on an intercepted native class, just use the
-      // identity hash code.
-      receiverHashCode = Primitives.objectHashCode(_receiver);
-    }
+    int receiverHashCode = objectHashCode(_receiver);
     return receiverHashCode ^ Primitives.objectHashCode(_target);
   }
 
-  toString() {
-    var receiver = _receiver == null ? _self : _receiver;
+  @override
+  String toString() {
     // TODO(sra): When minified, mark [_name] with a tag,
     // e.g. 'minified-property:' so that it can be unminified.
     return "Closure '$_name' of "
-        "${Primitives.objectToHumanReadableString(receiver)}";
+        "${Primitives.objectToHumanReadableString(_receiver)}";
   }
 
   @pragma('dart2js:parameter:trust')
   static evalRecipe(BoundClosure closure, String recipe) {
-    return newRti.evalInInstance(closure._self, recipe);
-  }
-
-  @pragma('dart2js:parameter:trust')
-  static evalRecipeIntercepted(BoundClosure closure, String recipe) {
     return newRti.evalInInstance(closure._receiver, recipe);
   }
 
   @pragma('dart2js:noInline')
   @pragma('dart2js:parameter:trust')
-  static selfOf(BoundClosure closure) => closure._self;
+  static receiverOf(BoundClosure closure) => closure._receiver;
 
   @pragma('dart2js:noInline')
   @pragma('dart2js:parameter:trust')
-  static receiverOf(BoundClosure closure) => closure._receiver;
-
-  static String? _selfFieldNameCache;
-  static String selfFieldName() =>
-      _selfFieldNameCache ??= _computeFieldNamed('self');
+  static interceptorOf(BoundClosure closure) => closure._interceptor;
 
   static String? _receiverFieldNameCache;
   static String receiverFieldName() =>
       _receiverFieldNameCache ??= _computeFieldNamed('receiver');
 
+  static String? _interceptorFieldNameCache;
+  static String interceptorFieldName() =>
+      _interceptorFieldNameCache ??= _computeFieldNamed('interceptor');
+
   @pragma('dart2js:noInline')
   @pragma('dart2js:noSideEffects')
   static String _computeFieldNamed(String fieldName) {
-    var template = new BoundClosure('self', 'receiver');
+    var template = new BoundClosure('receiver', 'interceptor');
     var names = JSArray.markFixedList(
         JS('', 'Object.getOwnPropertyNames(#)', template));
     for (int i = 0; i < names.length; i++) {
diff --git a/sdk/lib/_internal/js_runtime/lib/rti.dart b/sdk/lib/_internal/js_runtime/lib/rti.dart
index 96fc0a8..694bc1d 100644
--- a/sdk/lib/_internal/js_runtime/lib/rti.dart
+++ b/sdk/lib/_internal/js_runtime/lib/rti.dart
@@ -334,7 +334,7 @@
   }
 
   static Rti allocate() {
-    return new Rti();
+    return Rti();
   }
 
   Object? _canonicalRecipe;
@@ -2974,86 +2974,94 @@
   String sName = Rti._getInterfaceName(s);
   String tName = Rti._getInterfaceName(t);
 
-  // Interface Compositionality:
-  if (sName == tName) {
-    var sArgs = Rti._getInterfaceTypeArguments(s);
-    var tArgs = Rti._getInterfaceTypeArguments(t);
-    int length = _Utils.arrayLength(sArgs);
-    assert(length == _Utils.arrayLength(tArgs));
+  while (sName != tName) {
+    // The Super-Interface rule says that if [s] has superinterfaces C0,...,Cn,
+    // then we need to check if for some i, Ci <: [t]. However, this requires us
+    // to iterate over the superinterfaces. Instead, we can perform case
+    // analysis on [t]. By this point, [t] can only be Never, a type variable,
+    // or an interface type. (Bindings do not participate in subtype checks and
+    // all other cases have been eliminated.) If [t] is not an interface, then
+    // [s] </: [t]. Therefore, the only remaining case is that [t] is an
+    // interface, so rather than iterating over the Ci, we can instead look up
+    // [t] in our ruleset.
+    // TODO(fishythefish): Handle variance correctly.
 
-    var sVariances;
-    bool? hasVariances;
-    if (JS_GET_FLAG("VARIANCE")) {
-      sVariances = _Universe.findTypeParameterVariances(universe, sName);
-      hasVariances = sVariances != null;
-      assert(!hasVariances || length == _Utils.arrayLength(sVariances));
+    var rule = _Universe._findRule(universe, sName);
+    if (rule == null) return false;
+    if (_Utils.isString(rule)) {
+      sName = _Utils.asString(rule);
+      continue;
     }
 
+    var recipes = TypeRule.lookupSupertype(rule, tName);
+    if (recipes == null) return false;
+    int length = _Utils.arrayLength(recipes);
+    Object? supertypeArgs = length > 0 ? JS('', 'new Array(#)', length) : null;
     for (int i = 0; i < length; i++) {
-      Rti sArg = _Utils.asRti(_Utils.arrayAt(sArgs, i));
-      Rti tArg = _Utils.asRti(_Utils.arrayAt(tArgs, i));
-      if (JS_GET_FLAG("VARIANCE")) {
-        int sVariance = hasVariances != null
-            ? _Utils.asInt(_Utils.arrayAt(sVariances, i))
-            : Variance.legacyCovariant;
-        switch (sVariance) {
-          case Variance.legacyCovariant:
-          case Variance.covariant:
-            if (!_isSubtype(universe, sArg, sEnv, tArg, tEnv)) {
-              return false;
-            }
-            break;
-          case Variance.contravariant:
-            if (!_isSubtype(universe, tArg, tEnv, sArg, sEnv)) {
-              return false;
-            }
-            break;
-          case Variance.invariant:
-            if (!_isSubtype(universe, sArg, sEnv, tArg, tEnv) ||
-                !_isSubtype(universe, tArg, tEnv, sArg, sEnv)) {
-              return false;
-            }
-            break;
-          default:
-            throw StateError(
-                "Unknown variance given for subtype check: $sVariance");
-        }
-      } else {
-        if (!_isSubtype(universe, sArg, sEnv, tArg, tEnv)) {
-          return false;
-        }
-      }
+      String recipe = _Utils.asString(_Utils.arrayAt(recipes, i));
+      Rti supertypeArg = _Universe.evalInEnvironment(universe, s, recipe);
+      _Utils.arraySetAt(supertypeArgs, i, supertypeArg);
     }
-    return true;
+    var tArgs = Rti._getInterfaceTypeArguments(t);
+    return _areArgumentsSubtypes(
+        universe, supertypeArgs, null, sEnv, tArgs, tEnv);
   }
 
-  // The Super-Interface rule says that if [s] has superinterfaces C0,...,Cn,
-  // then we need to check if for some i, Ci <: [t]. However, this requires us
-  // to iterate over the superinterfaces. Instead, we can perform case
-  // analysis on [t]. By this point, [t] can only be Never, a type variable,
-  // or an interface type. (Bindings do not participate in subtype checks and
-  // all other cases have been eliminated.) If [t] is not an interface, then
-  // [s] </: [t]. Therefore, the only remaining case is that [t] is an
-  // interface, so rather than iterating over the Ci, we can instead look up
-  // [t] in our ruleset.
-  // TODO(fishythefish): Handle variance correctly.
-
-  // We don't list Object explicitly as a supertype of each interface, so check
-  // this trivial case first.
-  if (isObjectType(t)) return true;
-  var rule = _Universe.findRule(universe, sName);
-  if (rule == null) return false;
-  var supertypeArgs = TypeRule.lookupSupertype(rule, tName);
-  if (supertypeArgs == null) return false;
-  int length = _Utils.arrayLength(supertypeArgs);
+  // Interface Compositionality:
+  assert(sName == tName);
+  var sArgs = Rti._getInterfaceTypeArguments(s);
   var tArgs = Rti._getInterfaceTypeArguments(t);
+  var sVariances;
+  if (JS_GET_FLAG("VARIANCE")) {
+    sVariances = _Universe.findTypeParameterVariances(universe, sName);
+  }
+  return _areArgumentsSubtypes(universe, sArgs, sVariances, sEnv, tArgs, tEnv);
+}
+
+bool _areArgumentsSubtypes(Object? universe, Object? sArgs, Object? sVariances,
+    Object? sEnv, Object? tArgs, Object? tEnv) {
+  int length = sArgs != null ? _Utils.arrayLength(sArgs) : 0;
   assert(length == _Utils.arrayLength(tArgs));
+  bool hasVariances = sVariances != null;
+  if (JS_GET_FLAG("VARIANCE")) {
+    assert(!hasVariances || length == _Utils.arrayLength(sVariances));
+  } else {
+    assert(!hasVariances);
+  }
+
   for (int i = 0; i < length; i++) {
-    String recipe = _Utils.asString(_Utils.arrayAt(supertypeArgs, i));
-    Rti supertypeArg = _Universe.evalInEnvironment(universe, s, recipe);
+    Rti sArg = _Utils.asRti(_Utils.arrayAt(sArgs, i));
     Rti tArg = _Utils.asRti(_Utils.arrayAt(tArgs, i));
-    if (!_isSubtype(universe, supertypeArg, sEnv, tArg, tEnv)) {
-      return false;
+    if (JS_GET_FLAG("VARIANCE")) {
+      int sVariance = hasVariances
+          ? _Utils.asInt(_Utils.arrayAt(sVariances, i))
+          : Variance.legacyCovariant;
+      switch (sVariance) {
+        case Variance.legacyCovariant:
+        case Variance.covariant:
+          if (!_isSubtype(universe, sArg, sEnv, tArg, tEnv)) {
+            return false;
+          }
+          break;
+        case Variance.contravariant:
+          if (!_isSubtype(universe, tArg, tEnv, sArg, sEnv)) {
+            return false;
+          }
+          break;
+        case Variance.invariant:
+          if (!_isSubtype(universe, sArg, sEnv, tArg, tEnv) ||
+              !_isSubtype(universe, tArg, tEnv, sArg, sEnv)) {
+            return false;
+          }
+          break;
+        default:
+          throw StateError(
+              "Unknown variance given for subtype check: $sVariance");
+      }
+    } else {
+      if (!_isSubtype(universe, sArg, sEnv, tArg, tEnv)) {
+        return false;
+      }
     }
   }
   return true;
diff --git a/tests/web/internal/rti/js_interop_subtype_test.dart b/tests/web/internal/rti/js_interop_subtype_test.dart
new file mode 100644
index 0000000..66010da
--- /dev/null
+++ b/tests/web/internal/rti/js_interop_subtype_test.dart
@@ -0,0 +1,22 @@
+// Copyright (c) 2021, 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:_interceptors';
+
+import 'package:expect/expect.dart';
+import 'package:js/js.dart';
+
+@JS()
+class JSClass {}
+
+@JS()
+external void eval(String code);
+
+void main() {
+  eval(r'''
+      function JSClass() {}
+      ''');
+  Expect.type<JavaScriptObject>(JSClass());
+  Expect.type<List<JavaScriptObject>>(<JSClass>[]);
+}
diff --git a/tests/web_2/internal/rti/js_interop_subtype_test.dart b/tests/web_2/internal/rti/js_interop_subtype_test.dart
new file mode 100644
index 0000000..66010da
--- /dev/null
+++ b/tests/web_2/internal/rti/js_interop_subtype_test.dart
@@ -0,0 +1,22 @@
+// Copyright (c) 2021, 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:_interceptors';
+
+import 'package:expect/expect.dart';
+import 'package:js/js.dart';
+
+@JS()
+class JSClass {}
+
+@JS()
+external void eval(String code);
+
+void main() {
+  eval(r'''
+      function JSClass() {}
+      ''');
+  Expect.type<JavaScriptObject>(JSClass());
+  Expect.type<List<JavaScriptObject>>(<JSClass>[]);
+}
diff --git a/tools/VERSION b/tools/VERSION
index 54a920f..37cbba7 100644
--- a/tools/VERSION
+++ b/tools/VERSION
@@ -27,5 +27,5 @@
 MAJOR 2
 MINOR 14
 PATCH 0
-PRERELEASE 249
+PRERELEASE 250
 PRERELEASE_PATCH 0
\ No newline at end of file
diff --git a/tools/bots/test_matrix.json b/tools/bots/test_matrix.json
index b2b13ea..5ed68a5 100644
--- a/tools/bots/test_matrix.json
+++ b/tools/bots/test_matrix.json
@@ -1320,12 +1320,61 @@
     },
     {
       "builders": [
+        "vm-kernel-precomp-nnbd-mac-release-arm64"
+      ],
+      "meta": {
+        "description": "This configuration is for the macOS arm64 VM AOT nnbd builder group."
+      },
+      "steps": [
+        {
+          "name": "build dart",
+          "script": "tools/build.py",
+          "arguments": [
+            "runtime_kernel",
+            "dart_precompiled_runtime",
+            "gen_snapshot"
+          ]
+        },
+        {
+          "name": "vm nnbd tests in weak mode with asserts",
+          "arguments": [
+            "-ndartkp-weak-asserts-${system}-${mode}-${arch}",
+            "benchmark_smoke",
+            "corelib",
+            "ffi",
+            "language",
+            "lib",
+            "samples",
+            "service",
+            "standalone",
+            "utils",
+            "vm"
+          ]
+        },
+        {
+          "name": "vm nnbd tests in strong mode",
+          "arguments": [
+            "-ndartkp-strong-${system}-${mode}-${arch}",
+            "benchmark_smoke",
+            "corelib",
+            "ffi",
+            "language",
+            "lib",
+            "samples",
+            "standalone",
+            "utils",
+            "vm"
+          ]
+        }
+      ]
+    },
+    {
+      "builders": [
         "vm-kernel-precomp-nnbd-linux-debug-x64",
         "vm-kernel-precomp-nnbd-linux-release-simarm64",
         "vm-kernel-precomp-nnbd-linux-release-simarm64c",
         "vm-kernel-precomp-nnbd-linux-release-x64",
         "vm-kernel-precomp-nnbd-linux-release-x64c",
-        "vm-kernel-precomp-nnbd-mac-release-arm64",
         "vm-kernel-precomp-nnbd-mac-release-simarm64",
         "vm-kernel-precomp-nnbd-mac-release-simarm64c",
         "vm-kernel-precomp-nnbd-win-release-x64",