[VM] Extend subtype-test mechanism with support for generic methods

Until now the subtype-test cache mechanism did not work (i.e. could
return the wrong result) for partially instantiated generic closures.

Additionally, closures which close over generic methods were always
handled in runtime.  This caused a servere performance regression for
any code hitting this (e.g. code which uses `package:stack_trace`).

Fixes https://github.com/dart-lang/sdk/issues/34051
Fixes https://github.com/dart-lang/sdk/issues/34054

Change-Id: Idb73e6f348c2fe0c737f42c57009f5f7a636c9a6
Reviewed-on: https://dart-review.googlesource.com/68369
Commit-Queue: Martin Kustermann <kustermann@google.com>
Reviewed-by: RĂ©gis Crelier <regis@google.com>
diff --git a/runtime/vm/compiler/assembler/assembler_arm64.h b/runtime/vm/compiler/assembler/assembler_arm64.h
index db6a6cd..dfe29cb 100644
--- a/runtime/vm/compiler/assembler/assembler_arm64.h
+++ b/runtime/vm/compiler/assembler/assembler_arm64.h
@@ -1511,7 +1511,7 @@
   void CompareObject(Register reg, const Object& object);
 
   void LoadClassId(Register result, Register object);
-  // Overwrites class_id register.
+  // Overwrites class_id register (it will be tagged afterwards).
   void LoadClassById(Register result, Register class_id);
   void LoadClass(Register result, Register object);
   void CompareClassId(Register object,
diff --git a/runtime/vm/compiler/assembler/assembler_x64.h b/runtime/vm/compiler/assembler/assembler_x64.h
index df75109..3fb956f 100644
--- a/runtime/vm/compiler/assembler/assembler_x64.h
+++ b/runtime/vm/compiler/assembler/assembler_x64.h
@@ -771,7 +771,7 @@
   // Loading and comparing classes of objects.
   void LoadClassId(Register result, Register object);
 
-  // Overwrites class_id register.
+  // Overwrites class_id register (it will be tagged afterwards).
   void LoadClassById(Register result, Register class_id);
 
   void LoadClass(Register result, Register object);
diff --git a/runtime/vm/compiler/backend/flow_graph_compiler.h b/runtime/vm/compiler/backend/flow_graph_compiler.h
index 738254f..353b549 100644
--- a/runtime/vm/compiler/backend/flow_graph_compiler.h
+++ b/runtime/vm/compiler/backend/flow_graph_compiler.h
@@ -815,6 +815,11 @@
       Label* is_instance_lbl,
       Label* is_not_instance_label);
 
+  RawSubtypeTestCache* GenerateFunctionTypeTest(TokenPosition token_pos,
+                                                const AbstractType& dst_type,
+                                                Label* is_instance_lbl,
+                                                Label* is_not_instance_label);
+
   RawSubtypeTestCache* GenerateSubtype1TestCacheLookup(
       TokenPosition token_pos,
       const Class& type_class,
@@ -825,6 +830,7 @@
     kTestTypeOneArg,
     kTestTypeTwoArgs,
     kTestTypeFourArgs,
+    kTestTypeSixArgs,
   };
 
   RawSubtypeTestCache* GenerateCallSubtypeTestStub(
diff --git a/runtime/vm/compiler/backend/flow_graph_compiler_arm.cc b/runtime/vm/compiler/backend/flow_graph_compiler_arm.cc
index 0c3a45e..b06321f 100644
--- a/runtime/vm/compiler/backend/flow_graph_compiler_arm.cc
+++ b/runtime/vm/compiler/backend/flow_graph_compiler_arm.cc
@@ -223,6 +223,10 @@
     ASSERT(instantiator_type_arguments_reg == R2);
     ASSERT(function_type_arguments_reg == R1);
     __ BranchLink(*StubCode::Subtype4TestCache_entry());
+  } else if (test_kind == kTestTypeSixArgs) {
+    ASSERT(instantiator_type_arguments_reg == R2);
+    ASSERT(function_type_arguments_reg == R1);
+    __ BranchLink(*StubCode::Subtype6TestCache_entry());
   } else {
     UNREACHABLE();
   }
@@ -244,8 +248,9 @@
     Label* is_not_instance_lbl) {
   __ Comment("InstantiatedTypeWithArgumentsTest");
   ASSERT(type.IsInstantiated());
+  ASSERT(!type.IsFunctionType());
   const Class& type_class = Class::ZoneHandle(zone(), type.type_class());
-  ASSERT(type.IsFunctionType() || (type_class.NumTypeArguments() > 0));
+  ASSERT(type_class.NumTypeArguments() > 0);
   const Register kInstanceReg = R0;
   Error& bound_error = Error::Handle(zone());
   const Type& int_type = Type::Handle(zone(), Type::IntType());
@@ -259,45 +264,43 @@
   } else {
     __ b(is_not_instance_lbl, EQ);
   }
-  // A function type test requires checking the function signature.
-  if (!type.IsFunctionType()) {
-    const intptr_t num_type_args = type_class.NumTypeArguments();
-    const intptr_t num_type_params = type_class.NumTypeParameters();
-    const intptr_t from_index = num_type_args - num_type_params;
-    const TypeArguments& type_arguments =
-        TypeArguments::ZoneHandle(zone(), type.arguments());
-    const bool is_raw_type = type_arguments.IsNull() ||
-                             type_arguments.IsRaw(from_index, num_type_params);
-    if (is_raw_type) {
-      const Register kClassIdReg = R2;
-      // dynamic type argument, check only classes.
-      __ LoadClassId(kClassIdReg, kInstanceReg);
-      __ CompareImmediate(kClassIdReg, type_class.id());
-      __ b(is_instance_lbl, EQ);
-      // List is a very common case.
-      if (IsListClass(type_class)) {
-        GenerateListTypeCheck(kClassIdReg, is_instance_lbl);
-      }
-      return GenerateSubtype1TestCacheLookup(
-          token_pos, type_class, is_instance_lbl, is_not_instance_lbl);
+  const intptr_t num_type_args = type_class.NumTypeArguments();
+  const intptr_t num_type_params = type_class.NumTypeParameters();
+  const intptr_t from_index = num_type_args - num_type_params;
+  const TypeArguments& type_arguments =
+      TypeArguments::ZoneHandle(zone(), type.arguments());
+  const bool is_raw_type = type_arguments.IsNull() ||
+                           type_arguments.IsRaw(from_index, num_type_params);
+  if (is_raw_type) {
+    const Register kClassIdReg = R2;
+    // dynamic type argument, check only classes.
+    __ LoadClassId(kClassIdReg, kInstanceReg);
+    __ CompareImmediate(kClassIdReg, type_class.id());
+    __ b(is_instance_lbl, EQ);
+    // List is a very common case.
+    if (IsListClass(type_class)) {
+      GenerateListTypeCheck(kClassIdReg, is_instance_lbl);
     }
-    // If one type argument only, check if type argument is Object or dynamic.
-    if (type_arguments.Length() == 1) {
-      const AbstractType& tp_argument =
-          AbstractType::ZoneHandle(zone(), type_arguments.TypeAt(0));
-      ASSERT(!tp_argument.IsMalformed());
-      if (tp_argument.IsType()) {
-        ASSERT(tp_argument.HasResolvedTypeClass());
-        // Check if type argument is dynamic or Object.
-        const Type& object_type = Type::Handle(zone(), Type::ObjectType());
-        if (object_type.IsSubtypeOf(tp_argument, NULL, NULL, Heap::kOld)) {
-          // Instance class test only necessary.
-          return GenerateSubtype1TestCacheLookup(
-              token_pos, type_class, is_instance_lbl, is_not_instance_lbl);
-        }
+    return GenerateSubtype1TestCacheLookup(
+        token_pos, type_class, is_instance_lbl, is_not_instance_lbl);
+  }
+  // If one type argument only, check if type argument is Object or dynamic.
+  if (type_arguments.Length() == 1) {
+    const AbstractType& tp_argument =
+        AbstractType::ZoneHandle(zone(), type_arguments.TypeAt(0));
+    ASSERT(!tp_argument.IsMalformed());
+    if (tp_argument.IsType()) {
+      ASSERT(tp_argument.HasResolvedTypeClass());
+      // Check if type argument is dynamic or Object.
+      const Type& object_type = Type::Handle(zone(), Type::ObjectType());
+      if (object_type.IsSubtypeOf(tp_argument, NULL, NULL, Heap::kOld)) {
+        // Instance class test only necessary.
+        return GenerateSubtype1TestCacheLookup(
+            token_pos, type_class, is_instance_lbl, is_not_instance_lbl);
       }
     }
   }
+
   // Regular subtype test cache involving instance's type arguments.
   const Register kInstantiatorTypeArgumentsReg = kNoRegister;
   const Register kFunctionTypeArgumentsReg = kNoRegister;
@@ -332,10 +335,7 @@
     Label* is_not_instance_lbl) {
   __ Comment("InstantiatedTypeNoArgumentsTest");
   ASSERT(type.IsInstantiated());
-  if (type.IsFunctionType()) {
-    // Fallthrough.
-    return true;
-  }
+  ASSERT(!type.IsFunctionType());
   const Class& type_class = Class::Handle(zone(), type.type_class());
   ASSERT(type_class.NumTypeArguments() == 0);
 
@@ -428,12 +428,17 @@
     Label* is_instance_lbl,
     Label* is_not_instance_lbl) {
   __ Comment("UninstantiatedTypeTest");
+  const Register kInstanceReg = R0;
+  const Register kInstantiatorTypeArgumentsReg = R2;
+  const Register kFunctionTypeArgumentsReg = R1;
+  const Register kTempReg = kNoRegister;
   ASSERT(!type.IsInstantiated());
+  ASSERT(!type.IsFunctionType());
   // Skip check if destination is a dynamic type.
   if (type.IsTypeParameter()) {
     const TypeParameter& type_param = TypeParameter::Cast(type);
-    const Register kInstantiatorTypeArgumentsReg = R2;
-    const Register kFunctionTypeArgumentsReg = R1;
+    const AbstractType& bound = AbstractType::Handle(type_param.bound());
+
     __ ldm(IA, SP,
            (1 << kFunctionTypeArgumentsReg) |
                (1 << kInstantiatorTypeArgumentsReg));
@@ -466,32 +471,30 @@
     Label fall_through;
     __ b(&fall_through);
 
+    // If it's guaranteed, by type-parameter bound, that the type parameter will
+    // never have a value of a function type, then we can safely do a 4-type
+    // test instead of a 6-type test.
+    auto test_kind = !bound.IsTopType() && !bound.IsFunctionType() &&
+                             !bound.IsDartFunctionType() && bound.IsType()
+                         ? kTestTypeSixArgs
+                         : kTestTypeFourArgs;
+
     __ Bind(&not_smi);
-    // R0: instance.
-    // R2: instantiator type arguments.
-    // R1: function type arguments.
-    const Register kInstanceReg = R0;
-    const Register kTempReg = kNoRegister;
     const SubtypeTestCache& type_test_cache = SubtypeTestCache::ZoneHandle(
         zone(), GenerateCallSubtypeTestStub(
-                    kTestTypeFourArgs, kInstanceReg,
-                    kInstantiatorTypeArgumentsReg, kFunctionTypeArgumentsReg,
-                    kTempReg, is_instance_lbl, is_not_instance_lbl));
+                    test_kind, kInstanceReg, kInstantiatorTypeArgumentsReg,
+                    kFunctionTypeArgumentsReg, kTempReg, is_instance_lbl,
+                    is_not_instance_lbl));
     __ Bind(&fall_through);
     return type_test_cache.raw();
   }
   if (type.IsType()) {
-    const Register kInstanceReg = R0;
-    const Register kInstantiatorTypeArgumentsReg = R2;
-    const Register kFunctionTypeArgumentsReg = R1;
-    __ tst(kInstanceReg, Operand(kSmiTagMask));  // Is instance Smi?
-    __ b(is_not_instance_lbl, EQ);
+    __ BranchIfSmi(kInstanceReg, is_not_instance_lbl);
     __ ldm(IA, SP,
            (1 << kFunctionTypeArgumentsReg) |
                (1 << kInstantiatorTypeArgumentsReg));
     // Uninstantiated type class is known at compile time, but the type
     // arguments are determined at runtime by the instantiator(s).
-    const Register kTempReg = kNoRegister;
     return GenerateCallSubtypeTestStub(kTestTypeFourArgs, kInstanceReg,
                                        kInstantiatorTypeArgumentsReg,
                                        kFunctionTypeArgumentsReg, kTempReg,
@@ -500,6 +503,30 @@
   return SubtypeTestCache::null();
 }
 
+// Generates function type check.
+//
+// See [GenerateUninstantiatedTypeTest] for calling convention.
+RawSubtypeTestCache* FlowGraphCompiler::GenerateFunctionTypeTest(
+    TokenPosition token_pos,
+    const AbstractType& type,
+    Label* is_instance_lbl,
+    Label* is_not_instance_lbl) {
+  const Register kInstanceReg = R0;
+  const Register kInstantiatorTypeArgumentsReg = R2;
+  const Register kFunctionTypeArgumentsReg = R1;
+  __ BranchIfSmi(kInstanceReg, is_not_instance_lbl);
+  __ ldm(
+      IA, SP,
+      (1 << kFunctionTypeArgumentsReg) | (1 << kInstantiatorTypeArgumentsReg));
+  // Uninstantiated type class is known at compile time, but the type
+  // arguments are determined at runtime by the instantiator(s).
+  const Register kTempReg = kNoRegister;
+  return GenerateCallSubtypeTestStub(kTestTypeSixArgs, kInstanceReg,
+                                     kInstantiatorTypeArgumentsReg,
+                                     kFunctionTypeArgumentsReg, kTempReg,
+                                     is_instance_lbl, is_not_instance_lbl);
+}
+
 // Inputs:
 // - R0: instance being type checked (preserved).
 // - R2: optional instantiator type arguments (preserved).
@@ -517,12 +544,18 @@
     Label* is_instance_lbl,
     Label* is_not_instance_lbl) {
   __ Comment("InlineInstanceof");
+
+  if (type.IsFunctionType()) {
+    return GenerateFunctionTypeTest(token_pos, type, is_instance_lbl,
+                                    is_not_instance_lbl);
+  }
+
   if (type.IsInstantiated()) {
     const Class& type_class = Class::ZoneHandle(zone(), type.type_class());
     // A class equality check is only applicable with a dst type (not a
     // function type) of a non-parameterized class or with a raw dst type of
     // a parameterized class.
-    if (type.IsFunctionType() || (type_class.NumTypeArguments() > 0)) {
+    if (type_class.NumTypeArguments() > 0) {
       return GenerateInstantiatedTypeWithArgumentsTest(
           token_pos, type, is_instance_lbl, is_not_instance_lbl);
       // Fall through to runtime call.
diff --git a/runtime/vm/compiler/backend/flow_graph_compiler_arm64.cc b/runtime/vm/compiler/backend/flow_graph_compiler_arm64.cc
index cf56eec..d9eba6e 100644
--- a/runtime/vm/compiler/backend/flow_graph_compiler_arm64.cc
+++ b/runtime/vm/compiler/backend/flow_graph_compiler_arm64.cc
@@ -214,6 +214,10 @@
     ASSERT(instantiator_type_arguments_reg == R1);
     ASSERT(function_type_arguments_reg == R2);
     __ BranchLink(*StubCode::Subtype4TestCache_entry());
+  } else if (test_kind == kTestTypeSixArgs) {
+    ASSERT(instantiator_type_arguments_reg == R1);
+    ASSERT(function_type_arguments_reg == R2);
+    __ BranchLink(*StubCode::Subtype6TestCache_entry());
   } else {
     UNREACHABLE();
   }
@@ -235,8 +239,9 @@
     Label* is_not_instance_lbl) {
   __ Comment("InstantiatedTypeWithArgumentsTest");
   ASSERT(type.IsInstantiated());
+  ASSERT(!type.IsFunctionType());
   const Class& type_class = Class::ZoneHandle(zone(), type.type_class());
-  ASSERT(type.IsFunctionType() || (type_class.NumTypeArguments() > 0));
+  ASSERT(type_class.NumTypeArguments() > 0);
   const Register kInstanceReg = R0;
   Error& bound_error = Error::Handle(zone());
   const Type& int_type = Type::Handle(zone(), Type::IntType());
@@ -246,42 +251,40 @@
   ASSERT(smi_is_ok || bound_error.IsNull());
   __ BranchIfSmi(kInstanceReg,
                  smi_is_ok ? is_instance_lbl : is_not_instance_lbl);
-  // A function type test requires checking the function signature.
-  if (!type.IsFunctionType()) {
-    const intptr_t num_type_args = type_class.NumTypeArguments();
-    const intptr_t num_type_params = type_class.NumTypeParameters();
-    const intptr_t from_index = num_type_args - num_type_params;
-    const TypeArguments& type_arguments =
-        TypeArguments::ZoneHandle(zone(), type.arguments());
-    const bool is_raw_type = type_arguments.IsNull() ||
-                             type_arguments.IsRaw(from_index, num_type_params);
-    if (is_raw_type) {
-      const Register kClassIdReg = R2;
-      // dynamic type argument, check only classes.
-      __ LoadClassId(kClassIdReg, kInstanceReg);
-      __ CompareImmediate(kClassIdReg, type_class.id());
-      __ b(is_instance_lbl, EQ);
-      // List is a very common case.
-      if (IsListClass(type_class)) {
-        GenerateListTypeCheck(kClassIdReg, is_instance_lbl);
-      }
-      return GenerateSubtype1TestCacheLookup(
-          token_pos, type_class, is_instance_lbl, is_not_instance_lbl);
+
+  const intptr_t num_type_args = type_class.NumTypeArguments();
+  const intptr_t num_type_params = type_class.NumTypeParameters();
+  const intptr_t from_index = num_type_args - num_type_params;
+  const TypeArguments& type_arguments =
+      TypeArguments::ZoneHandle(zone(), type.arguments());
+  const bool is_raw_type = type_arguments.IsNull() ||
+                           type_arguments.IsRaw(from_index, num_type_params);
+  if (is_raw_type) {
+    const Register kClassIdReg = R2;
+    // dynamic type argument, check only classes.
+    __ LoadClassId(kClassIdReg, kInstanceReg);
+    __ CompareImmediate(kClassIdReg, type_class.id());
+    __ b(is_instance_lbl, EQ);
+    // List is a very common case.
+    if (IsListClass(type_class)) {
+      GenerateListTypeCheck(kClassIdReg, is_instance_lbl);
     }
-    // If one type argument only, check if type argument is Object or dynamic.
-    if (type_arguments.Length() == 1) {
-      const AbstractType& tp_argument =
-          AbstractType::ZoneHandle(zone(), type_arguments.TypeAt(0));
-      ASSERT(!tp_argument.IsMalformed());
-      if (tp_argument.IsType()) {
-        ASSERT(tp_argument.HasResolvedTypeClass());
-        // Check if type argument is dynamic or Object.
-        const Type& object_type = Type::Handle(zone(), Type::ObjectType());
-        if (object_type.IsSubtypeOf(tp_argument, NULL, NULL, Heap::kOld)) {
-          // Instance class test only necessary.
-          return GenerateSubtype1TestCacheLookup(
-              token_pos, type_class, is_instance_lbl, is_not_instance_lbl);
-        }
+    return GenerateSubtype1TestCacheLookup(
+        token_pos, type_class, is_instance_lbl, is_not_instance_lbl);
+  }
+  // If one type argument only, check if type argument is Object or dynamic.
+  if (type_arguments.Length() == 1) {
+    const AbstractType& tp_argument =
+        AbstractType::ZoneHandle(zone(), type_arguments.TypeAt(0));
+    ASSERT(!tp_argument.IsMalformed());
+    if (tp_argument.IsType()) {
+      ASSERT(tp_argument.HasResolvedTypeClass());
+      // Check if type argument is dynamic or Object.
+      const Type& object_type = Type::Handle(zone(), Type::ObjectType());
+      if (object_type.IsSubtypeOf(tp_argument, NULL, NULL, Heap::kOld)) {
+        // Instance class test only necessary.
+        return GenerateSubtype1TestCacheLookup(
+            token_pos, type_class, is_instance_lbl, is_not_instance_lbl);
       }
     }
   }
@@ -319,10 +322,7 @@
     Label* is_not_instance_lbl) {
   __ Comment("InstantiatedTypeNoArgumentsTest");
   ASSERT(type.IsInstantiated());
-  if (type.IsFunctionType()) {
-    // Fallthrough.
-    return true;
-  }
+  ASSERT(!type.IsFunctionType());
   const Class& type_class = Class::Handle(zone(), type.type_class());
   ASSERT(type_class.NumTypeArguments() == 0);
 
@@ -414,16 +414,22 @@
     Label* is_instance_lbl,
     Label* is_not_instance_lbl) {
   __ Comment("UninstantiatedTypeTest");
+  const Register kInstanceReg = R0;
+  const Register kInstantiatorTypeArgumentsReg = R1;
+  const Register kFunctionTypeArgumentsReg = R2;
+  const Register kTempReg = kNoRegister;
   ASSERT(!type.IsInstantiated());
+  ASSERT(!type.IsFunctionType());
   // Skip check if destination is a dynamic type.
   if (type.IsTypeParameter()) {
     const TypeParameter& type_param = TypeParameter::Cast(type);
+    const AbstractType& bound = AbstractType::Handle(type_param.bound());
+
     // Get instantiator type args (high, R1) and function type args (low, R2).
     __ ldp(R2, R1, Address(SP, 0 * kWordSize, Address::PairOffset));
-    // R1: instantiator type arguments.
-    // R2: function type arguments.
-    const Register kTypeArgumentsReg =
-        type_param.IsClassTypeParameter() ? R1 : R2;
+    const Register kTypeArgumentsReg = type_param.IsClassTypeParameter()
+                                           ? kInstantiatorTypeArgumentsReg
+                                           : kFunctionTypeArgumentsReg;
     // Check if type arguments are null, i.e. equivalent to vector of dynamic.
     __ CompareObject(kTypeArgumentsReg, Object::null_object());
     __ b(is_instance_lbl, EQ);
@@ -448,32 +454,29 @@
     Label fall_through;
     __ b(&fall_through);
 
+    // If it's guaranteed, by type-parameter bound, that the type parameter will
+    // never have a value of a function type, then we can safely do a 4-type
+    // test instead of a 6-type test.
+    auto test_kind = !bound.IsTopType() && !bound.IsFunctionType() &&
+                             !bound.IsDartFunctionType() && bound.IsType()
+                         ? kTestTypeSixArgs
+                         : kTestTypeFourArgs;
+
     __ Bind(&not_smi);
-    // R1: instantiator type arguments.
-    // R2: function type arguments.
-    // R0: instance.
-    const Register kInstanceReg = R0;
-    const Register kInstantiatorTypeArgumentsReg = R1;
-    const Register kFunctionTypeArgumentsReg = R2;
-    const Register kTempReg = kNoRegister;
     const SubtypeTestCache& type_test_cache = SubtypeTestCache::ZoneHandle(
         zone(), GenerateCallSubtypeTestStub(
-                    kTestTypeFourArgs, kInstanceReg,
-                    kInstantiatorTypeArgumentsReg, kFunctionTypeArgumentsReg,
-                    kTempReg, is_instance_lbl, is_not_instance_lbl));
+                    test_kind, kInstanceReg, kInstantiatorTypeArgumentsReg,
+                    kFunctionTypeArgumentsReg, kTempReg, is_instance_lbl,
+                    is_not_instance_lbl));
     __ Bind(&fall_through);
     return type_test_cache.raw();
   }
   if (type.IsType()) {
-    const Register kInstanceReg = R0;
-    const Register kInstantiatorTypeArgumentsReg = R1;
-    const Register kFunctionTypeArgumentsReg = R2;
-    __ BranchIfSmi(kInstanceReg, is_not_instance_lbl);  // Is instance Smi?
+    __ BranchIfSmi(kInstanceReg, is_not_instance_lbl);
     __ ldp(kFunctionTypeArgumentsReg, kInstantiatorTypeArgumentsReg,
            Address(SP, 0 * kWordSize, Address::PairOffset));
     // Uninstantiated type class is known at compile time, but the type
     // arguments are determined at runtime by the instantiator.
-    const Register kTempReg = kNoRegister;
     return GenerateCallSubtypeTestStub(kTestTypeFourArgs, kInstanceReg,
                                        kInstantiatorTypeArgumentsReg,
                                        kFunctionTypeArgumentsReg, kTempReg,
@@ -482,6 +485,29 @@
   return SubtypeTestCache::null();
 }
 
+// Generates function type check.
+//
+// See [GenerateUninstantiatedTypeTest] for calling convention.
+RawSubtypeTestCache* FlowGraphCompiler::GenerateFunctionTypeTest(
+    TokenPosition token_pos,
+    const AbstractType& type,
+    Label* is_instance_lbl,
+    Label* is_not_instance_lbl) {
+  const Register kInstanceReg = R0;
+  const Register kInstantiatorTypeArgumentsReg = R1;
+  const Register kFunctionTypeArgumentsReg = R2;
+  __ BranchIfSmi(kInstanceReg, is_not_instance_lbl);
+  __ ldp(kFunctionTypeArgumentsReg, kInstantiatorTypeArgumentsReg,
+         Address(SP, 0 * kWordSize, Address::PairOffset));
+  // Uninstantiated type class is known at compile time, but the type
+  // arguments are determined at runtime by the instantiator(s).
+  const Register kTempReg = kNoRegister;
+  return GenerateCallSubtypeTestStub(kTestTypeSixArgs, kInstanceReg,
+                                     kInstantiatorTypeArgumentsReg,
+                                     kFunctionTypeArgumentsReg, kTempReg,
+                                     is_instance_lbl, is_not_instance_lbl);
+}
+
 // Inputs:
 // - R0: instance being type checked (preserved).
 // - R1: optional instantiator type arguments (preserved).
@@ -499,12 +525,18 @@
     Label* is_instance_lbl,
     Label* is_not_instance_lbl) {
   __ Comment("InlineInstanceof");
+
+  if (type.IsFunctionType()) {
+    return GenerateFunctionTypeTest(token_pos, type, is_instance_lbl,
+                                    is_not_instance_lbl);
+  }
+
   if (type.IsInstantiated()) {
     const Class& type_class = Class::ZoneHandle(zone(), type.type_class());
     // A class equality check is only applicable with a dst type (not a
     // function type) of a non-parameterized class or with a raw dst type of
     // a parameterized class.
-    if (type.IsFunctionType() || (type_class.NumTypeArguments() > 0)) {
+    if (type_class.NumTypeArguments() > 0) {
       return GenerateInstantiatedTypeWithArgumentsTest(
           token_pos, type, is_instance_lbl, is_not_instance_lbl);
       // Fall through to runtime call.
diff --git a/runtime/vm/compiler/backend/flow_graph_compiler_ia32.cc b/runtime/vm/compiler/backend/flow_graph_compiler_ia32.cc
index 6e4f305..77e3d0a 100644
--- a/runtime/vm/compiler/backend/flow_graph_compiler_ia32.cc
+++ b/runtime/vm/compiler/backend/flow_graph_compiler_ia32.cc
@@ -222,6 +222,10 @@
     __ pushl(instantiator_type_arguments_reg);
     __ pushl(function_type_arguments_reg);
     __ Call(*StubCode::Subtype4TestCache_entry());
+  } else if (test_kind == kTestTypeSixArgs) {
+    __ pushl(instantiator_type_arguments_reg);
+    __ pushl(function_type_arguments_reg);
+    __ Call(*StubCode::Subtype6TestCache_entry());
   } else {
     UNREACHABLE();
   }
@@ -248,8 +252,9 @@
     Label* is_not_instance_lbl) {
   __ Comment("InstantiatedTypeWithArgumentsTest");
   ASSERT(type.IsInstantiated());
+  ASSERT(!type.IsFunctionType());
   const Class& type_class = Class::ZoneHandle(zone(), type.type_class());
-  ASSERT(type.IsFunctionType() || (type_class.NumTypeArguments() > 0));
+  ASSERT(type_class.NumTypeArguments() > 0);
   const Register kInstanceReg = EAX;
   Error& bound_error = Error::Handle(zone());
   const Type& int_type = Type::Handle(zone(), Type::IntType());
@@ -263,42 +268,39 @@
   } else {
     __ j(ZERO, is_not_instance_lbl);
   }
-  // A function type test requires checking the function signature.
-  if (!type.IsFunctionType()) {
-    const intptr_t num_type_args = type_class.NumTypeArguments();
-    const intptr_t num_type_params = type_class.NumTypeParameters();
-    const intptr_t from_index = num_type_args - num_type_params;
-    const TypeArguments& type_arguments =
-        TypeArguments::ZoneHandle(zone(), type.arguments());
-    const bool is_raw_type = type_arguments.IsNull() ||
-                             type_arguments.IsRaw(from_index, num_type_params);
-    if (is_raw_type) {
-      const Register kClassIdReg = ECX;
-      // dynamic type argument, check only classes.
-      __ LoadClassId(kClassIdReg, kInstanceReg);
-      __ cmpl(kClassIdReg, Immediate(type_class.id()));
-      __ j(EQUAL, is_instance_lbl);
-      // List is a very common case.
-      if (IsListClass(type_class)) {
-        GenerateListTypeCheck(kClassIdReg, is_instance_lbl);
-      }
-      return GenerateSubtype1TestCacheLookup(
-          token_pos, type_class, is_instance_lbl, is_not_instance_lbl);
+  const intptr_t num_type_args = type_class.NumTypeArguments();
+  const intptr_t num_type_params = type_class.NumTypeParameters();
+  const intptr_t from_index = num_type_args - num_type_params;
+  const TypeArguments& type_arguments =
+      TypeArguments::ZoneHandle(zone(), type.arguments());
+  const bool is_raw_type = type_arguments.IsNull() ||
+                           type_arguments.IsRaw(from_index, num_type_params);
+  if (is_raw_type) {
+    const Register kClassIdReg = ECX;
+    // dynamic type argument, check only classes.
+    __ LoadClassId(kClassIdReg, kInstanceReg);
+    __ cmpl(kClassIdReg, Immediate(type_class.id()));
+    __ j(EQUAL, is_instance_lbl);
+    // List is a very common case.
+    if (IsListClass(type_class)) {
+      GenerateListTypeCheck(kClassIdReg, is_instance_lbl);
     }
-    // If one type argument only, check if type argument is Object or dynamic.
-    if (type_arguments.Length() == 1) {
-      const AbstractType& tp_argument =
-          AbstractType::ZoneHandle(zone(), type_arguments.TypeAt(0));
-      ASSERT(!tp_argument.IsMalformed());
-      if (tp_argument.IsType()) {
-        ASSERT(tp_argument.HasResolvedTypeClass());
-        // Check if type argument is dynamic or Object.
-        const Type& object_type = Type::Handle(zone(), Type::ObjectType());
-        if (object_type.IsSubtypeOf(tp_argument, NULL, NULL, Heap::kOld)) {
-          // Instance class test only necessary.
-          return GenerateSubtype1TestCacheLookup(
-              token_pos, type_class, is_instance_lbl, is_not_instance_lbl);
-        }
+    return GenerateSubtype1TestCacheLookup(
+        token_pos, type_class, is_instance_lbl, is_not_instance_lbl);
+  }
+  // If one type argument only, check if type argument is Object or dynamic.
+  if (type_arguments.Length() == 1) {
+    const AbstractType& tp_argument =
+        AbstractType::ZoneHandle(zone(), type_arguments.TypeAt(0));
+    ASSERT(!tp_argument.IsMalformed());
+    if (tp_argument.IsType()) {
+      ASSERT(tp_argument.HasResolvedTypeClass());
+      // Check if type argument is dynamic or Object.
+      const Type& object_type = Type::Handle(zone(), Type::ObjectType());
+      if (object_type.IsSubtypeOf(tp_argument, NULL, NULL, Heap::kOld)) {
+        // Instance class test only necessary.
+        return GenerateSubtype1TestCacheLookup(
+            token_pos, type_class, is_instance_lbl, is_not_instance_lbl);
       }
     }
   }
@@ -335,10 +337,7 @@
     Label* is_not_instance_lbl) {
   __ Comment("InstantiatedTypeNoArgumentsTest");
   ASSERT(type.IsInstantiated());
-  if (type.IsFunctionType()) {
-    // Fallthrough.
-    return true;
-  }
+  ASSERT(!type.IsFunctionType());
   const Class& type_class = Class::Handle(zone(), type.type_class());
   ASSERT(type_class.NumTypeArguments() == 0);
 
@@ -432,12 +431,19 @@
     Label* is_instance_lbl,
     Label* is_not_instance_lbl) {
   __ Comment("UninstantiatedTypeTest");
+  const Register kInstanceReg = EAX;
+  const Register kInstantiatorTypeArgumentsReg = EDX;
+  const Register kFunctionTypeArgumentsReg = ECX;
+  const Register kTempReg = EDI;
   ASSERT(!type.IsInstantiated());
+  ASSERT(!type.IsFunctionType());
   // Skip check if destination is a dynamic type.
   const Immediate& raw_null =
       Immediate(reinterpret_cast<intptr_t>(Object::null()));
   if (type.IsTypeParameter()) {
     const TypeParameter& type_param = TypeParameter::Cast(type);
+    const AbstractType& bound = AbstractType::Handle(type_param.bound());
+
     __ movl(EDX, Address(ESP, 1 * kWordSize));  // Get instantiator type args.
     __ movl(ECX, Address(ESP, 0 * kWordSize));  // Get function type args.
     // EDX: instantiator type arguments.
@@ -469,33 +475,29 @@
     Label fall_through;
     __ jmp(&fall_through);
 
+    // If it's guaranteed, by type-parameter bound, that the type parameter will
+    // never have a value of a function type.
+    auto test_kind = !bound.IsTopType() && !bound.IsFunctionType() &&
+                             !bound.IsDartFunctionType() && bound.IsType()
+                         ? kTestTypeSixArgs
+                         : kTestTypeFourArgs;
+
     __ Bind(&not_smi);
-    // EDX: instantiator type arguments.
-    // ECX: function type arguments.
-    // EAX: instance.
-    const Register kInstanceReg = EAX;
-    const Register kInstantiatorTypeArgumentsReg = EDX;
-    const Register kFunctionTypeArgumentsReg = ECX;
-    const Register kTempReg = EDI;
     const SubtypeTestCache& type_test_cache = SubtypeTestCache::ZoneHandle(
         zone(), GenerateCallSubtypeTestStub(
-                    kTestTypeFourArgs, kInstanceReg,
-                    kInstantiatorTypeArgumentsReg, kFunctionTypeArgumentsReg,
-                    kTempReg, is_instance_lbl, is_not_instance_lbl));
+                    test_kind, kInstanceReg, kInstantiatorTypeArgumentsReg,
+                    kFunctionTypeArgumentsReg, kTempReg, is_instance_lbl,
+                    is_not_instance_lbl));
     __ Bind(&fall_through);
     return type_test_cache.raw();
   }
   if (type.IsType()) {
-    const Register kInstanceReg = EAX;
-    const Register kInstantiatorTypeArgumentsReg = EDX;
-    const Register kFunctionTypeArgumentsReg = ECX;
     __ testl(kInstanceReg, Immediate(kSmiTagMask));  // Is instance Smi?
     __ j(ZERO, is_not_instance_lbl);
     __ movl(kInstantiatorTypeArgumentsReg, Address(ESP, 1 * kWordSize));
     __ movl(kFunctionTypeArgumentsReg, Address(ESP, 0 * kWordSize));
     // Uninstantiated type class is known at compile time, but the type
     // arguments are determined at runtime by the instantiator(s).
-    const Register kTempReg = EDI;
     return GenerateCallSubtypeTestStub(kTestTypeFourArgs, kInstanceReg,
                                        kInstantiatorTypeArgumentsReg,
                                        kFunctionTypeArgumentsReg, kTempReg,
@@ -504,6 +506,30 @@
   return SubtypeTestCache::null();
 }
 
+// Generates function type check.
+//
+// See [GenerateUninstantiatedTypeTest] for calling convention.
+RawSubtypeTestCache* FlowGraphCompiler::GenerateFunctionTypeTest(
+    TokenPosition token_pos,
+    const AbstractType& type,
+    Label* is_instance_lbl,
+    Label* is_not_instance_lbl) {
+  const Register kInstanceReg = EAX;
+  const Register kInstantiatorTypeArgumentsReg = EDX;
+  const Register kFunctionTypeArgumentsReg = ECX;
+  __ Comment("FunctionTypeTest");
+
+  __ testl(kInstanceReg, Immediate(kSmiTagMask));
+  __ j(ZERO, is_not_instance_lbl);
+  // Uninstantiated type class is known at compile time, but the type
+  // arguments are determined at runtime by the instantiator(s).
+  const Register kTempReg = EDI;
+  return GenerateCallSubtypeTestStub(kTestTypeSixArgs, kInstanceReg,
+                                     kInstantiatorTypeArgumentsReg,
+                                     kFunctionTypeArgumentsReg, kTempReg,
+                                     is_instance_lbl, is_not_instance_lbl);
+}
+
 // Inputs:
 // - EAX: instance to test against (preserved).
 // - EDX: optional instantiator type arguments (preserved).
@@ -521,12 +547,18 @@
     Label* is_instance_lbl,
     Label* is_not_instance_lbl) {
   __ Comment("InlineInstanceof");
+
+  if (type.IsFunctionType()) {
+    return GenerateFunctionTypeTest(token_pos, type, is_instance_lbl,
+                                    is_not_instance_lbl);
+  }
+
   if (type.IsInstantiated()) {
     const Class& type_class = Class::ZoneHandle(zone(), type.type_class());
     // A class equality check is only applicable with a dst type (not a
     // function type) of a non-parameterized class or with a raw dst type of
     // a parameterized class.
-    if (type.IsFunctionType() || (type_class.NumTypeArguments() > 0)) {
+    if (type_class.NumTypeArguments() > 0) {
       return GenerateInstantiatedTypeWithArgumentsTest(
           token_pos, type, is_instance_lbl, is_not_instance_lbl);
       // Fall through to runtime call.
diff --git a/runtime/vm/compiler/backend/flow_graph_compiler_x64.cc b/runtime/vm/compiler/backend/flow_graph_compiler_x64.cc
index c62e0dd..427a297 100644
--- a/runtime/vm/compiler/backend/flow_graph_compiler_x64.cc
+++ b/runtime/vm/compiler/backend/flow_graph_compiler_x64.cc
@@ -221,6 +221,10 @@
     ASSERT(RDX == instantiator_type_arguments_reg);
     ASSERT(RCX == function_type_arguments_reg);
     __ Call(*StubCode::Subtype4TestCache_entry());
+  } else if (test_kind == kTestTypeSixArgs) {
+    ASSERT(RDX == instantiator_type_arguments_reg);
+    ASSERT(RCX == function_type_arguments_reg);
+    __ Call(*StubCode::Subtype6TestCache_entry());
   } else {
     UNREACHABLE();
   }
@@ -242,8 +246,9 @@
     Label* is_not_instance_lbl) {
   __ Comment("InstantiatedTypeWithArgumentsTest");
   ASSERT(type.IsInstantiated());
+  ASSERT(!type.IsFunctionType());
   const Class& type_class = Class::ZoneHandle(zone(), type.type_class());
-  ASSERT(type.IsFunctionType() || (type_class.NumTypeArguments() > 0));
+  ASSERT(type_class.NumTypeArguments() > 0);
   const Register kInstanceReg = RAX;
   Error& bound_error = Error::Handle(zone());
   const Type& int_type = Type::Handle(zone(), Type::IntType());
@@ -257,45 +262,44 @@
   } else {
     __ j(ZERO, is_not_instance_lbl);
   }
-  // A function type test requires checking the function signature.
-  if (!type.IsFunctionType()) {
-    const intptr_t num_type_args = type_class.NumTypeArguments();
-    const intptr_t num_type_params = type_class.NumTypeParameters();
-    const intptr_t from_index = num_type_args - num_type_params;
-    const TypeArguments& type_arguments =
-        TypeArguments::ZoneHandle(zone(), type.arguments());
-    const bool is_raw_type = type_arguments.IsNull() ||
-                             type_arguments.IsRaw(from_index, num_type_params);
-    if (is_raw_type) {
-      const Register kClassIdReg = R10;
-      // dynamic type argument, check only classes.
-      __ LoadClassId(kClassIdReg, kInstanceReg);
-      __ cmpl(kClassIdReg, Immediate(type_class.id()));
-      __ j(EQUAL, is_instance_lbl);
-      // List is a very common case.
-      if (IsListClass(type_class)) {
-        GenerateListTypeCheck(kClassIdReg, is_instance_lbl);
-      }
-      return GenerateSubtype1TestCacheLookup(
-          token_pos, type_class, is_instance_lbl, is_not_instance_lbl);
+
+  const intptr_t num_type_args = type_class.NumTypeArguments();
+  const intptr_t num_type_params = type_class.NumTypeParameters();
+  const intptr_t from_index = num_type_args - num_type_params;
+  const TypeArguments& type_arguments =
+      TypeArguments::ZoneHandle(zone(), type.arguments());
+  const bool is_raw_type = type_arguments.IsNull() ||
+                           type_arguments.IsRaw(from_index, num_type_params);
+  if (is_raw_type) {
+    const Register kClassIdReg = R10;
+    // dynamic type argument, check only classes.
+    __ LoadClassId(kClassIdReg, kInstanceReg);
+    __ cmpl(kClassIdReg, Immediate(type_class.id()));
+    __ j(EQUAL, is_instance_lbl);
+    // List is a very common case.
+    if (IsListClass(type_class)) {
+      GenerateListTypeCheck(kClassIdReg, is_instance_lbl);
     }
-    // If one type argument only, check if type argument is Object or dynamic.
-    if (type_arguments.Length() == 1) {
-      const AbstractType& tp_argument =
-          AbstractType::ZoneHandle(zone(), type_arguments.TypeAt(0));
-      ASSERT(!tp_argument.IsMalformed());
-      if (tp_argument.IsType()) {
-        ASSERT(tp_argument.HasResolvedTypeClass());
-        // Check if type argument is dynamic or Object.
-        const Type& object_type = Type::Handle(zone(), Type::ObjectType());
-        if (object_type.IsSubtypeOf(tp_argument, NULL, NULL, Heap::kOld)) {
-          // Instance class test only necessary.
-          return GenerateSubtype1TestCacheLookup(
-              token_pos, type_class, is_instance_lbl, is_not_instance_lbl);
-        }
+    return GenerateSubtype1TestCacheLookup(
+        token_pos, type_class, is_instance_lbl, is_not_instance_lbl);
+  }
+  // If one type argument only, check if type argument is Object or dynamic.
+  if (type_arguments.Length() == 1) {
+    const AbstractType& tp_argument =
+        AbstractType::ZoneHandle(zone(), type_arguments.TypeAt(0));
+    ASSERT(!tp_argument.IsMalformed());
+    if (tp_argument.IsType()) {
+      ASSERT(tp_argument.HasResolvedTypeClass());
+      // Check if type argument is dynamic or Object.
+      const Type& object_type = Type::Handle(zone(), Type::ObjectType());
+      if (object_type.IsSubtypeOf(tp_argument, NULL, NULL, Heap::kOld)) {
+        // Instance class test only necessary.
+        return GenerateSubtype1TestCacheLookup(
+            token_pos, type_class, is_instance_lbl, is_not_instance_lbl);
       }
     }
   }
+
   // Regular subtype test cache involving instance's type arguments.
   const Register kInstantiatorTypeArgumentsReg = kNoRegister;
   const Register kFunctionTypeArgumentsReg = kNoRegister;
@@ -333,10 +337,7 @@
     Label* is_not_instance_lbl) {
   __ Comment("InstantiatedTypeNoArgumentsTest");
   ASSERT(type.IsInstantiated());
-  if (type.IsFunctionType()) {
-    // Fallthrough.
-    return true;
-  }
+  ASSERT(!type.IsFunctionType());
   const Class& type_class = Class::Handle(zone(), type.type_class());
   ASSERT(type_class.NumTypeArguments() == 0);
 
@@ -444,9 +445,12 @@
   const Register kTempReg = kNoRegister;
   __ Comment("UninstantiatedTypeTest");
   ASSERT(!type.IsInstantiated());
+  ASSERT(!type.IsFunctionType());
   // Skip check if destination is a dynamic type.
   if (type.IsTypeParameter()) {
     const TypeParameter& type_param = TypeParameter::Cast(type);
+    const AbstractType& bound = AbstractType::Handle(type_param.bound());
+
     // RDX: instantiator type arguments.
     // RCX: function type arguments.
     const Register kTypeArgumentsReg =
@@ -477,12 +481,20 @@
     Label fall_through;
     __ jmp(&fall_through);
 
+    // If it's guaranteed, by type-parameter bound, that the type parameter will
+    // never have a value of a function type, then we can safely do a 4-type
+    // test instead of a 6-type test.
+    auto test_kind = !bound.IsTopType() && !bound.IsFunctionType() &&
+                             !bound.IsDartFunctionType() && bound.IsType()
+                         ? kTestTypeSixArgs
+                         : kTestTypeFourArgs;
+
     __ Bind(&not_smi);
     const SubtypeTestCache& type_test_cache = SubtypeTestCache::ZoneHandle(
         zone(), GenerateCallSubtypeTestStub(
-                    kTestTypeFourArgs, kInstanceReg,
-                    kInstantiatorTypeArgumentsReg, kFunctionTypeArgumentsReg,
-                    kTempReg, is_instance_lbl, is_not_instance_lbl));
+                    test_kind, kInstanceReg, kInstantiatorTypeArgumentsReg,
+                    kFunctionTypeArgumentsReg, kTempReg, is_instance_lbl,
+                    is_not_instance_lbl));
     __ Bind(&fall_through);
     return type_test_cache.raw();
   }
@@ -499,6 +511,28 @@
   return SubtypeTestCache::null();
 }
 
+// Generates function type check.
+//
+// See [GenerateUninstantiatedTypeTest] for calling convention.
+RawSubtypeTestCache* FlowGraphCompiler::GenerateFunctionTypeTest(
+    TokenPosition token_pos,
+    const AbstractType& type,
+    Label* is_instance_lbl,
+    Label* is_not_instance_lbl) {
+  const Register kInstanceReg = RAX;
+  const Register kInstantiatorTypeArgumentsReg = RDX;
+  const Register kFunctionTypeArgumentsReg = RCX;
+  const Register kTempReg = kNoRegister;
+  __ Comment("FunctionTypeTest");
+
+  __ testq(kInstanceReg, Immediate(kSmiTagMask));
+  __ j(ZERO, is_not_instance_lbl);
+  return GenerateCallSubtypeTestStub(kTestTypeSixArgs, kInstanceReg,
+                                     kInstantiatorTypeArgumentsReg,
+                                     kFunctionTypeArgumentsReg, kTempReg,
+                                     is_instance_lbl, is_not_instance_lbl);
+}
+
 // Inputs:
 //   - RAX : instance to test against.
 //   - RDX : instantiator type arguments.
@@ -515,12 +549,18 @@
     Label* is_instance_lbl,
     Label* is_not_instance_lbl) {
   __ Comment("InlineInstanceof");
+
+  if (type.IsFunctionType()) {
+    return GenerateFunctionTypeTest(token_pos, type, is_instance_lbl,
+                                    is_not_instance_lbl);
+  }
+
   if (type.IsInstantiated()) {
     const Class& type_class = Class::ZoneHandle(zone(), type.type_class());
     // A class equality check is only applicable with a dst type (not a
     // function type) of a non-parameterized class or with a raw dst type of
     // a parameterized class.
-    if (type.IsFunctionType() || (type_class.NumTypeArguments() > 0)) {
+    if (type_class.NumTypeArguments() > 0) {
       return GenerateInstantiatedTypeWithArgumentsTest(
           token_pos, type, is_instance_lbl, is_not_instance_lbl);
       // Fall through to runtime call.
diff --git a/runtime/vm/object.cc b/runtime/vm/object.cc
index 4639c40..dbefe80 100644
--- a/runtime/vm/object.cc
+++ b/runtime/vm/object.cc
@@ -15746,6 +15746,8 @@
     const TypeArguments& instance_type_arguments,
     const TypeArguments& instantiator_type_arguments,
     const TypeArguments& function_type_arguments,
+    const TypeArguments& instance_parent_function_type_arguments,
+    const TypeArguments& instance_delayed_type_arguments,
     const Bool& test_result) const {
   intptr_t old_num = NumberOfChecks();
   Array& data = Array::Handle(cache());
@@ -15759,15 +15761,22 @@
   data.SetAt(data_pos + kInstantiatorTypeArguments,
              instantiator_type_arguments);
   data.SetAt(data_pos + kFunctionTypeArguments, function_type_arguments);
+  data.SetAt(data_pos + kInstanceParentFunctionTypeArguments,
+             instance_parent_function_type_arguments);
+  data.SetAt(data_pos + kInstanceDelayedFunctionTypeArguments,
+             instance_delayed_type_arguments);
   data.SetAt(data_pos + kTestResult, test_result);
 }
 
-void SubtypeTestCache::GetCheck(intptr_t ix,
-                                Object* instance_class_id_or_function,
-                                TypeArguments* instance_type_arguments,
-                                TypeArguments* instantiator_type_arguments,
-                                TypeArguments* function_type_arguments,
-                                Bool* test_result) const {
+void SubtypeTestCache::GetCheck(
+    intptr_t ix,
+    Object* instance_class_id_or_function,
+    TypeArguments* instance_type_arguments,
+    TypeArguments* instantiator_type_arguments,
+    TypeArguments* function_type_arguments,
+    TypeArguments* instance_parent_function_type_arguments,
+    TypeArguments* instance_delayed_type_arguments,
+    Bool* test_result) const {
   Array& data = Array::Handle(cache());
   intptr_t data_pos = ix * kTestEntryLength;
   *instance_class_id_or_function =
@@ -15776,6 +15785,10 @@
   *instantiator_type_arguments ^=
       data.At(data_pos + kInstantiatorTypeArguments);
   *function_type_arguments ^= data.At(data_pos + kFunctionTypeArguments);
+  *instance_parent_function_type_arguments ^=
+      data.At(data_pos + kInstanceParentFunctionTypeArguments);
+  *instance_delayed_type_arguments ^=
+      data.At(data_pos + kInstanceDelayedFunctionTypeArguments);
   *test_result ^= data.At(data_pos + kTestResult);
 }
 
diff --git a/runtime/vm/object.h b/runtime/vm/object.h
index 23e1105..7635773 100644
--- a/runtime/vm/object.h
+++ b/runtime/vm/object.h
@@ -958,6 +958,7 @@
     ASSERT(is_valid_id(value));
     StoreNonPointer(&raw_ptr()->id_, value);
   }
+  static intptr_t id_offset() { return OFFSET_OF(RawClass, id_); }
 
   RawString* Name() const;
   RawString* ScrubbedName() const;
@@ -5413,12 +5414,14 @@
 class SubtypeTestCache : public Object {
  public:
   enum Entries {
-    kInstanceClassIdOrFunction = 0,
-    kInstanceTypeArguments = 1,
-    kInstantiatorTypeArguments = 2,
-    kFunctionTypeArguments = 3,
-    kTestResult = 4,
-    kTestEntryLength = 5,
+    kTestResult = 0,
+    kInstanceClassIdOrFunction = 1,
+    kInstanceTypeArguments = 2,
+    kInstantiatorTypeArguments = 3,
+    kFunctionTypeArguments = 4,
+    kInstanceParentFunctionTypeArguments = 5,
+    kInstanceDelayedFunctionTypeArguments = 6,
+    kTestEntryLength = 7,
   };
 
   intptr_t NumberOfChecks() const;
@@ -5426,12 +5429,16 @@
                 const TypeArguments& instance_type_arguments,
                 const TypeArguments& instantiator_type_arguments,
                 const TypeArguments& function_type_arguments,
+                const TypeArguments& instance_parent_function_type_arguments,
+                const TypeArguments& instance_delayed_type_arguments,
                 const Bool& test_result) const;
   void GetCheck(intptr_t ix,
                 Object* instance_class_id_or_function,
                 TypeArguments* instance_type_arguments,
                 TypeArguments* instantiator_type_arguments,
                 TypeArguments* function_type_arguments,
+                TypeArguments* instance_parent_function_type_arguments,
+                TypeArguments* instance_delayed_type_arguments,
                 Bool* test_result) const;
 
   static RawSubtypeTestCache* New();
@@ -6396,6 +6403,8 @@
   // was parameterized to obtain the actual signature.
   RawFunction* signature() const;
   void set_signature(const Function& value) const;
+  static intptr_t signature_offset() { return OFFSET_OF(RawType, sig_or_err_); }
+
   virtual bool IsFunctionType() const {
     return signature() != Function::null();
   }
diff --git a/runtime/vm/object_test.cc b/runtime/vm/object_test.cc
index ca62225..920aa33 100644
--- a/runtime/vm/object_test.cc
+++ b/runtime/vm/object_test.cc
@@ -3014,15 +3014,20 @@
   const TypeArguments& targ_0 = TypeArguments::Handle(TypeArguments::New(2));
   const TypeArguments& targ_1 = TypeArguments::Handle(TypeArguments::New(3));
   const TypeArguments& targ_2 = TypeArguments::Handle(TypeArguments::New(4));
-  cache.AddCheck(class_id_or_fun, targ_0, targ_1, targ_2, Bool::True());
+  const TypeArguments& targ_3 = TypeArguments::Handle(TypeArguments::New(5));
+  const TypeArguments& targ_4 = TypeArguments::Handle(TypeArguments::New(6));
+  cache.AddCheck(class_id_or_fun, targ_0, targ_1, targ_2, targ_3, targ_4,
+                 Bool::True());
   EXPECT_EQ(1, cache.NumberOfChecks());
   Object& test_class_id_or_fun = Object::Handle();
   TypeArguments& test_targ_0 = TypeArguments::Handle();
   TypeArguments& test_targ_1 = TypeArguments::Handle();
   TypeArguments& test_targ_2 = TypeArguments::Handle();
+  TypeArguments& test_targ_3 = TypeArguments::Handle();
+  TypeArguments& test_targ_4 = TypeArguments::Handle();
   Bool& test_result = Bool::Handle();
   cache.GetCheck(0, &test_class_id_or_fun, &test_targ_0, &test_targ_1,
-                 &test_targ_2, &test_result);
+                 &test_targ_2, &test_targ_3, &test_targ_4, &test_result);
   EXPECT_EQ(class_id_or_fun.raw(), test_class_id_or_fun.raw());
   EXPECT_EQ(targ_0.raw(), test_targ_0.raw());
   EXPECT_EQ(targ_1.raw(), test_targ_1.raw());
diff --git a/runtime/vm/runtime_entry.cc b/runtime/vm/runtime_entry.cc
index cd732db..96d88cd 100644
--- a/runtime/vm/runtime_entry.cc
+++ b/runtime/vm/runtime_entry.cc
@@ -635,6 +635,7 @@
 // evaluation of type arguments.
 // This operation is currently very slow (lookup of code is not efficient yet).
 static void UpdateTypeTestCache(
+    Zone* zone,
     const Instance& instance,
     const AbstractType& type,
     const TypeArguments& instantiator_type_arguments,
@@ -661,27 +662,18 @@
   // when concatenated.
   ASSERT(function_type_arguments.IsNull() ||
          function_type_arguments.IsCanonical());
-  const Class& instance_class = Class::Handle(instance.clazz());
-  Object& instance_class_id_or_function = Object::Handle();
-  TypeArguments& instance_type_arguments = TypeArguments::Handle();
+  const Class& instance_class = Class::Handle(zone, instance.clazz());
+  auto& instance_class_id_or_function = Object::Handle(zone);
+  auto& instance_type_arguments = TypeArguments::Handle(zone);
+  auto& instance_parent_function_type_arguments = TypeArguments::Handle(zone);
+  auto& instance_delayed_type_arguments = TypeArguments::Handle(zone);
   if (instance_class.IsClosureClass()) {
-    // If the closure instance has a generic parent, we cannot perform the
-    // optimization, because one more input (closure.function_type_arguments)
-    // would need to be considered. For now, only perform the optimization if
-    // the closure's function_type_arguments field is null, meaning the closure
-    // function has no generic parent.
-    if (Closure::Cast(instance).function_type_arguments() !=
-        TypeArguments::null()) {
-      if (FLAG_trace_type_checks) {
-        OS::PrintErr(
-            "UpdateTypeTestCache: closure function_type_arguments is "
-            "not null\n");
-      }
-      return;
-    }
-    instance_class_id_or_function = Closure::Cast(instance).function();
-    instance_type_arguments =
-        Closure::Cast(instance).instantiator_type_arguments();
+    const auto& closure = Closure::Cast(instance);
+    const auto& closure_function = Function::Handle(zone, closure.function());
+    instance_class_id_or_function = closure_function.raw();
+    instance_type_arguments = closure.instantiator_type_arguments();
+    instance_parent_function_type_arguments = closure.function_type_arguments();
+    instance_delayed_type_arguments = closure.delayed_type_arguments();
   } else {
     instance_class_id_or_function = Smi::New(instance_class.id());
     if (instance_class.NumTypeArguments() > 0) {
@@ -699,22 +691,34 @@
          instantiator_type_arguments.IsCanonical());
   ASSERT(function_type_arguments.IsNull() ||
          function_type_arguments.IsCanonical());
-  Object& last_instance_class_id_or_function = Object::Handle();
-  TypeArguments& last_instance_type_arguments = TypeArguments::Handle();
-  TypeArguments& last_instantiator_type_arguments = TypeArguments::Handle();
-  TypeArguments& last_function_type_arguments = TypeArguments::Handle();
-  Bool& last_result = Bool::Handle();
+  ASSERT(instance_parent_function_type_arguments.IsNull() ||
+         instance_parent_function_type_arguments.IsCanonical());
+  ASSERT(instance_delayed_type_arguments.IsNull() ||
+         instance_delayed_type_arguments.IsCanonical());
+  auto& last_instance_class_id_or_function = Object::Handle(zone);
+  auto& last_instance_type_arguments = TypeArguments::Handle(zone);
+  auto& last_instantiator_type_arguments = TypeArguments::Handle(zone);
+  auto& last_function_type_arguments = TypeArguments::Handle(zone);
+  auto& last_instance_parent_function_type_arguments =
+      TypeArguments::Handle(zone);
+  auto& last_instance_delayed_type_arguments = TypeArguments::Handle(zone);
+  Bool& last_result = Bool::Handle(zone);
   for (intptr_t i = 0; i < len; ++i) {
-    new_cache.GetCheck(i, &last_instance_class_id_or_function,
-                       &last_instance_type_arguments,
-                       &last_instantiator_type_arguments,
-                       &last_function_type_arguments, &last_result);
+    new_cache.GetCheck(
+        i, &last_instance_class_id_or_function, &last_instance_type_arguments,
+        &last_instantiator_type_arguments, &last_function_type_arguments,
+        &last_instance_parent_function_type_arguments,
+        &last_instance_delayed_type_arguments, &last_result);
     if ((last_instance_class_id_or_function.raw() ==
          instance_class_id_or_function.raw()) &&
         (last_instance_type_arguments.raw() == instance_type_arguments.raw()) &&
         (last_instantiator_type_arguments.raw() ==
          instantiator_type_arguments.raw()) &&
-        (last_function_type_arguments.raw() == function_type_arguments.raw())) {
+        (last_function_type_arguments.raw() == function_type_arguments.raw()) &&
+        (last_instance_parent_function_type_arguments.raw() ==
+         instance_parent_function_type_arguments.raw()) &&
+        (last_instance_delayed_type_arguments.raw() ==
+         instance_delayed_type_arguments.raw())) {
       OS::PrintErr("  Error in test cache %p ix: %" Pd ",", new_cache.raw(), i);
       PrintTypeCheck(" duplicate cache entry", instance, type,
                      instantiator_type_arguments, function_type_arguments,
@@ -726,16 +730,20 @@
 #endif
   new_cache.AddCheck(instance_class_id_or_function, instance_type_arguments,
                      instantiator_type_arguments, function_type_arguments,
-                     result);
+                     instance_parent_function_type_arguments,
+                     instance_delayed_type_arguments, result);
   if (FLAG_trace_type_checks) {
-    AbstractType& test_type = AbstractType::Handle(type.raw());
+    AbstractType& test_type = AbstractType::Handle(zone, type.raw());
     if (!test_type.IsInstantiated()) {
-      Error& bound_error = Error::Handle();
+      Error& bound_error = Error::Handle(zone);
       test_type = type.InstantiateFrom(instantiator_type_arguments,
                                        function_type_arguments, kAllFree,
                                        &bound_error, NULL, NULL, Heap::kNew);
       ASSERT(bound_error.IsNull());  // Malbounded types are not optimized.
     }
+    const auto& type_class = Class::Handle(zone, test_type.type_class());
+    const auto& instance_class_name =
+        String::Handle(zone, instance_class.Name());
     OS::PrintErr(
         "  Updated test cache %p ix: %" Pd
         " with "
@@ -745,20 +753,13 @@
         "),    type-args: %p %s]\n"
         "    test-type [class: (%p '%s' cid: %" Pd
         "), i-type-args: %p %s, f-type-args: %p %s]\n",
-        new_cache.raw(), len,
-
-        instance_class_id_or_function.raw(), instance_type_arguments.raw(),
-        instantiator_type_arguments.raw(), instantiator_type_arguments.raw(),
-        result.ToCString(),
-
-        instance_class.raw(), String::Handle(instance_class.Name()).ToCString(),
+        new_cache.raw(), len, instance_class_id_or_function.raw(),
+        instance_type_arguments.raw(), instantiator_type_arguments.raw(),
+        instantiator_type_arguments.raw(), result.ToCString(),
+        instance_class.raw(), instance_class_name.ToCString(),
         instance_class.id(), instance_type_arguments.raw(),
-        instance_type_arguments.ToCString(),
-
-        test_type.type_class(),
-        String::Handle(Class::Handle(test_type.type_class()).Name())
-            .ToCString(),
-        Class::Handle(test_type.type_class()).id(),
+        instance_type_arguments.ToCString(), type_class.raw(),
+        String::Handle(zone, type_class.Name()).ToCString(), type_class.id(),
         instantiator_type_arguments.raw(),
         instantiator_type_arguments.ToCString(), function_type_arguments.raw(),
         function_type_arguments.ToCString());
@@ -805,7 +806,8 @@
                                         Symbols::Empty(), bound_error_message);
     UNREACHABLE();
   }
-  UpdateTypeTestCache(instance, type, instantiator_type_arguments,
+
+  UpdateTypeTestCache(zone, instance, type, instantiator_type_arguments,
                       function_type_arguments, result, cache);
   arguments.SetReturn(result);
 }
@@ -948,8 +950,9 @@
 #endif
     }
 
-    UpdateTypeTestCache(src_instance, dst_type, instantiator_type_arguments,
-                        function_type_arguments, Bool::True(), cache);
+    UpdateTypeTestCache(zone, src_instance, dst_type,
+                        instantiator_type_arguments, function_type_arguments,
+                        Bool::True(), cache);
   }
 
   arguments.SetReturn(src_instance);
diff --git a/runtime/vm/simulator_dbc.cc b/runtime/vm/simulator_dbc.cc
index 9c0e5b6..c5d2269 100644
--- a/runtime/vm/simulator_dbc.cc
+++ b/runtime/vm/simulator_dbc.cc
@@ -3006,15 +3006,17 @@
         RawTypeArguments* instance_type_arguments =
             static_cast<RawTypeArguments*>(null_value);
         RawObject* instance_cid_or_function;
+
+        RawTypeArguments* parent_function_type_arguments;
+        RawTypeArguments* delayed_function_type_arguments;
         if (cid == kClosureCid) {
           RawClosure* closure = static_cast<RawClosure*>(instance);
-          if (closure->ptr()->function_type_arguments_ !=
-              TypeArguments::null()) {
-            // Cache cannot be used for generic closures.
-            goto AssertAssignableCallRuntime;
-          }
           instance_type_arguments =
               closure->ptr()->instantiator_type_arguments_;
+          parent_function_type_arguments =
+              closure->ptr()->function_type_arguments_;
+          delayed_function_type_arguments =
+              closure->ptr()->delayed_type_arguments_;
           instance_cid_or_function = closure->ptr()->function_;
         } else {
           instance_cid_or_function = Smi::New(cid);
@@ -3027,6 +3029,10 @@
                 instance->ptr())[instance_class->ptr()
                                      ->type_arguments_field_offset_in_words_];
           }
+          parent_function_type_arguments =
+              static_cast<RawTypeArguments*>(null_value);
+          delayed_function_type_arguments =
+              static_cast<RawTypeArguments*>(null_value);
         }
 
         for (RawObject** entries = cache->ptr()->cache_->ptr()->data();
@@ -3039,7 +3045,13 @@
               (entries[SubtypeTestCache::kInstantiatorTypeArguments] ==
                instantiator_type_arguments) &&
               (entries[SubtypeTestCache::kFunctionTypeArguments] ==
-               function_type_arguments)) {
+               function_type_arguments) &&
+              (entries
+                   [SubtypeTestCache::kInstanceParentFunctionTypeArguments] ==
+               parent_function_type_arguments) &&
+              (entries
+                   [SubtypeTestCache::kInstanceDelayedFunctionTypeArguments] ==
+               delayed_function_type_arguments)) {
             if (true_value == entries[SubtypeTestCache::kTestResult]) {
               goto AssertAssignableOk;
             } else {
diff --git a/runtime/vm/stub_code.h b/runtime/vm/stub_code.h
index 1607fde..bde424b 100644
--- a/runtime/vm/stub_code.h
+++ b/runtime/vm/stub_code.h
@@ -70,6 +70,7 @@
   V(Subtype1TestCache)                                                         \
   V(Subtype2TestCache)                                                         \
   V(Subtype4TestCache)                                                         \
+  V(Subtype6TestCache)                                                         \
   V(DefaultTypeTest)                                                           \
   V(TopTypeTypeTest)                                                           \
   V(TypeRefTypeTest)                                                           \
diff --git a/runtime/vm/stub_code_arm.cc b/runtime/vm/stub_code_arm.cc
index 0a2a352..6158b18 100644
--- a/runtime/vm/stub_code_arm.cc
+++ b/runtime/vm/stub_code_arm.cc
@@ -1848,124 +1848,184 @@
 // Used to check class and type arguments. Arguments passed in registers:
 // LR: return address.
 // R0: instance (must be preserved).
-// R2: instantiator type arguments (only if n == 4, can be raw_null).
-// R1: function type arguments (only if n == 4, can be raw_null).
+// R2: instantiator type arguments (only if n >= 4, can be raw_null).
+// R1: function type arguments (only if n >= 4, can be raw_null).
 // R3: SubtypeTestCache.
 //
 // Preserves R0/R2
 //
 // Result in R1: null -> not found, otherwise result (true or false).
 static void GenerateSubtypeNTestCacheStub(Assembler* assembler, int n) {
-  ASSERT((n == 1) || (n == 2) || (n == 4));
-  if (n > 1) {
-    __ LoadClass(R8, R0, R4);
-    // Compute instance type arguments into R4.
-    Label has_no_type_arguments;
-    __ LoadObject(R4, Object::null_object());
-    __ ldr(R9, FieldAddress(
-                   R8, Class::type_arguments_field_offset_in_words_offset()));
-    __ CompareImmediate(R9, Class::kNoTypeArguments);
-    __ b(&has_no_type_arguments, EQ);
-    __ add(R9, R0, Operand(R9, LSL, 2));
-    __ ldr(R4, FieldAddress(R9, 0));
-    __ Bind(&has_no_type_arguments);
-  }
-  __ LoadClassId(R8, R0);
-  // R0: instance.
-  // R2: instantiator type arguments (only if n == 4, can be raw_null).
-  // R1: function type arguments (only if n == 4, can be raw_null).
-  // R3: SubtypeTestCache.
-  // R8: instance class id.
-  // R4: instance type arguments (null if none), used only if n > 1.
-  __ ldr(R3, FieldAddress(R3, SubtypeTestCache::cache_offset()));
-  __ AddImmediate(R3, Array::data_offset() - kHeapObjectTag);
+  ASSERT(n == 1 || n == 2 || n == 4 || n == 6);
 
-  Label loop, found, not_found, next_iteration;
-  // R3: entry start.
-  // R8: instance class id.
-  // R4: instance type arguments (still null if closure).
-  __ SmiTag(R8);
-  __ CompareImmediate(R8, Smi::RawValue(kClosureCid));
-  __ b(&loop, NE);
-  __ ldr(R4, FieldAddress(R0, Closure::function_type_arguments_offset()));
-  __ CompareObject(R4, Object::null_object());
-  __ b(&not_found, NE);  // Cache cannot be used for generic closures.
-  __ ldr(R4, FieldAddress(R0, Closure::instantiator_type_arguments_offset()));
-  __ ldr(R8, FieldAddress(R0, Closure::function_offset()));
-  // R8: instance class id as Smi or function.
+  const Register kCacheReg = R3;
+  const Register kInstanceReg = R0;
+  const Register kInstantiatorTypeArgumentsReg = R2;
+  const Register kFunctionTypeArgumentsReg = R1;
+
+  const Register kInstanceCidOrFunction = R8;
+  const Register kInstanceInstantiatorTypeArgumentsReg = R4;
+  const Register kInstanceParentFunctionTypeArgumentsReg = CODE_REG;
+  const Register kInstanceDelayedFunctionTypeArgumentsReg = PP;
+
+  const Register kNullReg = NOTFP;
+
+  __ LoadObject(kNullReg, Object::null_object());
+
+  // Free up these 2 registers to be used for 6-value test.
+  if (n >= 6) {
+    __ PushList(1 << kInstanceParentFunctionTypeArgumentsReg |
+                1 << kInstanceDelayedFunctionTypeArgumentsReg);
+  }
+
+  // Loop initialization (moved up here to avoid having all dependent loads
+  // after each other).
+  __ ldr(kCacheReg, FieldAddress(kCacheReg, SubtypeTestCache::cache_offset()));
+  __ AddImmediate(kCacheReg, Array::data_offset() - kHeapObjectTag);
+
+  Label loop, not_closure;
+  __ LoadClassId(kInstanceCidOrFunction, kInstanceReg);
+  __ CompareImmediate(kInstanceCidOrFunction, kClosureCid);
+  __ b(&not_closure, NE);
+
+  // Closure handling.
+  {
+    __ ldr(kInstanceCidOrFunction,
+           FieldAddress(kInstanceReg, Closure::function_offset()));
+    if (n >= 2) {
+      __ ldr(kInstanceInstantiatorTypeArgumentsReg,
+             FieldAddress(kInstanceReg,
+                          Closure::instantiator_type_arguments_offset()));
+      if (n >= 6) {
+        ASSERT(n == 6);
+        __ ldr(kInstanceParentFunctionTypeArgumentsReg,
+               FieldAddress(kInstanceReg,
+                            Closure::function_type_arguments_offset()));
+        __ ldr(kInstanceDelayedFunctionTypeArgumentsReg,
+               FieldAddress(kInstanceReg,
+                            Closure::delayed_type_arguments_offset()));
+      }
+    }
+    __ b(&loop);
+  }
+
+  // Non-Closure handling.
+  {
+    __ Bind(&not_closure);
+    if (n >= 2) {
+      Label has_no_type_arguments;
+      __ LoadClassById(R9, kInstanceCidOrFunction);
+      __ mov(kInstanceInstantiatorTypeArgumentsReg, Operand(kNullReg));
+      __ ldr(R9, FieldAddress(
+                     R9, Class::type_arguments_field_offset_in_words_offset()));
+      __ CompareImmediate(R9, Class::kNoTypeArguments);
+      __ b(&has_no_type_arguments, EQ);
+      __ add(R9, kInstanceReg, Operand(R9, LSL, 2));
+      __ ldr(kInstanceInstantiatorTypeArgumentsReg, FieldAddress(R9, 0));
+      __ Bind(&has_no_type_arguments);
+
+      if (n >= 6) {
+        __ mov(kInstanceParentFunctionTypeArgumentsReg, Operand(kNullReg));
+        __ mov(kInstanceDelayedFunctionTypeArgumentsReg, Operand(kNullReg));
+      }
+    }
+    __ SmiTag(kInstanceCidOrFunction);
+  }
+
+  Label found, not_found, next_iteration;
+
+  // Loop header.
   __ Bind(&loop);
-  __ ldr(R9,
-         Address(R3, kWordSize * SubtypeTestCache::kInstanceClassIdOrFunction));
-  __ CompareObject(R9, Object::null_object());
+  __ ldr(R9, Address(kCacheReg,
+                     kWordSize * SubtypeTestCache::kInstanceClassIdOrFunction));
+  __ cmp(R9, Operand(kNullReg));
   __ b(&not_found, EQ);
-  __ cmp(R9, Operand(R8));
+  __ cmp(R9, Operand(kInstanceCidOrFunction));
   if (n == 1) {
     __ b(&found, EQ);
   } else {
     __ b(&next_iteration, NE);
-    __ ldr(R9,
-           Address(R3, kWordSize * SubtypeTestCache::kInstanceTypeArguments));
-    __ cmp(R9, Operand(R4));
+    __ ldr(R9, Address(kCacheReg,
+                       kWordSize * SubtypeTestCache::kInstanceTypeArguments));
+    __ cmp(R9, Operand(kInstanceInstantiatorTypeArgumentsReg));
     if (n == 2) {
       __ b(&found, EQ);
     } else {
       __ b(&next_iteration, NE);
-      __ ldr(R9, Address(R3, kWordSize *
-                                 SubtypeTestCache::kInstantiatorTypeArguments));
-      __ cmp(R9, Operand(R2));
-      __ b(&next_iteration, NE);
       __ ldr(R9,
-             Address(R3, kWordSize * SubtypeTestCache::kFunctionTypeArguments));
-      __ cmp(R9, Operand(R1));
-      __ b(&found, EQ);
+             Address(kCacheReg,
+                     kWordSize * SubtypeTestCache::kInstantiatorTypeArguments));
+      __ cmp(R9, Operand(kInstantiatorTypeArgumentsReg));
+      __ b(&next_iteration, NE);
+      __ ldr(R9, Address(kCacheReg,
+                         kWordSize * SubtypeTestCache::kFunctionTypeArguments));
+      __ cmp(R9, Operand(kFunctionTypeArgumentsReg));
+      if (n == 4) {
+        __ b(&found, EQ);
+      } else {
+        ASSERT(n == 6);
+        __ b(&next_iteration, NE);
+
+        __ ldr(R9,
+               Address(
+                   kCacheReg,
+                   kWordSize *
+                       SubtypeTestCache::kInstanceParentFunctionTypeArguments));
+        __ cmp(R9, Operand(kInstanceParentFunctionTypeArgumentsReg));
+        __ b(&next_iteration, NE);
+
+        __ ldr(
+            R9,
+            Address(
+                kCacheReg,
+                kWordSize *
+                    SubtypeTestCache::kInstanceDelayedFunctionTypeArguments));
+        __ cmp(R9, Operand(kInstanceDelayedFunctionTypeArgumentsReg));
+        __ b(&found, EQ);
+      }
     }
   }
   __ Bind(&next_iteration);
-  __ AddImmediate(R3, kWordSize * SubtypeTestCache::kTestEntryLength);
+  __ AddImmediate(kCacheReg, kWordSize * SubtypeTestCache::kTestEntryLength);
   __ b(&loop);
-  // Fall through to not found.
-  __ Bind(&not_found);
-  __ LoadObject(R1, Object::null_object());
-  __ Ret();
 
   __ Bind(&found);
-  __ ldr(R1, Address(R3, kWordSize * SubtypeTestCache::kTestResult));
+  __ ldr(R1, Address(kCacheReg, kWordSize * SubtypeTestCache::kTestResult));
+  if (n >= 6) {
+    __ PopList(1 << kInstanceParentFunctionTypeArgumentsReg |
+               1 << kInstanceDelayedFunctionTypeArgumentsReg);
+  }
+  __ Ret();
+
+  __ Bind(&not_found);
+  __ mov(R1, Operand(kNullReg));
+  if (n >= 6) {
+    __ PopList(1 << kInstanceParentFunctionTypeArgumentsReg |
+               1 << kInstanceDelayedFunctionTypeArgumentsReg);
+  }
   __ Ret();
 }
 
-// Used to check class and type arguments. Arguments passed in registers:
-// LR: return address.
-// R0: instance (must be preserved).
-// R2: unused.
-// R1: unused.
-// R3: SubtypeTestCache.
-// Result in R1: null -> not found, otherwise result (true or false).
+// See comment on [GenerateSubtypeNTestCacheStub].
 void StubCode::GenerateSubtype1TestCacheStub(Assembler* assembler) {
   GenerateSubtypeNTestCacheStub(assembler, 1);
 }
 
-// Used to check class and type arguments. Arguments passed in registers:
-// LR: return address.
-// R0: instance (must be preserved).
-// R2: unused.
-// R1: unused.
-// R3: SubtypeTestCache.
-// Result in R1: null -> not found, otherwise result (true or false).
+// See comment on [GenerateSubtypeNTestCacheStub].
 void StubCode::GenerateSubtype2TestCacheStub(Assembler* assembler) {
   GenerateSubtypeNTestCacheStub(assembler, 2);
 }
 
-// Used to check class and type arguments. Arguments passed in registers:
-// LR: return address.
-// R0: instance (must be preserved).
-// R2: instantiator type arguments (can be raw_null).
-// R1: function type arguments (can be raw_null).
-// R3: SubtypeTestCache.
-// Result in R1: null -> not found, otherwise result (true or false).
+// See comment on [GenerateSubtypeNTestCacheStub].
 void StubCode::GenerateSubtype4TestCacheStub(Assembler* assembler) {
   GenerateSubtypeNTestCacheStub(assembler, 4);
 }
 
+// See comment on [GenerateSubtypeNTestCacheStub].
+void StubCode::GenerateSubtype6TestCacheStub(Assembler* assembler) {
+  GenerateSubtypeNTestCacheStub(assembler, 6);
+}
+
 // Used to test whether a given value is of a given type (different variants,
 // all have the same calling convention).
 //
diff --git a/runtime/vm/stub_code_arm64.cc b/runtime/vm/stub_code_arm64.cc
index d8d8418..41c1e28 100644
--- a/runtime/vm/stub_code_arm64.cc
+++ b/runtime/vm/stub_code_arm64.cc
@@ -2102,116 +2102,165 @@
 //
 // Result in R1: null -> not found, otherwise result (true or false).
 static void GenerateSubtypeNTestCacheStub(Assembler* assembler, int n) {
-  ASSERT((n == 1) || (n == 2) || (n == 4));
-  if (n > 1) {
-    __ LoadClass(R6, R0);
-    // Compute instance type arguments into R4.
-    Label has_no_type_arguments;
-    __ LoadObject(R4, Object::null_object());
-    __ LoadFieldFromOffset(
-        R5, R6, Class::type_arguments_field_offset_in_words_offset(), kWord);
-    __ CompareImmediate(R5, Class::kNoTypeArguments);
-    __ b(&has_no_type_arguments, EQ);
-    __ add(R5, R0, Operand(R5, LSL, 3));
-    __ LoadFieldFromOffset(R4, R5, 0);
-    __ Bind(&has_no_type_arguments);
-  }
-  __ LoadClassId(R6, R0);
-  // R0: instance.
-  // R1: instantiator type arguments (only if n == 4, can be raw_null).
-  // R2: function type arguments (only if n == 4, can be raw_null).
-  // R3: SubtypeTestCache.
-  // R6: instance class id.
-  // R4: instance type arguments (null if none), used only if n > 1.
-  __ LoadFieldFromOffset(R3, R3, SubtypeTestCache::cache_offset());
-  __ AddImmediate(R3, Array::data_offset() - kHeapObjectTag);
+  ASSERT(n == 1 || n == 2 || n == 4 || n == 6);
 
-  Label loop, found, not_found, next_iteration;
-  // R3: entry start.
-  // R6: instance class id.
-  // R4: instance type arguments (still null if closure).
-  __ SmiTag(R6);
-  __ CompareImmediate(R6, Smi::RawValue(kClosureCid));
-  __ b(&loop, NE);
-  __ LoadFieldFromOffset(R4, R0, Closure::function_type_arguments_offset());
-  __ CompareObject(R4, Object::null_object());
-  __ b(&not_found, NE);  // Cache cannot be used for generic closures.
-  __ LoadFieldFromOffset(R4, R0, Closure::instantiator_type_arguments_offset());
-  __ LoadFieldFromOffset(R6, R0, Closure::function_offset());
-  // R6: instance class id as Smi or function.
+  const Register kCacheReg = R3;
+  const Register kInstanceReg = R0;
+  const Register kInstantiatorTypeArgumentsReg = R1;
+  const Register kFunctionTypeArgumentsReg = R2;
+
+  const Register kInstanceCidOrFunction = R6;
+  const Register kInstanceInstantiatorTypeArgumentsReg = R4;
+  const Register kInstanceParentFunctionTypeArgumentsReg = R9;
+  const Register kInstanceDelayedFunctionTypeArgumentsReg = R10;
+
+  const Register kNullReg = R7;
+
+  __ LoadObject(kNullReg, Object::null_object());
+
+  // Loop initialization (moved up here to avoid having all dependent loads
+  // after each other).
+  __ ldr(kCacheReg, FieldAddress(kCacheReg, SubtypeTestCache::cache_offset()));
+  __ AddImmediate(kCacheReg, Array::data_offset() - kHeapObjectTag);
+
+  Label loop, not_closure;
+  __ LoadClassId(kInstanceCidOrFunction, kInstanceReg);
+  __ CompareImmediate(kInstanceCidOrFunction, kClosureCid);
+  __ b(&not_closure, NE);
+
+  // Closure handling.
+  {
+    __ ldr(kInstanceCidOrFunction,
+           FieldAddress(kInstanceReg, Closure::function_offset()));
+    if (n >= 2) {
+      __ ldr(kInstanceInstantiatorTypeArgumentsReg,
+             FieldAddress(kInstanceReg,
+                          Closure::instantiator_type_arguments_offset()));
+      if (n >= 6) {
+        ASSERT(n == 6);
+        __ ldr(kInstanceParentFunctionTypeArgumentsReg,
+               FieldAddress(kInstanceReg,
+                            Closure::function_type_arguments_offset()));
+        __ ldr(kInstanceDelayedFunctionTypeArgumentsReg,
+               FieldAddress(kInstanceReg,
+                            Closure::delayed_type_arguments_offset()));
+      }
+    }
+    __ b(&loop);
+  }
+
+  // Non-Closure handling.
+  {
+    __ Bind(&not_closure);
+    if (n == 1) {
+      __ SmiTag(kInstanceCidOrFunction);
+    } else {
+      ASSERT(n >= 2);
+      Label has_no_type_arguments;
+      // [LoadClassById] also tags [kInstanceCidOrFunction] as a side-effect.
+      __ LoadClassById(R5, kInstanceCidOrFunction);
+      __ mov(kInstanceInstantiatorTypeArgumentsReg, kNullReg);
+      __ LoadFieldFromOffset(
+          R5, R5, Class::type_arguments_field_offset_in_words_offset(), kWord);
+      __ CompareImmediate(R5, Class::kNoTypeArguments);
+      __ b(&has_no_type_arguments, EQ);
+      __ add(R5, kInstanceReg, Operand(R5, LSL, 3));
+      __ ldr(kInstanceInstantiatorTypeArgumentsReg, FieldAddress(R5, 0));
+      __ Bind(&has_no_type_arguments);
+
+      if (n >= 6) {
+        __ mov(kInstanceParentFunctionTypeArgumentsReg, kNullReg);
+        __ mov(kInstanceDelayedFunctionTypeArgumentsReg, kNullReg);
+      }
+    }
+  }
+
+  Label found, not_found, next_iteration;
+
+  // Loop header
   __ Bind(&loop);
-  __ LoadFromOffset(R5, R3,
-                    kWordSize * SubtypeTestCache::kInstanceClassIdOrFunction);
-  __ CompareObject(R5, Object::null_object());
+  __ ldr(R5, Address(kCacheReg,
+                     kWordSize * SubtypeTestCache::kInstanceClassIdOrFunction));
+  __ cmp(R5, Operand(kNullReg));
   __ b(&not_found, EQ);
-  __ CompareRegisters(R5, R6);
+  __ cmp(R5, Operand(kInstanceCidOrFunction));
   if (n == 1) {
     __ b(&found, EQ);
   } else {
     __ b(&next_iteration, NE);
-    __ LoadFromOffset(R5, R3,
-                      kWordSize * SubtypeTestCache::kInstanceTypeArguments);
-    __ CompareRegisters(R5, R4);
+    __ ldr(R5, Address(kCacheReg,
+                       kWordSize * SubtypeTestCache::kInstanceTypeArguments));
+    __ cmp(R5, Operand(kInstanceInstantiatorTypeArgumentsReg));
     if (n == 2) {
       __ b(&found, EQ);
     } else {
       __ b(&next_iteration, NE);
-      __ LoadFromOffset(
-          R5, R3, kWordSize * SubtypeTestCache::kInstantiatorTypeArguments);
-      __ CompareRegisters(R5, R1);
+      __ ldr(R5,
+             Address(kCacheReg,
+                     kWordSize * SubtypeTestCache::kInstantiatorTypeArguments));
+      __ cmp(R5, Operand(kInstantiatorTypeArgumentsReg));
       __ b(&next_iteration, NE);
-      __ LoadFromOffset(R5, R3,
-                        kWordSize * SubtypeTestCache::kFunctionTypeArguments);
-      __ CompareRegisters(R5, R2);
-      __ b(&found, EQ);
+      __ ldr(R5, Address(kCacheReg,
+                         kWordSize * SubtypeTestCache::kFunctionTypeArguments));
+      __ cmp(R5, Operand(kFunctionTypeArgumentsReg));
+      if (n == 4) {
+        __ b(&found, EQ);
+      } else {
+        ASSERT(n == 6);
+        __ b(&next_iteration, NE);
+
+        __ ldr(R5,
+               Address(
+                   kCacheReg,
+                   kWordSize *
+                       SubtypeTestCache::kInstanceParentFunctionTypeArguments));
+        __ cmp(R5, Operand(kInstanceParentFunctionTypeArgumentsReg));
+        __ b(&next_iteration, NE);
+
+        __ ldr(
+            R5,
+            Address(
+                kCacheReg,
+                kWordSize *
+                    SubtypeTestCache::kInstanceDelayedFunctionTypeArguments));
+        __ cmp(R5, Operand(kInstanceDelayedFunctionTypeArgumentsReg));
+        __ b(&found, EQ);
+      }
     }
   }
   __ Bind(&next_iteration);
-  __ AddImmediate(R3, kWordSize * SubtypeTestCache::kTestEntryLength);
+  __ AddImmediate(kCacheReg, kWordSize * SubtypeTestCache::kTestEntryLength);
   __ b(&loop);
-  // Fall through to not found.
-  __ Bind(&not_found);
-  __ LoadObject(R1, Object::null_object());
-  __ ret();
 
   __ Bind(&found);
-  __ LoadFromOffset(R1, R3, kWordSize * SubtypeTestCache::kTestResult);
+  __ ldr(R1, Address(kCacheReg, kWordSize * SubtypeTestCache::kTestResult));
+  __ ret();
+
+  __ Bind(&not_found);
+  __ mov(R1, kNullReg);
   __ ret();
 }
 
-// Used to check class and type arguments. Arguments passed on stack:
-// LR: return address.
-// R0: instance (must be preserved).
-// R1: unused.
-// R2: unused.
-// R3: SubtypeTestCache.
-// Result in R1: null -> not found, otherwise result (true or false).
+// See comment on [GenerateSubtypeNTestCacheStub].
 void StubCode::GenerateSubtype1TestCacheStub(Assembler* assembler) {
   GenerateSubtypeNTestCacheStub(assembler, 1);
 }
 
-// Used to check class and type arguments. Arguments passed in registers:
-// LR: return address.
-// R0: instance (must be preserved).
-// R1: unused.
-// R2: unused.
-// R3: SubtypeTestCache.
-// Result in R1: null -> not found, otherwise result (true or false).
+// See comment on [GenerateSubtypeNTestCacheStub].
 void StubCode::GenerateSubtype2TestCacheStub(Assembler* assembler) {
   GenerateSubtypeNTestCacheStub(assembler, 2);
 }
 
-// Used to check class and type arguments. Arguments passed on stack:
-// LR: return address.
-// R0: instance (must be preserved).
-// R1: instantiator type arguments (can be raw_null).
-// R2: function type arguments (can be raw_null).
-// R3: SubtypeTestCache.
-// Result in R1: null -> not found, otherwise result (true or false).
+// See comment on [GenerateSubtypeNTestCacheStub].
 void StubCode::GenerateSubtype4TestCacheStub(Assembler* assembler) {
   GenerateSubtypeNTestCacheStub(assembler, 4);
 }
 
+// See comment on [GenerateSubtypeNTestCacheStub].
+void StubCode::GenerateSubtype6TestCacheStub(Assembler* assembler) {
+  GenerateSubtypeNTestCacheStub(assembler, 6);
+}
+
 // Used to test whether a given value is of a given type (different variants,
 // all have the same calling convention).
 //
diff --git a/runtime/vm/stub_code_ia32.cc b/runtime/vm/stub_code_ia32.cc
index 144a6bf..041d1c9 100644
--- a/runtime/vm/stub_code_ia32.cc
+++ b/runtime/vm/stub_code_ia32.cc
@@ -1698,62 +1698,96 @@
 // TOS + 4: SubtypeTestCache.
 // Result in ECX: null -> not found, otherwise result (true or false).
 static void GenerateSubtypeNTestCacheStub(Assembler* assembler, int n) {
-  ASSERT((n == 1) || (n == 2) || (n == 4));
-  const intptr_t kFunctionTypeArgumentsInBytes = 1 * kWordSize;
-  const intptr_t kInstantiatorTypeArgumentsInBytes = 2 * kWordSize;
-  const intptr_t kInstanceOffsetInBytes = 3 * kWordSize;
-  const intptr_t kCacheOffsetInBytes = 4 * kWordSize;
+  ASSERT(n == 1 || n == 2 || n == 4 || n == 6);
+
+  static intptr_t kFunctionTypeArgumentsInBytes = 1 * kWordSize;
+  static intptr_t kInstantiatorTypeArgumentsInBytes = 2 * kWordSize;
+  static intptr_t kInstanceOffsetInBytes = 3 * kWordSize;
+  static intptr_t kCacheOffsetInBytes = 4 * kWordSize;
+
+  const Register kInstanceReg = EAX;
+
+  const Register kInstanceCidOrFunction = ECX;
+  const Register kInstanceInstantiatorTypeArgumentsReg = EBX;
+
   const Immediate& raw_null =
       Immediate(reinterpret_cast<intptr_t>(Object::null()));
-  __ movl(EAX, Address(ESP, kInstanceOffsetInBytes));
-  if (n > 1) {
-    __ LoadClass(ECX, EAX, EBX);
-    // Compute instance type arguments into EBX.
-    Label has_no_type_arguments;
-    __ movl(EBX, raw_null);
-    __ movl(EDI,
-            FieldAddress(ECX,
-                         Class::type_arguments_field_offset_in_words_offset()));
-    __ cmpl(EDI, Immediate(Class::kNoTypeArguments));
-    __ j(EQUAL, &has_no_type_arguments, Assembler::kNearJump);
-    __ movl(EBX, FieldAddress(EAX, EDI, TIMES_4, 0));
-    __ Bind(&has_no_type_arguments);
-  }
-  __ LoadClassId(ECX, EAX);
-  // EAX: instance, ECX: instance class id.
-  // EBX: instance type arguments (null if none), used only if n > 1.
+
+  __ movl(kInstanceReg, Address(ESP, kInstanceOffsetInBytes));
+
+  // Loop initialization (moved up here to avoid having all dependent loads
+  // after each other)
   __ movl(EDX, Address(ESP, kCacheOffsetInBytes));
-  // EDX: SubtypeTestCache.
   __ movl(EDX, FieldAddress(EDX, SubtypeTestCache::cache_offset()));
   __ addl(EDX, Immediate(Array::data_offset() - kHeapObjectTag));
 
-  Label loop, found, not_found, next_iteration;
-  // EDX: Entry start.
-  // ECX: instance class id.
-  // EBX: instance type arguments (still null if closure).
-  __ SmiTag(ECX);
-  __ cmpl(ECX, Immediate(Smi::RawValue(kClosureCid)));
-  __ j(NOT_EQUAL, &loop, Assembler::kNearJump);
-  __ movl(EBX, FieldAddress(EAX, Closure::function_type_arguments_offset()));
-  __ cmpl(EBX, raw_null);  // Cache cannot be used for generic closures.
-  __ j(NOT_EQUAL, &not_found, Assembler::kNearJump);
-  __ movl(EBX,
-          FieldAddress(EAX, Closure::instantiator_type_arguments_offset()));
-  __ movl(ECX, FieldAddress(EAX, Closure::function_offset()));
-  // ECX: instance class id as Smi or function.
+  Label loop, not_closure;
+
+  __ LoadClassId(kInstanceCidOrFunction, kInstanceReg);
+  __ cmpl(kInstanceCidOrFunction, Immediate(kClosureCid));
+  __ j(NOT_EQUAL, &not_closure, Assembler::kNearJump);
+
+  // Closure handling.
+  {
+    __ movl(kInstanceCidOrFunction,
+            FieldAddress(kInstanceReg, Closure::function_offset()));
+    if (n >= 2) {
+      __ movl(kInstanceInstantiatorTypeArgumentsReg,
+              FieldAddress(kInstanceReg,
+                           Closure::instantiator_type_arguments_offset()));
+      if (n >= 6) {
+        __ pushl(FieldAddress(kInstanceReg,
+                              Closure::delayed_type_arguments_offset()));
+        __ pushl(FieldAddress(kInstanceReg,
+                              Closure::function_type_arguments_offset()));
+      }
+    }
+    __ jmp(&loop, Assembler::kNearJump);
+  }
+
+  // Non-Closure handling.
+  {
+    __ Bind(&not_closure);
+    if (n >= 2) {
+      Label has_no_type_arguments;
+      __ LoadClassById(EDI, kInstanceCidOrFunction);
+      __ movl(kInstanceInstantiatorTypeArgumentsReg, raw_null);
+      __ movl(EDI,
+              FieldAddress(
+                  EDI, Class::type_arguments_field_offset_in_words_offset()));
+      __ cmpl(EDI, Immediate(Class::kNoTypeArguments));
+      __ j(EQUAL, &has_no_type_arguments, Assembler::kNearJump);
+      __ movl(kInstanceInstantiatorTypeArgumentsReg,
+              FieldAddress(kInstanceReg, EDI, TIMES_4, 0));
+      __ Bind(&has_no_type_arguments);
+
+      if (n >= 6) {
+        __ pushl(raw_null);  // delayed.
+        __ pushl(raw_null);  // function.
+      }
+    }
+    __ SmiTag(kInstanceCidOrFunction);
+  }
+
+  const intptr_t kInstanceParentFunctionTypeArgumentsFromSp = 0;
+  const intptr_t kInstanceDelayedFunctionTypeArgumentsFromSp = kWordSize;
+  const intptr_t args_offset = n >= 6 ? 2 * kWordSize : 0;
+
+  Label found, not_found, next_iteration;
+
+  // Loop header.
   __ Bind(&loop);
   __ movl(EDI, Address(EDX, kWordSize *
                                 SubtypeTestCache::kInstanceClassIdOrFunction));
   __ cmpl(EDI, raw_null);
   __ j(EQUAL, &not_found, Assembler::kNearJump);
-  __ cmpl(EDI, ECX);
+  __ cmpl(EDI, kInstanceCidOrFunction);
   if (n == 1) {
     __ j(EQUAL, &found, Assembler::kNearJump);
   } else {
     __ j(NOT_EQUAL, &next_iteration, Assembler::kNearJump);
-    __ movl(EDI,
+    __ cmpl(kInstanceInstantiatorTypeArgumentsReg,
             Address(EDX, kWordSize * SubtypeTestCache::kInstanceTypeArguments));
-    __ cmpl(EDI, EBX);
     if (n == 2) {
       __ j(EQUAL, &found, Assembler::kNearJump);
     } else {
@@ -1761,60 +1795,76 @@
       __ movl(EDI,
               Address(EDX, kWordSize *
                                SubtypeTestCache::kInstantiatorTypeArguments));
-      __ cmpl(EDI, Address(ESP, kInstantiatorTypeArgumentsInBytes));
+      __ cmpl(EDI,
+              Address(ESP, args_offset + kInstantiatorTypeArgumentsInBytes));
       __ j(NOT_EQUAL, &next_iteration, Assembler::kNearJump);
       __ movl(EDI, Address(EDX, kWordSize *
                                     SubtypeTestCache::kFunctionTypeArguments));
-      __ cmpl(EDI, Address(ESP, kFunctionTypeArgumentsInBytes));
-      __ j(EQUAL, &found, Assembler::kNearJump);
+      __ cmpl(EDI, Address(ESP, args_offset + kFunctionTypeArgumentsInBytes));
+      if (n == 4) {
+        __ j(EQUAL, &found, Assembler::kNearJump);
+      } else {
+        ASSERT(n == 6);
+        __ j(NOT_EQUAL, &next_iteration, Assembler::kNearJump);
+
+        __ movl(
+            EDI,
+            Address(
+                EDX,
+                kWordSize *
+                    SubtypeTestCache::kInstanceParentFunctionTypeArguments));
+        __ cmpl(EDI, Address(ESP, kInstanceParentFunctionTypeArgumentsFromSp));
+        __ j(NOT_EQUAL, &next_iteration, Assembler::kNearJump);
+        __ movl(
+            EDI,
+            Address(
+                EDX,
+                kWordSize *
+                    SubtypeTestCache::kInstanceDelayedFunctionTypeArguments));
+        __ cmpl(EDI, Address(ESP, kInstanceDelayedFunctionTypeArgumentsFromSp));
+        __ j(EQUAL, &found, Assembler::kNearJump);
+      }
     }
   }
   __ Bind(&next_iteration);
   __ addl(EDX, Immediate(kWordSize * SubtypeTestCache::kTestEntryLength));
   __ jmp(&loop, Assembler::kNearJump);
-  // Fall through to not found.
-  __ Bind(&not_found);
-  __ movl(ECX, raw_null);
-  __ ret();
 
   __ Bind(&found);
   __ movl(ECX, Address(EDX, kWordSize * SubtypeTestCache::kTestResult));
+  if (n == 6) {
+    __ Drop(2);
+  }
+  __ ret();
+
+  __ Bind(&not_found);
+  __ movl(ECX, raw_null);
+  if (n == 6) {
+    __ Drop(2);
+  }
   __ ret();
 }
 
-// Used to check class and type arguments. Arguments passed on stack:
-// TOS + 0: return address.
-// TOS + 1: raw_null.
-// TOS + 2: raw_null.
-// TOS + 3: instance.
-// TOS + 4: SubtypeTestCache.
-// Result in ECX: null -> not found, otherwise result (true or false).
+// See comment on [GenerateSubtypeNTestCacheStub].
 void StubCode::GenerateSubtype1TestCacheStub(Assembler* assembler) {
   GenerateSubtypeNTestCacheStub(assembler, 1);
 }
 
-// Used to check class and type arguments. Arguments passed on stack:
-// TOS + 0: return address.
-// TOS + 1: raw_null.
-// TOS + 2: raw_null.
-// TOS + 3: instance.
-// TOS + 4: SubtypeTestCache.
-// Result in ECX: null -> not found, otherwise result (true or false).
+// See comment on [GenerateSubtypeNTestCacheStub].
 void StubCode::GenerateSubtype2TestCacheStub(Assembler* assembler) {
   GenerateSubtypeNTestCacheStub(assembler, 2);
 }
 
-// Used to check class and type arguments. Arguments passed on stack:
-// TOS + 0: return address.
-// TOS + 1: function type arguments (can be raw_null).
-// TOS + 2: instantiator type arguments (can be raw_null).
-// TOS + 3: instance.
-// TOS + 4: SubtypeTestCache.
-// Result in ECX: null -> not found, otherwise result (true or false).
+// See comment on [GenerateSubtypeNTestCacheStub].
 void StubCode::GenerateSubtype4TestCacheStub(Assembler* assembler) {
   GenerateSubtypeNTestCacheStub(assembler, 4);
 }
 
+// See comment on [GenerateSubtypeNTestCacheStub].
+void StubCode::GenerateSubtype6TestCacheStub(Assembler* assembler) {
+  GenerateSubtypeNTestCacheStub(assembler, 6);
+}
+
 void StubCode::GenerateDefaultTypeTestStub(Assembler* assembler) {
   // Not implemented on ia32.
   __ Breakpoint();
diff --git a/runtime/vm/stub_code_x64.cc b/runtime/vm/stub_code_x64.cc
index b470d5f..bb6865a 100644
--- a/runtime/vm/stub_code_x64.cc
+++ b/runtime/vm/stub_code_x64.cc
@@ -2040,126 +2040,177 @@
 //
 // Result in R8: null -> not found, otherwise result (true or false).
 static void GenerateSubtypeNTestCacheStub(Assembler* assembler, int n) {
-  ASSERT((n == 1) || (n == 2) || (n == 4));
+  ASSERT(n == 1 || n == 2 || n == 4 || n == 6);
 
   const Register kCacheReg = R9;
   const Register kInstanceReg = RAX;
   const Register kInstantiatorTypeArgumentsReg = RDX;
   const Register kFunctionTypeArgumentsReg = RCX;
 
-  __ LoadObject(R8, Object::null_object());
-  if (n > 1) {
-    __ LoadClass(R10, kInstanceReg);
-    // Compute instance type arguments into R13.
-    Label has_no_type_arguments;
-    __ movq(R13, R8);
-    __ movl(RDI,
-            FieldAddress(R10,
-                         Class::type_arguments_field_offset_in_words_offset()));
-    __ cmpl(RDI, Immediate(Class::kNoTypeArguments));
-    __ j(EQUAL, &has_no_type_arguments, Assembler::kNearJump);
-    __ movq(R13, FieldAddress(kInstanceReg, RDI, TIMES_8, 0));
-    __ Bind(&has_no_type_arguments);
+  const Register kInstanceCidOrFunction = R10;
+  const Register kInstanceInstantiatorTypeArgumentsReg = R13;
+  const Register kInstanceParentFunctionTypeArgumentsReg = PP;
+  const Register kInstanceDelayedFunctionTypeArgumentsReg = CODE_REG;
+
+  const Register kNullReg = R8;
+
+  __ LoadObject(kNullReg, Object::null_object());
+
+  // Free up these 2 registers to be used for 6-value test.
+  if (n >= 6) {
+    __ pushq(kInstanceParentFunctionTypeArgumentsReg);
+    __ pushq(kInstanceDelayedFunctionTypeArgumentsReg);
   }
-  __ LoadClassId(R10, kInstanceReg);
-  // RAX: instance, R10: instance class id.
-  // R13: instance type arguments or null, used only if n > 1.
-  __ movq(RSI, kCacheReg);
-  // RSI: SubtypeTestCache.
-  __ movq(RSI, FieldAddress(RSI, SubtypeTestCache::cache_offset()));
+
+  // Loop initialization (moved up here to avoid having all dependent loads
+  // after each other).
+  __ movq(RSI, FieldAddress(kCacheReg, SubtypeTestCache::cache_offset()));
   __ addq(RSI, Immediate(Array::data_offset() - kHeapObjectTag));
-  // RSI: Entry start.
-  // R10: instance class id.
-  // R13: instance type arguments (still null if closure).
-  Label loop, found, not_found, next_iteration;
-  __ SmiTag(R10);
-  __ cmpq(R10, Immediate(Smi::RawValue(kClosureCid)));
-  __ j(NOT_EQUAL, &loop, Assembler::kNearJump);
-  __ movq(R13, FieldAddress(kInstanceReg,
-                            Closure::function_type_arguments_offset()));
-  __ cmpq(R13, R8);  // Cache cannot be used for generic closures.
-  __ j(NOT_EQUAL, &not_found, Assembler::kNearJump);
-  __ movq(R13, FieldAddress(kInstanceReg,
-                            Closure::instantiator_type_arguments_offset()));
-  __ movq(R10, FieldAddress(kInstanceReg, Closure::function_offset()));
-  // R10: instance class id as Smi or function.
+
+  Label loop, not_closure;
+  __ LoadClassId(R10, kInstanceReg);
+  __ cmpq(R10, Immediate(kClosureCid));
+  __ j(NOT_EQUAL, &not_closure, Assembler::kNearJump);
+
+  // Closure handling.
+  {
+    __ movq(kInstanceCidOrFunction,
+            FieldAddress(kInstanceReg, Closure::function_offset()));
+    if (n >= 2) {
+      __ movq(kInstanceInstantiatorTypeArgumentsReg,
+              FieldAddress(kInstanceReg,
+                           Closure::instantiator_type_arguments_offset()));
+      if (n >= 6) {
+        ASSERT(n == 6);
+        __ movq(kInstanceParentFunctionTypeArgumentsReg,
+                FieldAddress(kInstanceReg,
+                             Closure::function_type_arguments_offset()));
+        __ movq(kInstanceDelayedFunctionTypeArgumentsReg,
+                FieldAddress(kInstanceReg,
+                             Closure::delayed_type_arguments_offset()));
+      }
+    }
+    __ jmp(&loop, Assembler::kNearJump);
+  }
+
+  // Non-Closure handling.
+  {
+    __ Bind(&not_closure);
+    if (n == 1) {
+      __ SmiTag(kInstanceCidOrFunction);
+    } else {
+      ASSERT(n >= 2);
+      Label has_no_type_arguments;
+      // [LoadClassById] also tags [kInstanceCidOrFunction] as a side-effect.
+      __ LoadClassById(RDI, kInstanceCidOrFunction);
+      __ movq(kInstanceInstantiatorTypeArgumentsReg, kNullReg);
+      __ movl(RDI,
+              FieldAddress(
+                  RDI, Class::type_arguments_field_offset_in_words_offset()));
+      __ cmpl(RDI, Immediate(Class::kNoTypeArguments));
+      __ j(EQUAL, &has_no_type_arguments, Assembler::kNearJump);
+      __ movq(kInstanceInstantiatorTypeArgumentsReg,
+              FieldAddress(kInstanceReg, RDI, TIMES_8, 0));
+      __ Bind(&has_no_type_arguments);
+
+      if (n >= 6) {
+        __ movq(kInstanceParentFunctionTypeArgumentsReg, kNullReg);
+        __ movq(kInstanceDelayedFunctionTypeArgumentsReg, kNullReg);
+      }
+    }
+  }
+
+  Label found, not_found, next_iteration;
+
+  // Loop header.
   __ Bind(&loop);
   __ movq(RDI, Address(RSI, kWordSize *
                                 SubtypeTestCache::kInstanceClassIdOrFunction));
-  __ cmpq(RDI, R8);
+  __ cmpq(RDI, kNullReg);
   __ j(EQUAL, &not_found, Assembler::kNearJump);
-  __ cmpq(RDI, R10);
+  __ cmpq(RDI, kInstanceCidOrFunction);
   if (n == 1) {
     __ j(EQUAL, &found, Assembler::kNearJump);
   } else {
     __ j(NOT_EQUAL, &next_iteration, Assembler::kNearJump);
-    __ movq(RDI,
+    __ cmpq(kInstanceInstantiatorTypeArgumentsReg,
             Address(RSI, kWordSize * SubtypeTestCache::kInstanceTypeArguments));
-    __ cmpq(RDI, R13);
     if (n == 2) {
       __ j(EQUAL, &found, Assembler::kNearJump);
     } else {
       __ j(NOT_EQUAL, &next_iteration, Assembler::kNearJump);
-      __ movq(RDI,
+      __ cmpq(kInstantiatorTypeArgumentsReg,
               Address(RSI, kWordSize *
                                SubtypeTestCache::kInstantiatorTypeArguments));
-      __ cmpq(RDI, kInstantiatorTypeArgumentsReg);
       __ j(NOT_EQUAL, &next_iteration, Assembler::kNearJump);
-      __ movq(RDI, Address(RSI, kWordSize *
-                                    SubtypeTestCache::kFunctionTypeArguments));
-      __ cmpq(RDI, kFunctionTypeArgumentsReg);
-      __ j(EQUAL, &found, Assembler::kNearJump);
+      __ cmpq(
+          kFunctionTypeArgumentsReg,
+          Address(RSI, kWordSize * SubtypeTestCache::kFunctionTypeArguments));
+
+      if (n == 4) {
+        __ j(EQUAL, &found, Assembler::kNearJump);
+      } else {
+        ASSERT(n == 6);
+        __ j(NOT_EQUAL, &next_iteration, Assembler::kNearJump);
+
+        __ cmpq(
+            kInstanceParentFunctionTypeArgumentsReg,
+            Address(
+                RSI,
+                kWordSize *
+                    SubtypeTestCache::kInstanceParentFunctionTypeArguments));
+        __ j(NOT_EQUAL, &next_iteration, Assembler::kNearJump);
+        __ cmpq(
+            kInstanceDelayedFunctionTypeArgumentsReg,
+            Address(
+                RSI,
+                kWordSize *
+                    SubtypeTestCache::kInstanceDelayedFunctionTypeArguments));
+        __ j(EQUAL, &found, Assembler::kNearJump);
+      }
     }
   }
 
   __ Bind(&next_iteration);
   __ addq(RSI, Immediate(kWordSize * SubtypeTestCache::kTestEntryLength));
   __ jmp(&loop, Assembler::kNearJump);
-  // Fall through to not found.
-  __ Bind(&not_found);
-  __ ret();
 
   __ Bind(&found);
   __ movq(R8, Address(RSI, kWordSize * SubtypeTestCache::kTestResult));
+  if (n >= 6) {
+    __ popq(kInstanceDelayedFunctionTypeArgumentsReg);
+    __ popq(kInstanceParentFunctionTypeArgumentsReg);
+  }
+  __ ret();
+
+  __ Bind(&not_found);
+  if (n >= 6) {
+    __ popq(kInstanceDelayedFunctionTypeArgumentsReg);
+    __ popq(kInstanceParentFunctionTypeArgumentsReg);
+  }
   __ ret();
 }
 
-// Used to check class and type arguments. Arguments passed on stack:
-// TOS + 0: return address.
-// TOS + 1: raw_null.
-// TOS + 2: raw_null.
-// TOS + 3: instance.
-// TOS + 4: SubtypeTestCache.
-// Result in R8: null -> not found, otherwise result (true or false).
-// Preserves RCX/RDX.
+// See comment on [GenerateSubtypeNTestCacheStub].
 void StubCode::GenerateSubtype1TestCacheStub(Assembler* assembler) {
   GenerateSubtypeNTestCacheStub(assembler, 1);
 }
 
-// Used to check class and type arguments. Arguments passed on stack:
-// TOS + 0: return address.
-// TOS + 1: raw_null.
-// TOS + 2: raw_null.
-// TOS + 3: instance.
-// TOS + 4: SubtypeTestCache.
-// Result in R8: null -> not found, otherwise result (true or false).
-// Preserves RCX/RDX.
+// See comment on [GenerateSubtypeNTestCacheStub].
 void StubCode::GenerateSubtype2TestCacheStub(Assembler* assembler) {
   GenerateSubtypeNTestCacheStub(assembler, 2);
 }
 
-// Used to check class and type arguments. Arguments passed on stack:
-// TOS + 0: return address.
-// TOS + 1: function type arguments (can be raw_null).
-// TOS + 2: instantiator type arguments (can be raw_null).
-// TOS + 3: instance.
-// TOS + 4: SubtypeTestCache.
-// Result in R8: null -> not found, otherwise result (true or false).
-// Preserves RCX/RDX.
+// See comment on [GenerateSubtypeNTestCacheStub].
 void StubCode::GenerateSubtype4TestCacheStub(Assembler* assembler) {
   GenerateSubtypeNTestCacheStub(assembler, 4);
 }
 
+// See comment on [GenerateSubtypeNTestCacheStub].
+void StubCode::GenerateSubtype6TestCacheStub(Assembler* assembler) {
+  GenerateSubtypeNTestCacheStub(assembler, 6);
+}
+
 // Used to test whether a given value is of a given type (different variants,
 // all have the same calling convention).
 //
@@ -2333,18 +2384,24 @@
   const Register kTmp = RDI;
 
   // If this is not a [Type] object, we'll go to the runtime.
-  Label is_simple, is_instantiated, is_uninstantiated;
+  Label is_simple_case, is_complex_case;
   __ LoadClassId(kTmp, kDstTypeReg);
   __ cmpq(kTmp, Immediate(kTypeCid));
-  __ BranchIf(NOT_EQUAL, &is_uninstantiated);
+  __ BranchIf(NOT_EQUAL, &is_complex_case);
 
   // Check whether this [Type] is instantiated/uninstantiated.
   __ cmpb(FieldAddress(kDstTypeReg, Type::type_state_offset()),
           Immediate(RawType::kFinalizedInstantiated));
-  __ BranchIf(NOT_EQUAL, &is_uninstantiated);
-  // Fall through to &is_instantiated
+  __ BranchIf(NOT_EQUAL, &is_complex_case);
 
-  __ Bind(&is_instantiated);
+  // Check whether this [Type] is a function type.
+  __ movq(kTmp, FieldAddress(kDstTypeReg, Type::signature_offset()));
+  __ CompareObject(kTmp, Object::null_object());
+  __ BranchIf(NOT_EQUAL, &is_complex_case);
+
+  // Fall through to &is_simple_case
+
+  __ Bind(&is_simple_case);
   {
     __ Call(*StubCode::Subtype2TestCache_entry());
     __ CompareObject(R8, Bool::True());
@@ -2352,9 +2409,9 @@
     __ Jump(&call_runtime);
   }
 
-  __ Bind(&is_uninstantiated);
+  __ Bind(&is_complex_case);
   {
-    __ Call(*StubCode::Subtype4TestCache_entry());
+    __ Call(*StubCode::Subtype6TestCache_entry());
     __ CompareObject(R8, Bool::True());
     __ BranchIf(EQUAL, &done);  // Cache said: yes.
     // Fall through to runtime_call
diff --git a/tests/language_2/vm/regress_34051_test.dart b/tests/language_2/vm/regress_34051_test.dart
new file mode 100644
index 0000000..0e78912
--- /dev/null
+++ b/tests/language_2/vm/regress_34051_test.dart
@@ -0,0 +1,24 @@
+// Copyright (c) 2018, 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:expect/expect.dart";
+
+main() {
+  // This is a regression test for http://dartbug.com/34051.
+
+  // Warm-up the subtype cache with a "(int) -> int" closure.
+  Expect.isTrue(fooClosure(partialInst(gen)));
+
+  // Test that the generic "(T) -> T" closure raises a type error.
+  Expect.throwsTypeError(() => fooClosure(gen));
+}
+
+FI partialInst(FI arg) => arg;
+
+T gen<T>(T a) => a;
+
+final dynamic fooClosure = foo;
+bool foo(FI arg) => arg is FI;
+
+typedef FI = int Function(int);