[VM runtime] Support Smi instances in type test cache.

This adds SubtypeTestCache-based optimizations for type tests against
      * dst_type = FutureOr<T> (when T=int/num)
      * dst_type = T (when T = FutureOr<int/num>)

Remove dangerous LoadClass pseudo assembler instruction (does not work for Smi).
Handle instantiated void in type tests (along with dynamic and Object).

Change-Id: I0df0fc72ff173b9464d16cc971969132b055a429
Reviewed-on: https://dart-review.googlesource.com/c/81182
Commit-Queue: Régis Crelier <regis@google.com>
Reviewed-by: Martin Kustermann <kustermann@google.com>
diff --git a/runtime/vm/compiler/assembler/assembler_arm.cc b/runtime/vm/compiler/assembler/assembler_arm.cc
index 1f56574..e35c3c1 100644
--- a/runtime/vm/compiler/assembler/assembler_arm.cc
+++ b/runtime/vm/compiler/assembler/assembler_arm.cc
@@ -1825,12 +1825,6 @@
   ldr(result, Address(result, class_id, LSL, kSizeOfClassPairLog2));
 }
 
-void Assembler::LoadClass(Register result, Register object, Register scratch) {
-  ASSERT(scratch != result);
-  LoadClassId(scratch, object);
-  LoadClassById(result, scratch);
-}
-
 void Assembler::CompareClassId(Register object,
                                intptr_t class_id,
                                Register scratch) {
diff --git a/runtime/vm/compiler/assembler/assembler_arm.h b/runtime/vm/compiler/assembler/assembler_arm.h
index e78943db..93da8c4 100644
--- a/runtime/vm/compiler/assembler/assembler_arm.h
+++ b/runtime/vm/compiler/assembler/assembler_arm.h
@@ -860,7 +860,6 @@
 
   void LoadClassId(Register result, Register object, Condition cond = AL);
   void LoadClassById(Register result, Register class_id);
-  void LoadClass(Register result, Register object, Register scratch);
   void CompareClassId(Register object, intptr_t class_id, Register scratch);
   void LoadClassIdMayBeSmi(Register result, Register object);
   void LoadTaggedClassIdMayBeSmi(Register result, Register object);
diff --git a/runtime/vm/compiler/assembler/assembler_arm64.cc b/runtime/vm/compiler/assembler/assembler_arm64.cc
index cdd45c8..c258d9a 100644
--- a/runtime/vm/compiler/assembler/assembler_arm64.cc
+++ b/runtime/vm/compiler/assembler/assembler_arm64.cc
@@ -1115,12 +1115,6 @@
   ldr(result, Address(result, class_id, UXTX, Address::Scaled));
 }
 
-void Assembler::LoadClass(Register result, Register object) {
-  ASSERT(object != TMP);
-  LoadClassId(TMP, object);
-  LoadClassById(result, TMP);
-}
-
 void Assembler::CompareClassId(Register object,
                                intptr_t class_id,
                                Register scratch) {
diff --git a/runtime/vm/compiler/assembler/assembler_arm64.h b/runtime/vm/compiler/assembler/assembler_arm64.h
index 785b0ac..7807f87 100644
--- a/runtime/vm/compiler/assembler/assembler_arm64.h
+++ b/runtime/vm/compiler/assembler/assembler_arm64.h
@@ -1531,7 +1531,6 @@
   void LoadClassId(Register result, Register object);
   // 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,
                       intptr_t class_id,
                       Register scratch = kNoRegister);
diff --git a/runtime/vm/compiler/assembler/assembler_ia32.cc b/runtime/vm/compiler/assembler/assembler_ia32.cc
index 1f528e18..e2074e5 100644
--- a/runtime/vm/compiler/assembler/assembler_ia32.cc
+++ b/runtime/vm/compiler/assembler/assembler_ia32.cc
@@ -2434,12 +2434,6 @@
   movl(result, Address(result, class_id, TIMES_8, 0));
 }
 
-void Assembler::LoadClass(Register result, Register object, Register scratch) {
-  ASSERT(scratch != result);
-  LoadClassId(scratch, object);
-  LoadClassById(result, scratch);
-}
-
 void Assembler::CompareClassId(Register object,
                                intptr_t class_id,
                                Register scratch) {
diff --git a/runtime/vm/compiler/assembler/assembler_ia32.h b/runtime/vm/compiler/assembler/assembler_ia32.h
index c3b8492..cb75d28 100644
--- a/runtime/vm/compiler/assembler/assembler_ia32.h
+++ b/runtime/vm/compiler/assembler/assembler_ia32.h
@@ -666,8 +666,6 @@
 
   void LoadClassById(Register result, Register class_id);
 
-  void LoadClass(Register result, Register object, Register scratch);
-
   void CompareClassId(Register object, intptr_t class_id, Register scratch);
 
   void LoadClassIdMayBeSmi(Register result, Register object);
diff --git a/runtime/vm/compiler/assembler/assembler_x64.cc b/runtime/vm/compiler/assembler/assembler_x64.cc
index d3df2c3..c91098b 100644
--- a/runtime/vm/compiler/assembler/assembler_x64.cc
+++ b/runtime/vm/compiler/assembler/assembler_x64.cc
@@ -1952,11 +1952,6 @@
   movq(result, Address(result, class_id, TIMES_8, 0));
 }
 
-void Assembler::LoadClass(Register result, Register object) {
-  LoadClassId(TMP, object);
-  LoadClassById(result, TMP);
-}
-
 void Assembler::CompareClassId(Register object,
                                intptr_t class_id,
                                Register scratch) {
diff --git a/runtime/vm/compiler/assembler/assembler_x64.h b/runtime/vm/compiler/assembler/assembler_x64.h
index 70f7d9e..6090c76 100644
--- a/runtime/vm/compiler/assembler/assembler_x64.h
+++ b/runtime/vm/compiler/assembler/assembler_x64.h
@@ -782,8 +782,6 @@
   // 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,
                       intptr_t class_id,
                       Register scratch = kNoRegister);
diff --git a/runtime/vm/compiler/backend/flow_graph_compiler_arm.cc b/runtime/vm/compiler/backend/flow_graph_compiler_arm.cc
index b6a070f..729d9ff 100644
--- a/runtime/vm/compiler/backend/flow_graph_compiler_arm.cc
+++ b/runtime/vm/compiler/backend/flow_graph_compiler_arm.cc
@@ -248,13 +248,14 @@
   ASSERT(type_class.NumTypeArguments() > 0);
   const Register kInstanceReg = R0;
   Error& bound_error = Error::Handle(zone());
-  const Type& int_type = Type::Handle(zone(), Type::IntType());
+  const Type& smi_type = Type::Handle(zone(), Type::SmiType());
   const bool smi_is_ok =
-      int_type.IsSubtypeOf(type, &bound_error, NULL, Heap::kOld);
+      smi_type.IsSubtypeOf(type, &bound_error, NULL, Heap::kOld);
   // Malformed type should have been handled at graph construction time.
   ASSERT(smi_is_ok || bound_error.IsNull());
   __ tst(kInstanceReg, Operand(kSmiTagMask));
   if (smi_is_ok) {
+    // Fast case for type = FutureOr<int/num/top-type>.
     __ b(is_instance_lbl, EQ);
   } else {
     __ b(is_not_instance_lbl, EQ);
@@ -286,7 +287,7 @@
     ASSERT(!tp_argument.IsMalformed());
     if (tp_argument.IsType()) {
       ASSERT(tp_argument.HasResolvedTypeClass());
-      // Check if type argument is dynamic or Object.
+      // Check if type argument is dynamic, Object, or void.
       const Type& object_type = Type::Handle(zone(), Type::ObjectType());
       if (object_type.IsSubtypeOf(tp_argument, NULL, NULL, Heap::kOld)) {
         // Instance class test only necessary.
@@ -341,6 +342,7 @@
   if (smi_class.IsSubtypeOf(Object::null_type_arguments(), type_class,
                             Object::null_type_arguments(), NULL, NULL,
                             Heap::kOld)) {
+    // Fast case for type = int/num/top-type.
     __ b(is_instance_lbl, EQ);
   } else {
     __ b(is_not_instance_lbl, EQ);
@@ -398,7 +400,14 @@
     Label* is_not_instance_lbl) {
   __ Comment("Subtype1TestCacheLookup");
   const Register kInstanceReg = R0;
-  __ LoadClass(R1, kInstanceReg, R2);
+#if defined(DEBUG)
+  Label ok;
+  __ BranchIfNotSmi(kInstanceReg, &ok);
+  __ Breakpoint();
+  __ Bind(&ok);
+#endif
+  __ LoadClassId(R2, kInstanceReg);
+  __ LoadClassById(R1, R2);
   // R1: instance class.
   // Check immediate superclass equality.
   __ ldr(R2, FieldAddress(R1, Class::super_type_offset()));
@@ -447,12 +456,13 @@
     __ ldr(R3, FieldAddress(kTypeArgumentsReg,
                             TypeArguments::type_at_offset(type_param.index())));
     // R3: concrete type of type.
-    // Check if type argument is dynamic.
+    // Check if type argument is dynamic, Object, or void.
     __ CompareObject(R3, Object::dynamic_type());
     __ b(is_instance_lbl, EQ);
     __ CompareObject(R3, Type::ZoneHandle(zone(), Type::ObjectType()));
     __ b(is_instance_lbl, EQ);
-    // TODO(regis): Optimize void type as well once allowed as type argument.
+    __ CompareObject(R3, Object::void_type());
+    __ b(is_instance_lbl, EQ);
 
     // For Smi check quickly against int and num interfaces.
     Label not_smi;
@@ -462,9 +472,8 @@
     __ b(is_instance_lbl, EQ);
     __ CompareObject(R3, Type::ZoneHandle(zone(), Type::Number()));
     __ b(is_instance_lbl, EQ);
-    // Smi must be handled in runtime.
-    Label fall_through;
-    __ b(&fall_through);
+    // Smi can be handled by type test cache.
+    __ Bind(&not_smi);
 
     // 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
@@ -474,17 +483,18 @@
                          ? kTestTypeSixArgs
                          : kTestTypeFourArgs;
 
-    __ Bind(&not_smi);
     const SubtypeTestCache& type_test_cache = SubtypeTestCache::ZoneHandle(
         zone(), GenerateCallSubtypeTestStub(
                     test_kind, kInstanceReg, kInstantiatorTypeArgumentsReg,
                     kFunctionTypeArgumentsReg, kTempReg, is_instance_lbl,
                     is_not_instance_lbl));
-    __ Bind(&fall_through);
     return type_test_cache.raw();
   }
   if (type.IsType()) {
-    __ BranchIfSmi(kInstanceReg, is_not_instance_lbl);
+    // Smi is FutureOr<T>, when T is a top type or int or num.
+    if (!FLAG_strong || !Class::Handle(type.type_class()).IsFutureOrClass()) {
+      __ BranchIfSmi(kInstanceReg, is_not_instance_lbl);
+    }
     __ ldm(IA, SP,
            (1 << kFunctionTypeArgumentsReg) |
                (1 << kInstantiatorTypeArgumentsReg));
diff --git a/runtime/vm/compiler/backend/flow_graph_compiler_arm64.cc b/runtime/vm/compiler/backend/flow_graph_compiler_arm64.cc
index eb23398..4812ac4 100644
--- a/runtime/vm/compiler/backend/flow_graph_compiler_arm64.cc
+++ b/runtime/vm/compiler/backend/flow_graph_compiler_arm64.cc
@@ -243,11 +243,12 @@
   ASSERT(type_class.NumTypeArguments() > 0);
   const Register kInstanceReg = R0;
   Error& bound_error = Error::Handle(zone());
-  const Type& int_type = Type::Handle(zone(), Type::IntType());
+  const Type& smi_type = Type::Handle(zone(), Type::SmiType());
   const bool smi_is_ok =
-      int_type.IsSubtypeOf(type, &bound_error, NULL, Heap::kOld);
+      smi_type.IsSubtypeOf(type, &bound_error, NULL, Heap::kOld);
   // Malformed type should have been handled at graph construction time.
   ASSERT(smi_is_ok || bound_error.IsNull());
+  // Fast case for type = FutureOr<int/num/top-type>.
   __ BranchIfSmi(kInstanceReg,
                  smi_is_ok ? is_instance_lbl : is_not_instance_lbl);
 
@@ -278,7 +279,7 @@
     ASSERT(!tp_argument.IsMalformed());
     if (tp_argument.IsType()) {
       ASSERT(tp_argument.HasResolvedTypeClass());
-      // Check if type argument is dynamic or Object.
+      // Check if type argument is dynamic, Object, or void.
       const Type& object_type = Type::Handle(zone(), Type::ObjectType());
       if (object_type.IsSubtypeOf(tp_argument, NULL, NULL, Heap::kOld)) {
         // Instance class test only necessary.
@@ -331,6 +332,7 @@
   if (smi_class.IsSubtypeOf(Object::null_type_arguments(), type_class,
                             Object::null_type_arguments(), NULL, NULL,
                             Heap::kOld)) {
+    // Fast case for type = int/num/top-type.
     __ BranchIfSmi(kInstanceReg, is_instance_lbl);
   } else {
     __ BranchIfSmi(kInstanceReg, is_not_instance_lbl);
@@ -388,7 +390,14 @@
     Label* is_not_instance_lbl) {
   __ Comment("Subtype1TestCacheLookup");
   const Register kInstanceReg = R0;
-  __ LoadClass(R1, kInstanceReg);
+#if defined(DEBUG)
+  Label ok;
+  __ BranchIfNotSmi(kInstanceReg, &ok);
+  __ Breakpoint();
+  __ Bind(&ok);
+#endif
+  __ LoadClassId(TMP, kInstanceReg);
+  __ LoadClassById(R1, TMP);
   // R1: instance class.
   // Check immediate superclass equality.
   __ LoadFieldFromOffset(R2, R1, Class::super_type_offset());
@@ -435,12 +444,13 @@
     __ LoadFieldFromOffset(R3, kTypeArgumentsReg,
                            TypeArguments::type_at_offset(type_param.index()));
     // R3: concrete type of type.
-    // Check if type argument is dynamic.
+    // Check if type argument is dynamic, Object, or void.
     __ CompareObject(R3, Object::dynamic_type());
     __ b(is_instance_lbl, EQ);
     __ CompareObject(R3, Type::ZoneHandle(zone(), Type::ObjectType()));
     __ b(is_instance_lbl, EQ);
-    // TODO(regis): Optimize void type as well once allowed as type argument.
+    __ CompareObject(R3, Object::void_type());
+    __ b(is_instance_lbl, EQ);
 
     // For Smi check quickly against int and num interfaces.
     Label not_smi;
@@ -449,9 +459,8 @@
     __ b(is_instance_lbl, EQ);
     __ CompareObject(R3, Type::ZoneHandle(zone(), Type::Number()));
     __ b(is_instance_lbl, EQ);
-    // Smi must be handled in runtime.
-    Label fall_through;
-    __ b(&fall_through);
+    // Smi can be handled by type test cache.
+    __ Bind(&not_smi);
 
     // 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
@@ -461,17 +470,18 @@
                          ? kTestTypeSixArgs
                          : kTestTypeFourArgs;
 
-    __ Bind(&not_smi);
     const SubtypeTestCache& type_test_cache = SubtypeTestCache::ZoneHandle(
         zone(), GenerateCallSubtypeTestStub(
                     test_kind, kInstanceReg, kInstantiatorTypeArgumentsReg,
                     kFunctionTypeArgumentsReg, kTempReg, is_instance_lbl,
                     is_not_instance_lbl));
-    __ Bind(&fall_through);
     return type_test_cache.raw();
   }
   if (type.IsType()) {
-    __ BranchIfSmi(kInstanceReg, is_not_instance_lbl);
+    // Smi is FutureOr<T>, when T is a top type or int or num.
+    if (!FLAG_strong || !Class::Handle(type.type_class()).IsFutureOrClass()) {
+      __ 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
diff --git a/runtime/vm/compiler/backend/flow_graph_compiler_ia32.cc b/runtime/vm/compiler/backend/flow_graph_compiler_ia32.cc
index d8de557..f6dc95a 100644
--- a/runtime/vm/compiler/backend/flow_graph_compiler_ia32.cc
+++ b/runtime/vm/compiler/backend/flow_graph_compiler_ia32.cc
@@ -256,13 +256,14 @@
   ASSERT(type_class.NumTypeArguments() > 0);
   const Register kInstanceReg = EAX;
   Error& bound_error = Error::Handle(zone());
-  const Type& int_type = Type::Handle(zone(), Type::IntType());
+  const Type& smi_type = Type::Handle(zone(), Type::SmiType());
   const bool smi_is_ok =
-      int_type.IsSubtypeOf(type, &bound_error, NULL, Heap::kOld);
+      smi_type.IsSubtypeOf(type, &bound_error, NULL, Heap::kOld);
   // Malformed type should have been handled at graph construction time.
   ASSERT(smi_is_ok || bound_error.IsNull());
   __ testl(kInstanceReg, Immediate(kSmiTagMask));
   if (smi_is_ok) {
+    // Fast case for type = FutureOr<int/num/top-type>.
     __ j(ZERO, is_instance_lbl);
   } else {
     __ j(ZERO, is_not_instance_lbl);
@@ -294,7 +295,7 @@
     ASSERT(!tp_argument.IsMalformed());
     if (tp_argument.IsType()) {
       ASSERT(tp_argument.HasResolvedTypeClass());
-      // Check if type argument is dynamic or Object.
+      // Check if type argument is dynamic, Object, or void.
       const Type& object_type = Type::Handle(zone(), Type::ObjectType());
       if (object_type.IsSubtypeOf(tp_argument, NULL, NULL, Heap::kOld)) {
         // Instance class test only necessary.
@@ -347,6 +348,7 @@
   if (smi_class.IsSubtypeOf(Object::null_type_arguments(), type_class,
                             Object::null_type_arguments(), NULL, NULL,
                             Heap::kOld)) {
+    // Fast case for type = int/num/top-type.
     __ j(ZERO, is_instance_lbl);
   } else {
     __ j(ZERO, is_not_instance_lbl);
@@ -404,7 +406,14 @@
     Label* is_not_instance_lbl) {
   __ Comment("Subtype1TestCacheLookup");
   const Register kInstanceReg = EAX;
-  __ LoadClass(ECX, kInstanceReg, EDI);
+#if defined(DEBUG)
+  Label ok;
+  __ BranchIfNotSmi(kInstanceReg, &ok);
+  __ Breakpoint();
+  __ Bind(&ok);
+#endif
+  __ LoadClassId(EDI, kInstanceReg);
+  __ LoadClassById(ECX, EDI);
   // ECX: instance class.
   // Check immediate superclass equality.
   __ movl(EDI, FieldAddress(ECX, Class::super_type_offset()));
@@ -455,12 +464,13 @@
     __ movl(EDI, FieldAddress(kTypeArgumentsReg, TypeArguments::type_at_offset(
                                                      type_param.index())));
     // EDI: concrete type of type.
-    // Check if type argument is dynamic.
+    // Check if type argument is dynamic, Object, or void.
     __ CompareObject(EDI, Object::dynamic_type());
     __ j(EQUAL, is_instance_lbl);
     __ CompareObject(EDI, Type::ZoneHandle(zone(), Type::ObjectType()));
     __ j(EQUAL, is_instance_lbl);
-    // TODO(regis): Optimize void type as well once allowed as type argument.
+    __ CompareObject(EDI, Object::void_type());
+    __ j(EQUAL, is_instance_lbl);
 
     // For Smi check quickly against int and num interfaces.
     Label not_smi;
@@ -470,9 +480,8 @@
     __ j(EQUAL, is_instance_lbl);
     __ CompareObject(EDI, Type::ZoneHandle(zone(), Type::Number()));
     __ j(EQUAL, is_instance_lbl);
-    // Smi must be handled in runtime.
-    Label fall_through;
-    __ jmp(&fall_through);
+    // Smi can be handled by type test cache.
+    __ Bind(&not_smi);
 
     // If it's guaranteed, by type-parameter bound, that the type parameter will
     // never have a value of a function type.
@@ -481,18 +490,19 @@
                          ? kTestTypeSixArgs
                          : kTestTypeFourArgs;
 
-    __ Bind(&not_smi);
     const SubtypeTestCache& type_test_cache = SubtypeTestCache::ZoneHandle(
         zone(), GenerateCallSubtypeTestStub(
                     test_kind, kInstanceReg, kInstantiatorTypeArgumentsReg,
                     kFunctionTypeArgumentsReg, kTempReg, is_instance_lbl,
                     is_not_instance_lbl));
-    __ Bind(&fall_through);
     return type_test_cache.raw();
   }
   if (type.IsType()) {
-    __ testl(kInstanceReg, Immediate(kSmiTagMask));  // Is instance Smi?
-    __ j(ZERO, is_not_instance_lbl);
+    // Smi is FutureOr<T>, when T is a top type or int or num.
+    if (!FLAG_strong || !Class::Handle(type.type_class()).IsFutureOrClass()) {
+      __ 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
diff --git a/runtime/vm/compiler/backend/flow_graph_compiler_x64.cc b/runtime/vm/compiler/backend/flow_graph_compiler_x64.cc
index d3e58d8..2471b4e 100644
--- a/runtime/vm/compiler/backend/flow_graph_compiler_x64.cc
+++ b/runtime/vm/compiler/backend/flow_graph_compiler_x64.cc
@@ -248,13 +248,14 @@
   ASSERT(type_class.NumTypeArguments() > 0);
   const Register kInstanceReg = RAX;
   Error& bound_error = Error::Handle(zone());
-  const Type& int_type = Type::Handle(zone(), Type::IntType());
+  const Type& smi_type = Type::Handle(zone(), Type::SmiType());
   const bool smi_is_ok =
-      int_type.IsSubtypeOf(type, &bound_error, NULL, Heap::kOld);
+      smi_type.IsSubtypeOf(type, &bound_error, NULL, Heap::kOld);
   // Malformed type should have been handled at graph construction time.
   ASSERT(smi_is_ok || bound_error.IsNull());
   __ testq(kInstanceReg, Immediate(kSmiTagMask));
   if (smi_is_ok) {
+    // Fast case for type = FutureOr<int/num/top-type>.
     __ j(ZERO, is_instance_lbl);
   } else {
     __ j(ZERO, is_not_instance_lbl);
@@ -287,7 +288,7 @@
     ASSERT(!tp_argument.IsMalformed());
     if (tp_argument.IsType()) {
       ASSERT(tp_argument.HasResolvedTypeClass());
-      // Check if type argument is dynamic or Object.
+      // Check if type argument is dynamic, Object, or void.
       const Type& object_type = Type::Handle(zone(), Type::ObjectType());
       if (object_type.IsSubtypeOf(tp_argument, NULL, NULL, Heap::kOld)) {
         // Instance class test only necessary.
@@ -345,6 +346,7 @@
   if (smi_class.IsSubtypeOf(Object::null_type_arguments(), type_class,
                             Object::null_type_arguments(), NULL, NULL,
                             Heap::kOld)) {
+    // Fast case for type = int/num/top-type.
     __ j(ZERO, is_instance_lbl);
   } else {
     __ j(ZERO, is_not_instance_lbl);
@@ -406,7 +408,14 @@
     Label* is_not_instance_lbl) {
   __ Comment("Subtype1TestCacheLookup");
   const Register kInstanceReg = RAX;
-  __ LoadClass(R10, kInstanceReg);
+#if defined(DEBUG)
+  Label ok;
+  __ BranchIfNotSmi(kInstanceReg, &ok);
+  __ Breakpoint();
+  __ Bind(&ok);
+#endif
+  __ LoadClassId(TMP, kInstanceReg);
+  __ LoadClassById(R10, TMP);
   // R10: instance class.
   // Check immediate superclass equality.
   __ movq(R13, FieldAddress(R10, Class::super_type_offset()));
@@ -458,13 +467,14 @@
     __ movq(RDI, FieldAddress(kTypeArgumentsReg, TypeArguments::type_at_offset(
                                                      type_param.index())));
     // RDI: Concrete type of type.
-    // Check if type argument is dynamic.
+    // Check if type argument is dynamic, Object, or void.
     __ CompareObject(RDI, Object::dynamic_type());
     __ j(EQUAL, is_instance_lbl);
     const Type& object_type = Type::ZoneHandle(zone(), Type::ObjectType());
     __ CompareObject(RDI, object_type);
     __ j(EQUAL, is_instance_lbl);
-    // TODO(regis): Optimize void type as well once allowed as type argument.
+    __ CompareObject(RDI, Object::void_type());
+    __ j(EQUAL, is_instance_lbl);
 
     // For Smi check quickly against int and num interfaces.
     Label not_smi;
@@ -474,9 +484,8 @@
     __ j(EQUAL, is_instance_lbl);
     __ CompareObject(RDI, Type::ZoneHandle(zone(), Type::Number()));
     __ j(EQUAL, is_instance_lbl);
-    // Smi must be handled in runtime.
-    Label fall_through;
-    __ jmp(&fall_through);
+    // Smi can be handled by type test cache.
+    __ Bind(&not_smi);
 
     // 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
@@ -486,18 +495,19 @@
                          ? kTestTypeSixArgs
                          : kTestTypeFourArgs;
 
-    __ Bind(&not_smi);
     const SubtypeTestCache& type_test_cache = SubtypeTestCache::ZoneHandle(
         zone(), GenerateCallSubtypeTestStub(
                     test_kind, kInstanceReg, kInstantiatorTypeArgumentsReg,
                     kFunctionTypeArgumentsReg, kTempReg, is_instance_lbl,
                     is_not_instance_lbl));
-    __ Bind(&fall_through);
     return type_test_cache.raw();
   }
   if (type.IsType()) {
-    __ testq(kInstanceReg, Immediate(kSmiTagMask));  // Is instance Smi?
-    __ j(ZERO, is_not_instance_lbl);
+    // Smi is FutureOr<T>, when T is a top type or int or num.
+    if (!FLAG_strong || !Class::Handle(type.type_class()).IsFutureOrClass()) {
+      __ testq(kInstanceReg, Immediate(kSmiTagMask));  // Is instance Smi?
+      __ 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).
     return GenerateCallSubtypeTestStub(kTestTypeFourArgs, kInstanceReg,
diff --git a/runtime/vm/runtime_entry.cc b/runtime/vm/runtime_entry.cc
index ad00ea5..5994aed 100644
--- a/runtime/vm/runtime_entry.cc
+++ b/runtime/vm/runtime_entry.cc
@@ -651,18 +651,17 @@
     }
     return;
   }
+  Class& instance_class = Class::Handle(zone);
   if (instance.IsSmi()) {
-    if (FLAG_trace_type_checks) {
-      OS::PrintErr("UpdateTypeTestCache: instance is Smi\n");
-    }
-    return;
+    instance_class = Smi::Class();
+  } else {
+    instance_class = instance.clazz();
   }
   // If the type is uninstantiated and refers to parent function type
   // parameters, the function_type_arguments have been canonicalized
   // when concatenated.
   ASSERT(function_type_arguments.IsNull() ||
          function_type_arguments.IsCanonical());
-  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);
@@ -748,14 +747,16 @@
         "  Updated test cache %p ix: %" Pd
         " with "
         "(cid-or-fun: %p, type-args: %p, i-type-args: %p, f-type-args: %p, "
-        "result: %s)\n"
+        "p-type-args: %p, d-type-args: %p, result: %s)\n"
         "    instance  [class: (%p '%s' cid: %" Pd
         "),    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(),
+        function_type_arguments.raw(),
+        instance_parent_function_type_arguments.raw(),
+        instance_delayed_type_arguments.raw(), result.ToCString(),
         instance_class.raw(), instance_class_name.ToCString(),
         instance_class.id(), instance_type_arguments.raw(),
         instance_type_arguments.ToCString(), type_class.raw(),
diff --git a/runtime/vm/stub_code_arm.cc b/runtime/vm/stub_code_arm.cc
index 8791013..2ddd1dc 100644
--- a/runtime/vm/stub_code_arm.cc
+++ b/runtime/vm/stub_code_arm.cc
@@ -2007,7 +2007,11 @@
   __ AddImmediate(kCacheReg, Array::data_offset() - kHeapObjectTag);
 
   Label loop, not_closure;
-  __ LoadClassId(kInstanceCidOrFunction, kInstanceReg);
+  if (n >= 4) {
+    __ LoadClassIdMayBeSmi(kInstanceCidOrFunction, kInstanceReg);
+  } else {
+    __ LoadClassId(kInstanceCidOrFunction, kInstanceReg);
+  }
   __ CompareImmediate(kInstanceCidOrFunction, kClosureCid);
   __ b(&not_closure, NE);
 
@@ -2295,7 +2299,6 @@
 void StubCode::GenerateSlowTypeTestStub(Assembler* assembler) {
   Label done, call_runtime;
 
-  const Register kInstanceReg = R0;
   const Register kFunctionTypeArgumentsReg = R1;
   const Register kDstTypeReg = R8;
   const Register kSubtypeTestCacheReg = R3;
@@ -2303,6 +2306,7 @@
   __ EnterStubFrame();
 
 #ifdef DEBUG
+  const Register kInstanceReg = R0;
   // Guaranteed by caller.
   Label no_error;
   __ CompareObject(kInstanceReg, Object::null_object());
@@ -2311,11 +2315,6 @@
   __ Bind(&no_error);
 #endif
 
-  // Need to handle slow cases of [Smi]s here because the
-  // [SubtypeTestCache]-based stubs do not handle [Smi]s.
-  Label non_smi_value;
-  __ BranchIfSmi(kInstanceReg, &call_runtime);
-
   // If the subtype-cache is null, it needs to be lazily-created by the runtime.
   __ CompareObject(kSubtypeTestCacheReg, Object::null_object());
   __ BranchIf(EQUAL, &call_runtime);
diff --git a/runtime/vm/stub_code_arm64.cc b/runtime/vm/stub_code_arm64.cc
index 8aa8213..fc87279 100644
--- a/runtime/vm/stub_code_arm64.cc
+++ b/runtime/vm/stub_code_arm64.cc
@@ -2271,7 +2271,11 @@
   __ AddImmediate(kCacheReg, Array::data_offset() - kHeapObjectTag);
 
   Label loop, not_closure;
-  __ LoadClassId(kInstanceCidOrFunction, kInstanceReg);
+  if (n >= 4) {
+    __ LoadClassIdMayBeSmi(kInstanceCidOrFunction, kInstanceReg);
+  } else {
+    __ LoadClassId(kInstanceCidOrFunction, kInstanceReg);
+  }
   __ CompareImmediate(kInstanceCidOrFunction, kClosureCid);
   __ b(&not_closure, NE);
 
@@ -2567,7 +2571,6 @@
 void StubCode::GenerateSlowTypeTestStub(Assembler* assembler) {
   Label done, call_runtime;
 
-  const Register kInstanceReg = R0;
   const Register kInstantiatorTypeArgumentsReg = R1;
 
   const Register kSubtypeTestCacheReg = R3;
@@ -2576,6 +2579,7 @@
   __ EnterStubFrame();
 
 #ifdef DEBUG
+  const Register kInstanceReg = R0;
   // Guaranteed by caller.
   Label no_error;
   __ CompareObject(kInstanceReg, Object::null_object());
@@ -2584,11 +2588,6 @@
   __ Bind(&no_error);
 #endif
 
-  // Need to handle slow cases of [Smi]s here because the
-  // [SubtypeTestCache]-based stubs do not handle [Smi]s.
-  Label non_smi_value;
-  __ BranchIfSmi(kInstanceReg, &call_runtime);
-
   // If the subtype-cache is null, it needs to be lazily-created by the runtime.
   __ CompareObject(kSubtypeTestCacheReg, Object::null_object());
   __ BranchIf(EQUAL, &call_runtime);
diff --git a/runtime/vm/stub_code_ia32.cc b/runtime/vm/stub_code_ia32.cc
index 4535bad..d609e2a 100644
--- a/runtime/vm/stub_code_ia32.cc
+++ b/runtime/vm/stub_code_ia32.cc
@@ -1752,8 +1752,11 @@
   __ addl(EDX, Immediate(Array::data_offset() - kHeapObjectTag));
 
   Label loop, not_closure;
-
-  __ LoadClassId(kInstanceCidOrFunction, kInstanceReg);
+  if (n >= 4) {
+    __ LoadClassIdMayBeSmi(kInstanceCidOrFunction, kInstanceReg);
+  } else {
+    __ LoadClassId(kInstanceCidOrFunction, kInstanceReg);
+  }
   __ cmpl(kInstanceCidOrFunction, Immediate(kClosureCid));
   __ j(NOT_EQUAL, &not_closure, Assembler::kNearJump);
 
diff --git a/runtime/vm/stub_code_x64.cc b/runtime/vm/stub_code_x64.cc
index c8f4218..295d7ce 100644
--- a/runtime/vm/stub_code_x64.cc
+++ b/runtime/vm/stub_code_x64.cc
@@ -2291,8 +2291,12 @@
   __ addq(RSI, Immediate(Array::data_offset() - kHeapObjectTag));
 
   Label loop, not_closure;
-  __ LoadClassId(R10, kInstanceReg);
-  __ cmpq(R10, Immediate(kClosureCid));
+  if (n >= 4) {
+    __ LoadClassIdMayBeSmi(kInstanceCidOrFunction, kInstanceReg);
+  } else {
+    __ LoadClassId(kInstanceCidOrFunction, kInstanceReg);
+  }
+  __ cmpq(kInstanceCidOrFunction, Immediate(kClosureCid));
   __ j(NOT_EQUAL, &not_closure, Assembler::kNearJump);
 
   // Closure handling.
@@ -2581,13 +2585,13 @@
 void StubCode::GenerateSlowTypeTestStub(Assembler* assembler) {
   Label done, call_runtime;
 
-  const Register kInstanceReg = RAX;
   const Register kDstTypeReg = RBX;
   const Register kSubtypeTestCacheReg = R9;
 
   __ EnterStubFrame();
 
 #ifdef DEBUG
+  const Register kInstanceReg = RAX;
   // Guaranteed by caller.
   Label no_error;
   __ CompareObject(kInstanceReg, Object::null_object());
@@ -2596,10 +2600,6 @@
   __ Bind(&no_error);
 #endif
 
-  // Need to handle slow cases of [Smi]s here because the
-  // [SubtypeTestCache]-based stubs do not handle [Smi]s.
-  __ BranchIfSmi(kInstanceReg, &call_runtime);
-
   // If the subtype-cache is null, it needs to be lazily-created by the runtime.
   __ CompareObject(kSubtypeTestCacheReg, Object::null_object());
   __ BranchIf(EQUAL, &call_runtime);