[vm] Allow STCs that only store instantiator, not function, type args.

If we are generating a SubtypeTestCache for a known type that only
needs instantiator type arguments to be fully instantiated, then
don't store and check against the unneeded function type arguments.

TEST=vm/cc/STC, vm/cc/TTS, ci

Change-Id: I370adf820168079322b8a87811057670a47ee6b3
Cq-Include-Trybots: luci.dart.try:vm-tsan-linux-release-x64-try,vm-reload-rollback-linux-release-x64-try,vm-reload-linux-release-x64-try,vm-linux-release-x64-try,vm-aot-tsan-linux-release-x64-try,vm-aot-linux-release-x64-try,vm-aot-linux-product-x64-try,vm-aot-linux-debug-x64-try,vm-linux-debug-x64-try,vm-mac-debug-arm64-try,vm-aot-mac-release-arm64-try,vm-ffi-qemu-linux-release-arm-try,vm-ffi-qemu-linux-release-riscv64-try,vm-linux-debug-ia32-try,vm-linux-release-ia32-try
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/311382
Reviewed-by: Martin Kustermann <kustermann@google.com>
Commit-Queue: Tess Strickland <sstrickl@google.com>
Reviewed-by: Alexander Markov <alexmarkov@google.com>
diff --git a/runtime/vm/compiler/backend/flow_graph_compiler.cc b/runtime/vm/compiler/backend/flow_graph_compiler.cc
index 080f034..3d629e5 100644
--- a/runtime/vm/compiler/backend/flow_graph_compiler.cc
+++ b/runtime/vm/compiler/backend/flow_graph_compiler.cc
@@ -2669,10 +2669,13 @@
     if (!type.IsFutureOrType()) {
       __ BranchIfSmi(TypeTestABI::kInstanceReg, is_not_instance_lbl);
     }
+    const TypeTestStubKind test_kind =
+        type.IsInstantiated(kFunctions) ? TypeTestStubKind::kTestTypeThreeArgs
+                                        : TypeTestStubKind::kTestTypeFourArgs;
     // Uninstantiated type class is known at compile time, but the type
     // arguments are determined at runtime by the instantiator(s).
-    return GenerateCallSubtypeTestStub(TypeTestStubKind::kTestTypeFourArgs,
-                                       is_instance_lbl, is_not_instance_lbl);
+    return GenerateCallSubtypeTestStub(test_kind, is_instance_lbl,
+                                       is_not_instance_lbl);
   }
   return SubtypeTestCache::null();
 }
@@ -2744,9 +2747,9 @@
 // Expected inputs (from TypeTestABI):
 // - kInstanceReg: instance (preserved).
 // - kInstantiatorTypeArgumentsReg: instantiator type arguments
-//   (for test_kind == kTestTypeFourArg or test_kind == kTestTypeSixArg).
+//   (for test_kind >= kTestTypeThreeArg).
 // - kFunctionTypeArgumentsReg: function type arguments
-//   (for test_kind == kTestTypeFourArg or test_kind == kTestTypeSixArg).
+//   (for test_kind >= kTestTypeFourArg).
 //
 // See the arch-specific GenerateSubtypeNTestCacheStub method to see which
 // registers may need saving across this call.
diff --git a/runtime/vm/compiler/backend/flow_graph_compiler.h b/runtime/vm/compiler/backend/flow_graph_compiler.h
index c04c8a2..d898353 100644
--- a/runtime/vm/compiler/backend/flow_graph_compiler.h
+++ b/runtime/vm/compiler/backend/flow_graph_compiler.h
@@ -1023,10 +1023,17 @@
       compiler::Label* is_not_instance_lbl);
 
   enum class TypeTestStubKind {
+    // Just check the instance cid (no closures).
     kTestTypeOneArg = 1,
+    // Also check the instance type arguments.
     kTestTypeTwoArgs = 2,
+    // Also check the instantiator type arguments for the destination type.
+    kTestTypeThreeArgs = 3,
+    // Also check the function type arguments for the destination type.
     kTestTypeFourArgs = 4,
+    // Also check the parent function and delayed type arguments for a closure.
     kTestTypeSixArgs = 6,
+    // Also check the destination type, as it is not known at compile time.
     kTestTypeSevenArgs = 7,
   };
 
diff --git a/runtime/vm/compiler/backend/flow_graph_compiler_ia32.cc b/runtime/vm/compiler/backend/flow_graph_compiler_ia32.cc
index 2d2d22c..c1e00b9 100644
--- a/runtime/vm/compiler/backend/flow_graph_compiler_ia32.cc
+++ b/runtime/vm/compiler/backend/flow_graph_compiler_ia32.cc
@@ -190,9 +190,9 @@
 // - kInstanceReg: instance.
 // - kDstTypeReg: destination type (for test_kind == kTestTypeSevenArg).
 // - kInstantiatorTypeArgumentsReg: instantiator type arguments
-//   (for test_kind == kTestTypeFourArg, kTestTypeSixArg, or kTestTypeSevenArg).
+//   (for test_kind >= kTestTypeThreeArg).
 // - kFunctionTypeArgumentsReg: function type arguments
-//   (for test_kind == kTestTypeFourArg, kTestTypeSixArg, or kTestTypeSevenArg).
+//   (for test_kind >= kTestTypeFourArg).
 //
 // Only preserves kInstanceReg from TypeTestABI, all other TypeTestABI
 // registers may be used and thus must be saved by the caller.
diff --git a/runtime/vm/compiler/runtime_api.cc b/runtime/vm/compiler/runtime_api.cc
index 966035a..5a33326 100644
--- a/runtime/vm/compiler/runtime_api.cc
+++ b/runtime/vm/compiler/runtime_api.cc
@@ -297,6 +297,10 @@
   return dart::StubCode::Subtype2TestCache();
 }
 
+const Code& StubCodeSubtype3TestCache() {
+  return dart::StubCode::Subtype3TestCache();
+}
+
 const Code& StubCodeSubtype4TestCache() {
   return dart::StubCode::Subtype4TestCache();
 }
diff --git a/runtime/vm/compiler/runtime_api.h b/runtime/vm/compiler/runtime_api.h
index d3aa4ed..52b0b39 100644
--- a/runtime/vm/compiler/runtime_api.h
+++ b/runtime/vm/compiler/runtime_api.h
@@ -216,6 +216,7 @@
 
 const Code& StubCodeAllocateArray();
 const Code& StubCodeSubtype2TestCache();
+const Code& StubCodeSubtype3TestCache();
 const Code& StubCodeSubtype4TestCache();
 const Code& StubCodeSubtype6TestCache();
 const Code& StubCodeSubtype7TestCache();
diff --git a/runtime/vm/compiler/stub_code_compiler.cc b/runtime/vm/compiler/stub_code_compiler.cc
index 63fa5fd..c337f18 100644
--- a/runtime/vm/compiler/stub_code_compiler.cc
+++ b/runtime/vm/compiler/stub_code_compiler.cc
@@ -1091,15 +1091,17 @@
 
   // If the subtype-cache is null, it needs to be lazily-created by the runtime.
   __ CompareObject(TypeTestABI::kSubtypeTestCacheReg, NullObject());
-  __ BranchIf(EQUAL, &call_runtime, Assembler::kNearJump);
+  __ BranchIf(EQUAL, &call_runtime);
 
   // Use the number of inputs used by the STC to determine which stub to call.
-  Label call_2, call_4, call_6;
+  Label call_2, call_3, call_4, call_6;
   __ Comment("Check number of STC inputs");
   __ LoadFromSlot(TypeTestABI::kScratchReg, TypeTestABI::kSubtypeTestCacheReg,
                   Slot::SubtypeTestCache_num_inputs());
   __ CompareImmediate(TypeTestABI::kScratchReg, 2);
   __ BranchIf(EQUAL, &call_2, Assembler::kNearJump);
+  __ CompareImmediate(TypeTestABI::kScratchReg, 3);
+  __ BranchIf(EQUAL, &call_3, Assembler::kNearJump);
   __ CompareImmediate(TypeTestABI::kScratchReg, 4);
   __ BranchIf(EQUAL, &call_4, Assembler::kNearJump);
   __ CompareImmediate(TypeTestABI::kScratchReg, 6);
@@ -1135,6 +1137,16 @@
     __ Jump(&call_runtime, Assembler::kNearJump);
   }
 
+  __ Bind(&call_3);
+  {
+    __ Comment("Call 3 input STC check");
+    __ Call(StubCodeSubtype3TestCache());
+    __ CompareObject(TypeTestABI::kSubtypeTestCacheResultReg,
+                     CastHandle<Object>(TrueObject()));
+    __ BranchIf(EQUAL, &done);  // Cache said: yes.
+    __ Jump(&call_runtime, Assembler::kNearJump);
+  }
+
   __ Bind(&call_2);
   {
     __ Comment("Call 2 input STC check");
@@ -3101,7 +3113,7 @@
 
   // Fill in all the STC input registers.
   Label initialized, not_closure;
-  if (n >= 4) {
+  if (n >= 3) {
     __ LoadClassIdMayBeSmi(instance_cid_or_sig_reg, TypeTestABI::kInstanceReg);
   } else {
     // If the type is fully instantiated, then it can be determined at compile
@@ -3212,6 +3224,11 @@
 }
 
 // See comment on [GenerateSubtypeNTestCacheStub].
+void StubCodeCompiler::GenerateSubtype3TestCacheStub() {
+  GenerateSubtypeNTestCacheStub(assembler, 3);
+}
+
+// See comment on [GenerateSubtypeNTestCacheStub].
 void StubCodeCompiler::GenerateSubtype4TestCacheStub() {
   GenerateSubtypeNTestCacheStub(assembler, 4);
 }
diff --git a/runtime/vm/compiler/stub_code_compiler_arm.cc b/runtime/vm/compiler/stub_code_compiler_arm.cc
index e32d1d2..6109d78 100644
--- a/runtime/vm/compiler/stub_code_compiler_arm.cc
+++ b/runtime/vm/compiler/stub_code_compiler_arm.cc
@@ -2738,7 +2738,11 @@
 //   - kSubtypeTestCacheResultReg: the cached result, or null if not found.
 void StubCodeCompiler::GenerateSubtypeNTestCacheStub(Assembler* assembler,
                                                      int n) {
-  ASSERT(n == 1 || n == 2 || n == 4 || n == 6 || n == 7);
+  ASSERT(n >= 1);
+  ASSERT(n <= SubtypeTestCache::kMaxInputs);
+  // If we need the parent function type arguments for a closure, we also need
+  // the delayed type arguments, so this case will never happen.
+  ASSERT(n != 5);
   RegisterSet saved_registers;
 
   // Safe as the original value of TypeTestABI::kSubtypeTestCacheReg is only
diff --git a/runtime/vm/compiler/stub_code_compiler_arm64.cc b/runtime/vm/compiler/stub_code_compiler_arm64.cc
index 63070e7..48161d2 100644
--- a/runtime/vm/compiler/stub_code_compiler_arm64.cc
+++ b/runtime/vm/compiler/stub_code_compiler_arm64.cc
@@ -3072,7 +3072,11 @@
 //   - kSubtypeTestCacheResultReg: the cached result, or null if not found.
 void StubCodeCompiler::GenerateSubtypeNTestCacheStub(Assembler* assembler,
                                                      int n) {
-  ASSERT(n == 1 || n == 2 || n == 4 || n == 6 || n == 7);
+  ASSERT(n >= 1);
+  ASSERT(n <= SubtypeTestCache::kMaxInputs);
+  // If we need the parent function type arguments for a closure, we also need
+  // the delayed type arguments, so this case will never happen.
+  ASSERT(n != 5);
 
   // We could initialize kSubtypeTestCacheResultReg with null and use that as
   // the null register up until exit, which means we'd just need to return
diff --git a/runtime/vm/compiler/stub_code_compiler_ia32.cc b/runtime/vm/compiler/stub_code_compiler_ia32.cc
index a83a863..486425f 100644
--- a/runtime/vm/compiler/stub_code_compiler_ia32.cc
+++ b/runtime/vm/compiler/stub_code_compiler_ia32.cc
@@ -2501,7 +2501,11 @@
 // result (true or false).
 void StubCodeCompiler::GenerateSubtypeNTestCacheStub(Assembler* assembler,
                                                      int n) {
-  ASSERT(n == 1 || n == 2 || n == 4 || n == 6 || n == 7);
+  ASSERT(n >= 1);
+  ASSERT(n <= SubtypeTestCache::kMaxInputs);
+  // If we need the parent function type arguments for a closure, we also need
+  // the delayed type arguments, so this case will never happen.
+  ASSERT(n != 5);
 
   const auto& raw_null = Immediate(target::ToRawPointer(NullObject()));
 
@@ -2545,7 +2549,7 @@
                   target::Array::data_offset() - kHeapObjectTag);
 
   Label loop, not_closure;
-  if (n >= 4) {
+  if (n >= 3) {
     __ LoadClassIdMayBeSmi(STCInternal::kInstanceCidOrSignatureReg,
                            TypeTestABI::kInstanceReg);
   } else {
diff --git a/runtime/vm/compiler/stub_code_compiler_riscv.cc b/runtime/vm/compiler/stub_code_compiler_riscv.cc
index b5e5217..3ce14c7 100644
--- a/runtime/vm/compiler/stub_code_compiler_riscv.cc
+++ b/runtime/vm/compiler/stub_code_compiler_riscv.cc
@@ -2838,7 +2838,11 @@
 //   - kSubtypeTestCacheResultReg: the cached result, or null if not found.
 void StubCodeCompiler::GenerateSubtypeNTestCacheStub(Assembler* assembler,
                                                      int n) {
-  ASSERT(n == 1 || n == 2 || n == 4 || n == 6 || n == 7);
+  ASSERT(n >= 1);
+  ASSERT(n <= SubtypeTestCache::kMaxInputs);
+  // If we need the parent function type arguments for a closure, we also need
+  // the delayed type arguments, so this case will never happen.
+  ASSERT(n != 5);
 
   // We could initialize kSubtypeTestCacheResultReg with null and use that as
   // the null register up until exit, which means we'd just need to return
diff --git a/runtime/vm/compiler/stub_code_compiler_x64.cc b/runtime/vm/compiler/stub_code_compiler_x64.cc
index 8020821..a5098bc 100644
--- a/runtime/vm/compiler/stub_code_compiler_x64.cc
+++ b/runtime/vm/compiler/stub_code_compiler_x64.cc
@@ -3036,7 +3036,11 @@
 //   - kSubtypeTestCacheResultReg: the cached result, or null if not found.
 void StubCodeCompiler::GenerateSubtypeNTestCacheStub(Assembler* assembler,
                                                      int n) {
-  ASSERT(n == 1 || n == 2 || n == 4 || n == 6 || n == 7);
+  ASSERT(n >= 1);
+  ASSERT(n <= SubtypeTestCache::kMaxInputs);
+  // If we need the parent function type arguments for a closure, we also need
+  // the delayed type arguments, so this case will never happen.
+  ASSERT(n != 5);
   RegisterSet saved_registers;
 
   // Until we have the result, we use the result register to store the null
diff --git a/runtime/vm/object.cc b/runtime/vm/object.cc
index ee3f3e9..a29f44a 100644
--- a/runtime/vm/object.cc
+++ b/runtime/vm/object.cc
@@ -19939,7 +19939,9 @@
 
 intptr_t SubtypeTestCache::UsedInputsForType(const AbstractType& type) {
   if (type.IsType()) {
-    return type.IsInstantiated() ? 2 : 4;
+    if (type.IsInstantiated()) return 2;
+    if (type.IsInstantiated(kFunctions)) return 3;
+    return 4;
   }
   // Default to all inputs except for the destination type, which must be
   // statically known, otherwise this method wouldn't be called.
diff --git a/runtime/vm/stub_code.h b/runtime/vm/stub_code.h
index 95c01d7..ee70ec4 100644
--- a/runtime/vm/stub_code.h
+++ b/runtime/vm/stub_code.h
@@ -69,6 +69,8 @@
         return StubCode::Subtype1TestCache();
       case 2:
         return StubCode::Subtype2TestCache();
+      case 3:
+        return StubCode::Subtype3TestCache();
       case 4:
         return StubCode::Subtype4TestCache();
       case 6:
diff --git a/runtime/vm/stub_code_list.h b/runtime/vm/stub_code_list.h
index 12d2403..d8ab228 100644
--- a/runtime/vm/stub_code_list.h
+++ b/runtime/vm/stub_code_list.h
@@ -109,6 +109,7 @@
   V(NullIsAssignableToTypeNullSafe)                                            \
   V(Subtype1TestCache)                                                         \
   V(Subtype2TestCache)                                                         \
+  V(Subtype3TestCache)                                                         \
   V(Subtype4TestCache)                                                         \
   V(Subtype6TestCache)                                                         \
   V(Subtype7TestCache)                                                         \