Version 2.15.0-19.0.dev

Merge commit 'f4ddc8dc6b246cf915bb8777ee3e0efd04d20221' into 'dev'
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 79e0b5b..4f6512f 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -57,6 +57,11 @@
 - Added `void unawaited(Future)` top-level function to deal with the
   `unawaited_futures` lint.
 
+#### `dart:cli`
+
+- The experimental `waitFor` functionality, and the library containing only that
+  function, are now deprecated.
+
 #### `dart:core`
 
 - Introduce `Enum` interface implemented by all `enum` declarations.
diff --git a/runtime/vm/BUILD.gn b/runtime/vm/BUILD.gn
index 55db0a7..ab184be 100644
--- a/runtime/vm/BUILD.gn
+++ b/runtime/vm/BUILD.gn
@@ -109,6 +109,12 @@
   sources = vm_sources + rebase_path(compiler_api_sources, ".", "./compiler/") +
             rebase_path(disassembler_sources, ".", "./compiler/") +
             rebase_path(heap_sources, ".", "./heap/")
+  if (is_android) {
+    # Android specific workaround for a kernel bug. This source file can't
+    # go into vm_sources list because it will break Windows build which
+    # uses different assembler syntax.
+    sources += [ "thread_interrupter_android_arm.S" ]
+  }
   include_dirs = [ ".." ]
 }
 
diff --git a/runtime/vm/compiler/assembler/assembler_base.cc b/runtime/vm/compiler/assembler/assembler_base.cc
index 45e6a6f..9dbd0ff 100644
--- a/runtime/vm/compiler/assembler/assembler_base.cc
+++ b/runtime/vm/compiler/assembler/assembler_base.cc
@@ -299,7 +299,8 @@
 }
 
 bool AssemblerBase::EmittingComments() {
-  return FLAG_code_comments || FLAG_disassemble || FLAG_disassemble_optimized;
+  return FLAG_code_comments || FLAG_disassemble || FLAG_disassemble_optimized ||
+         FLAG_disassemble_stubs;
 }
 
 void AssemblerBase::Stop(const char* message) {
diff --git a/runtime/vm/compiler/backend/flow_graph_compiler.cc b/runtime/vm/compiler/backend/flow_graph_compiler.cc
index 5ecdd9e..4473e81 100644
--- a/runtime/vm/compiler/backend/flow_graph_compiler.cc
+++ b/runtime/vm/compiler/backend/flow_graph_compiler.cc
@@ -2349,7 +2349,7 @@
   // If there are no valid class ranges, the check will fail.  If we are
   // supposed to fall-through in the positive case, we'll explicitly jump to
   // the [outside_range_lbl].
-  if (cid_ranges.length() == 1 && cid_ranges[0].IsIllegalRange()) {
+  if (cid_ranges.is_empty()) {
     if (fall_through_if_inside) {
       assembler->Jump(outside_range_lbl);
     }
diff --git a/runtime/vm/compiler/backend/il.cc b/runtime/vm/compiler/backend/il.cc
index d2bf1c4..0224242 100644
--- a/runtime/vm/compiler/backend/il.cc
+++ b/runtime/vm/compiler/backend/il.cc
@@ -54,31 +54,16 @@
 DECLARE_FLAG(bool, inline_alloc);
 DECLARE_FLAG(bool, use_slow_path);
 
-class SubclassFinder {
+class SubtypeFinder {
  public:
-  SubclassFinder(Zone* zone,
-                 GrowableArray<intptr_t>* cids,
-                 bool include_abstract)
+  SubtypeFinder(Zone* zone,
+                GrowableArray<intptr_t>* cids,
+                bool include_abstract)
       : array_handles_(zone),
         class_handles_(zone),
         cids_(cids),
         include_abstract_(include_abstract) {}
 
-  void ScanSubClasses(const Class& klass) {
-    if (include_abstract_ || !klass.is_abstract()) {
-      cids_->Add(klass.id());
-    }
-    ScopedHandle<GrowableObjectArray> array(&array_handles_);
-    ScopedHandle<Class> subclass(&class_handles_);
-    *array = klass.direct_subclasses();
-    if (!array->IsNull()) {
-      for (intptr_t i = 0; i < array->Length(); ++i) {
-        *subclass ^= array->At(i);
-        ScanSubClasses(*subclass);
-      }
-    }
-  }
-
   void ScanImplementorClasses(const Class& klass) {
     // An implementor of [klass] is
     //    * the [klass] itself.
@@ -134,36 +119,9 @@
   CidRangeVector& ranges = (*cid_ranges)[klass.id()];
   if (ranges.length() == 0) {
     if (!FLAG_precompiled_mode) {
-      BuildRangesForJIT(table, &ranges, klass, /*use_subtype_test=*/true,
-                        include_abstract, exclude_null);
+      BuildRangesForJIT(table, &ranges, klass, include_abstract, exclude_null);
     } else {
-      BuildRangesFor(table, &ranges, klass, /*use_subtype_test=*/true,
-                     include_abstract, exclude_null);
-    }
-  }
-  return ranges;
-}
-
-const CidRangeVector& HierarchyInfo::SubclassRangesForClass(
-    const Class& klass) {
-  ClassTable* table = thread()->isolate_group()->class_table();
-  const intptr_t cid_count = table->NumCids();
-  if (cid_subclass_ranges_ == nullptr) {
-    cid_subclass_ranges_.reset(new CidRangeVector[cid_count]);
-  }
-
-  CidRangeVector& ranges = cid_subclass_ranges_[klass.id()];
-  if (ranges.length() == 0) {
-    if (!FLAG_precompiled_mode) {
-      BuildRangesForJIT(table, &ranges, klass,
-                        /*use_subtype_test=*/true,
-                        /*include_abstract=*/false,
-                        /*exclude_null=*/false);
-    } else {
-      BuildRangesFor(table, &ranges, klass,
-                     /*use_subtype_test=*/false,
-                     /*include_abstract=*/false,
-                     /*exclude_null=*/false);
+      BuildRangesFor(table, &ranges, klass, include_abstract, exclude_null);
     }
   }
   return ranges;
@@ -175,18 +133,12 @@
 void HierarchyInfo::BuildRangesFor(ClassTable* table,
                                    CidRangeVector* ranges,
                                    const Class& klass,
-                                   bool use_subtype_test,
                                    bool include_abstract,
                                    bool exclude_null) {
   Zone* zone = thread()->zone();
-  ClassTable* class_table = thread()->isolate_group()->class_table();
-
-  // Only really used if `use_subtype_test == true`.
   const Type& dst_type = Type::Handle(zone, Type::RawCast(klass.RareType()));
   AbstractType& cls_type = AbstractType::Handle(zone);
-
   Class& cls = Class::Handle(zone);
-  AbstractType& super_type = AbstractType::Handle(zone);
   const intptr_t cid_count = table->NumCids();
 
   // Iterate over all cids to find the ones to be included in the ranges.
@@ -210,24 +162,14 @@
     if (!include_abstract && cls.is_abstract()) continue;
     if (cls.IsTopLevel()) continue;
 
-    // We are either interested in [CidRange]es of subclasses or subtypes.
+    // We are interested in [CidRange]es of subtypes.
     bool test_succeeded = false;
     if (cid == kNullCid) {
       ASSERT(exclude_null);
       test_succeeded = false;
-    } else if (use_subtype_test) {
+    } else {
       cls_type = cls.RareType();
       test_succeeded = cls_type.IsSubtypeOf(dst_type, Heap::kNew);
-    } else {
-      while (!cls.IsObjectClass()) {
-        if (cls.ptr() == klass.ptr()) {
-          test_succeeded = true;
-          break;
-        }
-        super_type = cls.super_type();
-        const intptr_t type_class_id = super_type.type_class_id();
-        cls = class_table->At(type_class_id);
-      }
     }
 
     if (test_succeeded) {
@@ -245,41 +187,31 @@
     }
   }
 
-  // Construct last range (either close open one, or add invalid).
+  // Construct last range if there is a open one.
   if (start != -1) {
     ASSERT(start <= end);
     CidRange range(start, end);
     ranges->Add(range);
-  } else if (ranges->length() == 0) {
-    CidRange range;
-    ASSERT(range.IsIllegalRange());
-    ranges->Add(range);
   }
 }
 
 void HierarchyInfo::BuildRangesForJIT(ClassTable* table,
                                       CidRangeVector* ranges,
                                       const Class& dst_klass,
-                                      bool use_subtype_test,
                                       bool include_abstract,
                                       bool exclude_null) {
   if (dst_klass.InVMIsolateHeap()) {
-    BuildRangesFor(table, ranges, dst_klass, use_subtype_test, include_abstract,
-                   exclude_null);
+    BuildRangesFor(table, ranges, dst_klass, include_abstract, exclude_null);
     return;
   }
 
   Zone* zone = thread()->zone();
   GrowableArray<intptr_t> cids;
-  SubclassFinder finder(zone, &cids, include_abstract);
+  SubtypeFinder finder(zone, &cids, include_abstract);
   {
     SafepointReadRwLocker ml(thread(),
                              thread()->isolate_group()->program_lock());
-    if (use_subtype_test) {
-      finder.ScanImplementorClasses(dst_klass);
-    } else {
-      finder.ScanSubClasses(dst_klass);
-    }
+    finder.ScanImplementorClasses(dst_klass);
   }
 
   // Sort all collected cids.
@@ -429,17 +361,6 @@
   ASSERT(type_class.NumTypeParameters() > 0 &&
          type.arguments() != TypeArguments::null());
 
-  // If the type class is implemented the different implementations might have
-  // their type argument vector stored at different offsets and we can therefore
-  // not perform our optimized [CidRange]-based implementation.
-  //
-  // TODO(kustermann): If the class is implemented but all implementations
-  // store the instantator type argument vector at the same offset we can
-  // still do it!
-  if (type_class.is_implemented()) {
-    return false;
-  }
-
   const TypeArguments& ta =
       TypeArguments::Handle(zone, Type::Cast(type).arguments());
   ASSERT(ta.Length() == num_type_arguments);
@@ -477,11 +398,10 @@
                               /*exclude_null=*/true);
     if (ranges.length() == 1) {
       const CidRangeValue& range = ranges[0];
-      if (!range.IsIllegalRange()) {
-        *lower_limit = range.cid_start;
-        *upper_limit = range.cid_end;
-        return true;
-      }
+      ASSERT(!range.IsIllegalRange());
+      *lower_limit = range.cid_start;
+      *upper_limit = range.cid_end;
+      return true;
     }
   }
   return false;
diff --git a/runtime/vm/compiler/backend/il.h b/runtime/vm/compiler/backend/il.h
index 40658e5..e4d883f 100644
--- a/runtime/vm/compiler/backend/il.h
+++ b/runtime/vm/compiler/backend/il.h
@@ -5,6 +5,7 @@
 #ifndef RUNTIME_VM_COMPILER_BACKEND_IL_H_
 #define RUNTIME_VM_COMPILER_BACKEND_IL_H_
 
+#include "vm/hash_map.h"
 #if defined(DART_PRECOMPILED_RUNTIME)
 #error "AOT runtime should not use compiler sources (including header files)"
 #endif  // defined(DART_PRECOMPILED_RUNTIME)
@@ -247,17 +248,19 @@
         cid_subtype_ranges_nullable_(),
         cid_subtype_ranges_abstract_nullable_(),
         cid_subtype_ranges_nonnullable_(),
-        cid_subtype_ranges_abstract_nonnullable_(),
-        cid_subclass_ranges_() {
+        cid_subtype_ranges_abstract_nonnullable_() {
     thread->set_hierarchy_info(this);
   }
 
   ~HierarchyInfo() { thread()->set_hierarchy_info(NULL); }
 
+  // Returned from FindBestTAVOffset and SplitOnConsistentTypeArguments
+  // to denote a failure to find a compatible concrete, finalized class.
+  static const intptr_t kNoCompatibleTAVOffset = 0;
+
   const CidRangeVector& SubtypeRangesForClass(const Class& klass,
                                               bool include_abstract,
                                               bool exclude_null);
-  const CidRangeVector& SubclassRangesForClass(const Class& klass);
 
   bool InstanceOfHasClassRange(const AbstractType& type,
                                intptr_t* lower_limit,
@@ -284,13 +287,11 @@
  private:
   // Does not use any hierarchy information available in the system but computes
   // it via O(n) class table traversal. The boolean parameters denote:
-  //   use_subtype_test : if set, IsSubtypeOf() is used to compute inclusion
   //   include_abstract : if set, include abstract types (don't care otherwise)
   //   exclude_null     : if set, exclude null types (don't care otherwise)
   void BuildRangesFor(ClassTable* table,
                       CidRangeVector* ranges,
                       const Class& klass,
-                      bool use_subtype_test,
                       bool include_abstract,
                       bool exclude_null);
 
@@ -299,7 +300,6 @@
   void BuildRangesForJIT(ClassTable* table,
                          CidRangeVector* ranges,
                          const Class& klass,
-                         bool use_subtype_test,
                          bool include_abstract,
                          bool exclude_null);
 
@@ -307,7 +307,6 @@
   std::unique_ptr<CidRangeVector[]> cid_subtype_ranges_abstract_nullable_;
   std::unique_ptr<CidRangeVector[]> cid_subtype_ranges_nonnullable_;
   std::unique_ptr<CidRangeVector[]> cid_subtype_ranges_abstract_nonnullable_;
-  std::unique_ptr<CidRangeVector[]> cid_subclass_ranges_;
 };
 
 // An embedded container with N elements of type T.  Used (with partial
diff --git a/runtime/vm/compiler/backend/type_propagator.cc b/runtime/vm/compiler/backend/type_propagator.cc
index 42a9839..7adbe17 100644
--- a/runtime/vm/compiler/backend/type_propagator.cc
+++ b/runtime/vm/compiler/backend/type_propagator.cc
@@ -256,9 +256,7 @@
   CompileType result = CompileType::None();
   for (intptr_t i = 0, n = cids.length(); i < n; i++) {
     CidRange* cid_range = cids.At(i);
-    if (cid_range->IsIllegalRange()) {
-      return;
-    }
+    ASSERT(!cid_range->IsIllegalRange());
     for (intptr_t cid = cid_range->cid_start; cid <= cid_range->cid_end;
          cid++) {
       CompileType tp = CompileType::FromCid(cid);
diff --git a/runtime/vm/compiler/compiler_sources.gni b/runtime/vm/compiler/compiler_sources.gni
index 05fd213..c63846e 100644
--- a/runtime/vm/compiler/compiler_sources.gni
+++ b/runtime/vm/compiler/compiler_sources.gni
@@ -146,9 +146,6 @@
   "stub_code_compiler_arm64.cc",
   "stub_code_compiler_ia32.cc",
   "stub_code_compiler_x64.cc",
-  "type_testing_stubs_arm.cc",
-  "type_testing_stubs_arm64.cc",
-  "type_testing_stubs_x64.cc",
   "write_barrier_elimination.cc",
   "write_barrier_elimination.h",
 ]
diff --git a/runtime/vm/compiler/type_testing_stubs_arm.cc b/runtime/vm/compiler/type_testing_stubs_arm.cc
deleted file mode 100644
index ccd9d36..0000000
--- a/runtime/vm/compiler/type_testing_stubs_arm.cc
+++ /dev/null
@@ -1,29 +0,0 @@
-// Copyright (c) 2019, 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.
-
-#include "vm/globals.h"
-
-#if defined(TARGET_ARCH_ARM) && !defined(DART_PRECOMPILED_RUNTIME)
-
-#include "vm/type_testing_stubs.h"
-
-#define __ assembler->
-
-namespace dart {
-
-void TypeTestingStubGenerator::BuildOptimizedTypeTestStub(
-    compiler::Assembler* assembler,
-    compiler::UnresolvedPcRelativeCalls* unresolved_calls,
-    const Code& slow_type_test_stub,
-    HierarchyInfo* hi,
-    const Type& type,
-    const Class& type_class) {
-  BuildOptimizedTypeTestStubFastCases(assembler, hi, type, type_class);
-  __ Branch(compiler::Address(
-      THR, compiler::target::Thread::slow_type_test_entry_point_offset()));
-}
-
-}  // namespace dart
-
-#endif  // defined(TARGET_ARCH_ARM) && !defined(DART_PRECOMPILED_RUNTIME)
diff --git a/runtime/vm/compiler/type_testing_stubs_arm64.cc b/runtime/vm/compiler/type_testing_stubs_arm64.cc
deleted file mode 100644
index 850b198..0000000
--- a/runtime/vm/compiler/type_testing_stubs_arm64.cc
+++ /dev/null
@@ -1,32 +0,0 @@
-// Copyright (c) 2019, 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.
-
-#include "vm/globals.h"
-
-#if defined(TARGET_ARCH_ARM64) && !defined(DART_PRECOMPILED_RUNTIME)
-
-#include "vm/type_testing_stubs.h"
-
-#define __ assembler->
-
-namespace dart {
-
-void TypeTestingStubGenerator::BuildOptimizedTypeTestStub(
-    compiler::Assembler* assembler,
-    compiler::UnresolvedPcRelativeCalls* unresolved_calls,
-    const Code& slow_type_test_stub,
-    HierarchyInfo* hi,
-    const Type& type,
-    const Class& type_class) {
-  BuildOptimizedTypeTestStubFastCases(assembler, hi, type, type_class);
-  __ ldr(
-      TMP,
-      compiler::Address(
-          THR, compiler::target::Thread::slow_type_test_entry_point_offset()));
-  __ br(TMP);
-}
-
-}  // namespace dart
-
-#endif  // defined(TARGET_ARCH_ARM64) && !defined(DART_PRECOMPILED_RUNTIME)
diff --git a/runtime/vm/compiler/type_testing_stubs_x64.cc b/runtime/vm/compiler/type_testing_stubs_x64.cc
deleted file mode 100644
index 7722cee..0000000
--- a/runtime/vm/compiler/type_testing_stubs_x64.cc
+++ /dev/null
@@ -1,29 +0,0 @@
-// Copyright (c) 2019, 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.
-
-#include "vm/globals.h"
-
-#if defined(TARGET_ARCH_X64) && !defined(DART_PRECOMPILED_RUNTIME)
-
-#include "vm/type_testing_stubs.h"
-
-#define __ assembler->
-
-namespace dart {
-
-void TypeTestingStubGenerator::BuildOptimizedTypeTestStub(
-    compiler::Assembler* assembler,
-    compiler::UnresolvedPcRelativeCalls* unresolved_calls,
-    const Code& slow_type_test_stub,
-    HierarchyInfo* hi,
-    const Type& type,
-    const Class& type_class) {
-  BuildOptimizedTypeTestStubFastCases(assembler, hi, type, type_class);
-  __ jmp(compiler::Address(
-      THR, compiler::target::Thread::slow_type_test_entry_point_offset()));
-}
-
-}  // namespace dart
-
-#endif  // defined(TARGET_ARCH_X64) && !defined(DART_PRECOMPILED_RUNTIME)
diff --git a/runtime/vm/ffi_callback_trampolines.cc b/runtime/vm/ffi_callback_trampolines.cc
index 25057e0..8cc1946 100644
--- a/runtime/vm/ffi_callback_trampolines.cc
+++ b/runtime/vm/ffi_callback_trampolines.cc
@@ -14,8 +14,6 @@
 
 namespace dart {
 
-DECLARE_FLAG(bool, disassemble_stubs);
-
 #if !defined(DART_PRECOMPILED_RUNTIME)
 uword NativeCallbackTrampolines::TrampolineForId(int32_t callback_id) {
 #if defined(DART_PRECOMPILER)
diff --git a/runtime/vm/flag_list.h b/runtime/vm/flag_list.h
index 543b601..1e3544d 100644
--- a/runtime/vm/flag_list.h
+++ b/runtime/vm/flag_list.h
@@ -35,6 +35,7 @@
   P(disassemble, bool, false, "Disassemble dart code.")                        \
   P(disassemble_optimized, bool, false, "Disassemble optimized code.")         \
   P(disassemble_relative, bool, false, "Use offsets instead of absolute PCs")  \
+  P(disassemble_stubs, bool, false, "Disassemble generated stubs.")            \
   P(support_disassembler, bool, true, "Support the disassembler.")
 #else
 #define DISASSEMBLE_FLAGS(P, R, C, D)                                          \
@@ -42,6 +43,7 @@
   R(disassemble_optimized, false, bool, false, "Disassemble optimized code.")  \
   R(disassemble_relative, false, bool, false,                                  \
     "Use offsets instead of absolute PCs")                                     \
+  R(disassemble_stubs, false, bool, false, "Disassemble generated stubs.")     \
   R(support_disassembler, false, bool, true, "Support the disassembler.")
 #endif
 
diff --git a/runtime/vm/object.cc b/runtime/vm/object.cc
index a716109..5c3adc4 100644
--- a/runtime/vm/object.cc
+++ b/runtime/vm/object.cc
@@ -11397,48 +11397,93 @@
   return true;
 }
 
-// Given the type G<T0, ..., Tn> and class C<U0, ..., Un> find path to C at G.
-// This path can be used to compute type arguments of C at G.
-//
-// Note: we are relying on the restriction that the same class can only occur
-// once among the supertype.
-static bool FindInstantiationOf(const Type& type,
+bool Class::FindInstantiationOf(Zone* zone,
                                 const Class& cls,
                                 GrowableArray<const AbstractType*>* path,
-                                bool consider_only_super_classes) {
-  if (type.type_class() == cls.ptr()) {
+                                bool consider_only_super_classes) const {
+  ASSERT(cls.is_type_finalized());
+  if (cls.ptr() == ptr()) {
     return true;  // Found instantiation.
   }
 
-  Class& cls2 = Class::Handle();
-  AbstractType& super_type = AbstractType::Handle();
-  super_type = cls.super_type();
-  if (!super_type.IsNull() && !super_type.IsObjectType()) {
-    cls2 = super_type.type_class();
-    path->Add(&super_type);
-    if (FindInstantiationOf(type, cls2, path, consider_only_super_classes)) {
+  Class& cls2 = Class::Handle(zone);
+  AbstractType& super = AbstractType::Handle(zone, super_type());
+  if (!super.IsNull() && !super.IsObjectType()) {
+    cls2 = super.type_class();
+    if (path != nullptr) {
+      path->Add(&super);
+    }
+    if (cls2.FindInstantiationOf(zone, cls, path,
+                                 consider_only_super_classes)) {
       return true;  // Found instantiation.
     }
-    path->RemoveLast();
-  }
-
-  if (!consider_only_super_classes) {
-    Array& super_interfaces = Array::Handle(cls.interfaces());
-    for (intptr_t i = 0; i < super_interfaces.Length(); i++) {
-      super_type ^= super_interfaces.At(i);
-      cls2 = super_type.type_class();
-      path->Add(&super_type);
-      if (FindInstantiationOf(type, cls2, path,
-                              /*consider_only_supertypes=*/false)) {
-        return true;  // Found instantiation.
-      }
+    if (path != nullptr) {
       path->RemoveLast();
     }
   }
 
+  if (!consider_only_super_classes) {
+    Array& super_interfaces = Array::Handle(zone, interfaces());
+    for (intptr_t i = 0; i < super_interfaces.Length(); i++) {
+      super ^= super_interfaces.At(i);
+      cls2 = super.type_class();
+      if (path != nullptr) {
+        path->Add(&super);
+      }
+      if (cls2.FindInstantiationOf(zone, cls, path)) {
+        return true;  // Found instantiation.
+      }
+      if (path != nullptr) {
+        path->RemoveLast();
+      }
+    }
+  }
+
   return false;  // Not found.
 }
 
+bool Class::FindInstantiationOf(Zone* zone,
+                                const Type& type,
+                                GrowableArray<const AbstractType*>* path,
+                                bool consider_only_super_classes) const {
+  return FindInstantiationOf(zone, Class::Handle(zone, type.type_class()), path,
+                             consider_only_super_classes);
+}
+
+TypePtr Class::GetInstantiationOf(Zone* zone, const Class& cls) const {
+  if (ptr() == cls.ptr()) {
+    return DeclarationType();
+  }
+  if (FindInstantiationOf(zone, cls, /*consider_only_super_classes=*/true)) {
+    // Since [cls] is a superclass of [this], use [cls]'s declaration type.
+    return cls.DeclarationType();
+  }
+  const auto& decl_type = Type::Handle(zone, DeclarationType());
+  GrowableArray<const AbstractType*> path(zone, 0);
+  if (!FindInstantiationOf(zone, cls, &path)) {
+    return Type::null();
+  }
+  ASSERT(!path.is_empty());
+  auto& calculated_type = Type::Handle(zone, decl_type.ptr());
+  auto& calculated_type_args =
+      TypeArguments::Handle(zone, calculated_type.arguments());
+  for (auto* const type : path) {
+    calculated_type ^= type->ptr();
+    if (!calculated_type.IsInstantiated()) {
+      calculated_type ^= calculated_type.InstantiateFrom(
+          calculated_type_args, Object::null_type_arguments(), kAllFree,
+          Heap::kNew);
+    }
+    calculated_type_args = calculated_type.arguments();
+  }
+  ASSERT_EQUAL(calculated_type.type_class_id(), cls.id());
+  return calculated_type.ptr();
+}
+
+TypePtr Class::GetInstantiationOf(Zone* zone, const Type& type) const {
+  return GetInstantiationOf(zone, Class::Handle(zone, type.type_class()));
+}
+
 void Field::SetStaticValue(const Object& value) const {
   auto thread = Thread::Current();
   ASSERT(thread->IsMutatorThread());
@@ -11476,21 +11521,22 @@
   ASSERT(value.ptr() != Object::sentinel().ptr());
   ASSERT(value.ptr() != Object::transition_sentinel().ptr());
 
+  Zone* const zone = Thread::Current()->zone();
   const TypeArguments& static_type_args =
-      TypeArguments::Handle(static_type.arguments());
+      TypeArguments::Handle(zone, static_type.arguments());
 
-  TypeArguments& args = TypeArguments::Handle();
+  TypeArguments& args = TypeArguments::Handle(zone);
 
   ASSERT(static_type.IsFinalized());
-  const Class& cls = Class::Handle(value.clazz());
+  const Class& cls = Class::Handle(zone, value.clazz());
   GrowableArray<const AbstractType*> path(10);
 
   bool is_super_class = true;
-  if (!FindInstantiationOf(static_type, cls, &path,
-                           /*consider_only_super_classes=*/true)) {
+  if (!cls.FindInstantiationOf(zone, static_type, &path,
+                               /*consider_only_super_classes=*/true)) {
     is_super_class = false;
-    bool found_super_interface = FindInstantiationOf(
-        static_type, cls, &path, /*consider_only_super_classes=*/false);
+    bool found_super_interface =
+        cls.FindInstantiationOf(zone, static_type, &path);
     ASSERT(found_super_interface);
   }
 
@@ -11522,7 +11568,7 @@
   // To compute C<X0, ..., Xn> at G we walk the chain backwards and
   // instantiate Si using type parameters of S{i-1} which gives us a type
   // depending on type parameters of S{i-2}.
-  AbstractType& type = AbstractType::Handle(path.Last()->ptr());
+  AbstractType& type = AbstractType::Handle(zone, path.Last()->ptr());
   for (intptr_t i = path.length() - 2; (i >= 0) && !type.IsInstantiated();
        i--) {
     args = path[i]->arguments();
@@ -11560,19 +11606,19 @@
   const intptr_t num_type_params = cls.NumTypeParameters();
   bool trivial_case =
       (num_type_params ==
-       Class::Handle(static_type.type_class()).NumTypeParameters()) &&
+       Class::Handle(zone, static_type.type_class()).NumTypeParameters()) &&
       (value.GetTypeArguments() == static_type.arguments());
   if (!trivial_case && FLAG_trace_field_guards) {
     THR_Print("Not a simple case: %" Pd " vs %" Pd
               " type parameters, %s vs %s type arguments\n",
               num_type_params,
-              Class::Handle(static_type.type_class()).NumTypeParameters(),
+              Class::Handle(zone, static_type.type_class()).NumTypeParameters(),
               SafeTypeArgumentsToCString(
-                  TypeArguments::Handle(value.GetTypeArguments())),
+                  TypeArguments::Handle(zone, value.GetTypeArguments())),
               SafeTypeArgumentsToCString(static_type_args));
   }
 
-  AbstractType& type_arg = AbstractType::Handle();
+  AbstractType& type_arg = AbstractType::Handle(zone);
   args = type.arguments();
   for (intptr_t i = 0; (i < num_type_params) && trivial_case; i++) {
     type_arg = args.TypeAt(i);
diff --git a/runtime/vm/object.h b/runtime/vm/object.h
index 112efdd..3b9d8ec 100644
--- a/runtime/vm/object.h
+++ b/runtime/vm/object.h
@@ -1270,6 +1270,71 @@
   }
   void set_interfaces(const Array& value) const;
 
+  // Returns whether a path from [this] to [cls] can be found, where the first
+  // element is a direct supertype of [this], each following element is a direct
+  // supertype of the previous element and the final element has [cls] as its
+  // type class. If [this] and [cls] are the same class, then the path is empty.
+  //
+  // If [path] is not nullptr, then the elements of the path are added to it.
+  // This path can then be used to compute type arguments of [cls] given type
+  // arguments for an instance of [this].
+  //
+  // Note: There may be multiple paths to [cls], but the result of applying each
+  // path must be equal to the other results.
+  bool FindInstantiationOf(Zone* zone,
+                           const Class& cls,
+                           GrowableArray<const AbstractType*>* path,
+                           bool consider_only_super_classes = false) const;
+  bool FindInstantiationOf(Zone* zone,
+                           const Class& cls,
+                           bool consider_only_super_classes = false) const {
+    return FindInstantiationOf(zone, cls, /*path=*/nullptr,
+                               consider_only_super_classes);
+  }
+
+  // Returns whether a path from [this] to [type] can be found, where the first
+  // element is a direct supertype of [this], each following element is a direct
+  // supertype of the previous element and the final element has the same type
+  // class as [type]. If [this] is the type class of [type], then the path is
+  // empty.
+  //
+  // If [path] is not nullptr, then the elements of the path are added to it.
+  // This path can then be used to compute type arguments of [type]'s type
+  // class given type arguments for an instance of [this].
+  //
+  // Note: There may be multiple paths to [type]'s type class, but the result of
+  // applying each path must be equal to the other results.
+  bool FindInstantiationOf(Zone* zone,
+                           const Type& type,
+                           GrowableArray<const AbstractType*>* path,
+                           bool consider_only_super_classes = false) const;
+  bool FindInstantiationOf(Zone* zone,
+                           const Type& type,
+                           bool consider_only_super_classes = false) const {
+    return FindInstantiationOf(zone, type, /*path=*/nullptr,
+                               consider_only_super_classes);
+  }
+
+  // If [this] is a subtype of a type with type class [cls], then this
+  // returns [cls]<X_0, ..., X_n>, where n is the number of type arguments for
+  // [cls] and where each type argument X_k is either instantiated or has free
+  // class type parameters corresponding to the type parameters of [this].
+  // Thus, given an instance of [this], the result can be instantiated
+  // with the instance type arguments to get the type of the instance.
+  //
+  // If [this] is not a subtype of a type with type class [cls], returns null.
+  TypePtr GetInstantiationOf(Zone* zone, const Class& cls) const;
+
+  // If [this] is a subtype of [type], then this returns [cls]<X_0, ..., X_n>,
+  // where [cls] is the type class of [type], n is the number of type arguments
+  // for [cls], and where each type argument X_k is either instantiated or has
+  // free class type parameters corresponding to the type parameters of [this].
+  // Thus, given an instance of [this], the result can be instantiated with the
+  // instance type arguments to get the type of the instance.
+  //
+  // If [this] is not a subtype of a type with type class [cls], returns null.
+  TypePtr GetInstantiationOf(Zone* zone, const Type& type) const;
+
 #if !defined(PRODUCT) || !defined(DART_PRECOMPILED_RUNTIME)
   // Returns the list of classes directly implementing this class.
   GrowableObjectArrayPtr direct_implementors() const {
diff --git a/runtime/vm/object_test.cc b/runtime/vm/object_test.cc
index 93cfc12..a2e7385 100644
--- a/runtime/vm/object_test.cc
+++ b/runtime/vm/object_test.cc
@@ -5304,4 +5304,97 @@
   EXPECT(!m.IsSubtypeOf(t, Heap::kNew));
 }
 
+TEST_CASE(Class_GetInstantiationOf) {
+  const char* kScript = R"(
+    class B<T> {}
+    class A1<X, Y> implements B<List<Y>> {}
+    class A2<X, Y> extends A1<Y, X> {}
+  )";
+  Dart_Handle api_lib = TestCase::LoadTestScript(kScript, nullptr);
+  EXPECT_VALID(api_lib);
+  TransitionNativeToVM transition(thread);
+  Zone* const zone = thread->zone();
+
+  const auto& root_lib =
+      Library::CheckedHandle(zone, Api::UnwrapHandle(api_lib));
+  EXPECT(!root_lib.IsNull());
+  const auto& class_b = Class::Handle(zone, GetClass(root_lib, "B"));
+  const auto& class_a1 = Class::Handle(zone, GetClass(root_lib, "A1"));
+  const auto& class_a2 = Class::Handle(zone, GetClass(root_lib, "A2"));
+
+  const auto& core_lib = Library::Handle(zone, Library::CoreLibrary());
+  const auto& class_list = Class::Handle(zone, GetClass(core_lib, "List"));
+
+  auto expect_type_equal = [](const AbstractType& expected,
+                              const AbstractType& got) {
+    if (got.Equals(expected)) return;
+    TextBuffer buffer(128);
+    buffer.AddString("Expected type ");
+    expected.PrintName(Object::kScrubbedName, &buffer);
+    buffer.AddString(", got ");
+    got.PrintName(Object::kScrubbedName, &buffer);
+    dart::Expect(__FILE__, __LINE__).Fail("%s", buffer.buffer());
+  };
+
+  const auto& decl_type_b = Type::Handle(zone, class_b.DeclarationType());
+  const auto& decl_type_list = Type::Handle(zone, class_list.DeclarationType());
+  const auto& null_tav = Object::null_type_arguments();
+
+  // Test that A1.GetInstantiationOf(B) returns B<List<A1::Y>>.
+  {
+    const auto& decl_type_a1 = Type::Handle(zone, class_a1.DeclarationType());
+    const auto& decl_type_args_a1 =
+        TypeArguments::Handle(zone, decl_type_a1.arguments());
+    const auto& type_arg_a1_y =
+        TypeParameter::CheckedHandle(zone, decl_type_args_a1.TypeAt(1));
+    auto& tav_a1_y = TypeArguments::Handle(TypeArguments::New(1));
+    tav_a1_y.SetTypeAt(0, type_arg_a1_y);
+    tav_a1_y = tav_a1_y.Canonicalize(thread, nullptr);
+    auto& type_list_a1_y = Type::CheckedHandle(
+        zone, decl_type_list.InstantiateFrom(tav_a1_y, null_tav, kAllFree,
+                                             Heap::kNew));
+    type_list_a1_y ^= type_list_a1_y.Canonicalize(thread, nullptr);
+    auto& tav_list_a1_y = TypeArguments::Handle(TypeArguments::New(1));
+    tav_list_a1_y.SetTypeAt(0, type_list_a1_y);
+    tav_list_a1_y = tav_list_a1_y.Canonicalize(thread, nullptr);
+    auto& type_b_list_a1_y = Type::CheckedHandle(
+        zone, decl_type_b.InstantiateFrom(tav_list_a1_y, null_tav, kAllFree,
+                                          Heap::kNew));
+    type_b_list_a1_y ^= type_b_list_a1_y.Canonicalize(thread, nullptr);
+
+    const auto& inst_b_a1 =
+        Type::Handle(zone, class_a1.GetInstantiationOf(zone, class_b));
+    EXPECT(!inst_b_a1.IsNull());
+    expect_type_equal(type_b_list_a1_y, inst_b_a1);
+  }
+
+  // Test that A2.GetInstantiationOf(B) returns B<List<A2::X>>.
+  {
+    const auto& decl_type_a2 = Type::Handle(zone, class_a2.DeclarationType());
+    const auto& decl_type_args_a2 =
+        TypeArguments::Handle(zone, decl_type_a2.arguments());
+    const auto& type_arg_a2_x =
+        TypeParameter::CheckedHandle(zone, decl_type_args_a2.TypeAt(1));
+    auto& tav_a2_x = TypeArguments::Handle(TypeArguments::New(1));
+    tav_a2_x.SetTypeAt(0, type_arg_a2_x);
+    tav_a2_x = tav_a2_x.Canonicalize(thread, nullptr);
+    auto& type_list_a2_x = Type::CheckedHandle(
+        zone, decl_type_list.InstantiateFrom(tav_a2_x, null_tav, kAllFree,
+                                             Heap::kNew));
+    type_list_a2_x ^= type_list_a2_x.Canonicalize(thread, nullptr);
+    auto& tav_list_a2_x = TypeArguments::Handle(TypeArguments::New(1));
+    tav_list_a2_x.SetTypeAt(0, type_list_a2_x);
+    tav_list_a2_x = tav_list_a2_x.Canonicalize(thread, nullptr);
+    auto& type_b_list_a2_x = Type::CheckedHandle(
+        zone, decl_type_b.InstantiateFrom(tav_list_a2_x, null_tav, kAllFree,
+                                          Heap::kNew));
+    type_b_list_a2_x ^= type_b_list_a2_x.Canonicalize(thread, nullptr);
+
+    const auto& inst_b_a2 =
+        Type::Handle(zone, class_a2.GetInstantiationOf(zone, class_b));
+    EXPECT(!inst_b_a2.IsNull());
+    expect_type_equal(type_b_list_a2_x, inst_b_a2);
+  }
+}
+
 }  // namespace dart
diff --git a/runtime/vm/signal_handler.h b/runtime/vm/signal_handler.h
index 22821d3..cabe149 100644
--- a/runtime/vm/signal_handler.h
+++ b/runtime/vm/signal_handler.h
@@ -38,84 +38,19 @@
 #include <ucontext.h>  // NOLINT
 #endif
 
-// Old linux kernels on ARM might require a trampoline to
-// work around incorrect Thumb -> ARM transitions. See SignalHandlerTrampoline
-// below for more details.
-#if defined(HOST_ARCH_ARM) &&                                                  \
-    (defined(DART_HOST_OS_LINUX) || defined(DART_HOST_OS_ANDROID)) &&          \
-    !defined(__thumb__)
-#define USE_SIGNAL_HANDLER_TRAMPOLINE
-#endif
-
 namespace dart {
 
 typedef void (*SignalAction)(int signal, siginfo_t* info, void* context);
 
 class SignalHandler : public AllStatic {
  public:
-  template <SignalAction action>
-  static void Install() {
-#if defined(USE_SIGNAL_HANDLER_TRAMPOLINE)
-    InstallImpl(SignalHandlerTrampoline<action>);
-#else
-    InstallImpl(action);
-#endif  // defined(USE_SIGNAL_HANDLER_TRAMPOLINE)
-  }
+  static void Install(SignalAction action);
   static void Remove();
   static uintptr_t GetProgramCounter(const mcontext_t& mcontext);
   static uintptr_t GetFramePointer(const mcontext_t& mcontext);
   static uintptr_t GetCStackPointer(const mcontext_t& mcontext);
   static uintptr_t GetDartStackPointer(const mcontext_t& mcontext);
   static uintptr_t GetLinkRegister(const mcontext_t& mcontext);
-
- private:
-  static void InstallImpl(SignalAction action);
-
-#if defined(USE_SIGNAL_HANDLER_TRAMPOLINE)
-  // Work around for a bug in old kernels (only fixed in 3.18 Android kernel):
-  //
-  // Kernel does not clear If-Then execution state bits when entering ARM signal
-  // handler which violates requirements imposed by ARM architecture reference.
-  // Some CPUs look at these bits even while in ARM mode which causes them
-  // to skip some instructions in the prologue of the signal handler.
-  //
-  // To work around the issue we insert enough NOPs in the prologue to ensure
-  // that no actual instructions are skipped and then branch to the actual
-  // signal handler.
-  //
-  // For the kernel patch that fixes the issue see:
-  // http://git.kernel.org/cgit/linux/kernel/git/torvalds/linux.git/commit/?id=6ecf830e5029598732e04067e325d946097519cb
-  //
-  // Note: this function is marked "naked" because we must guarantee that
-  // our NOPs occur before any compiler generated prologue.
-  template <SignalAction action>
-  static __attribute__((naked)) void SignalHandlerTrampoline(int signal,
-                                                             siginfo_t* info,
-                                                             void* context_) {
-    // IT (If-Then) instruction makes up to four instructions that follow it
-    // conditional.
-    // Note: clobber all register so that compiler does not attempt to hoist
-    // anything from the next assembly block past this one.
-    asm volatile("nop; nop; nop; nop;"
-                 :
-                 :
-                 : "r0", "r1", "r2", "r3", "r4", "r5", "r6", "r7", "r8", "r9",
-                   "r10", "r11", "r12", "r13", "r14", "memory");
-
-    // Tail-call into the actual signal handler.
-    //
-    // Note: this code is split into a separate inline assembly block because
-    // any code that compiler generates to satisfy register constraints must
-    // be generated after four NOPs.
-    //
-    // Note: there is no portable way to specify that we want to have
-    // signal, info and context_ in r0 - r2 respectively. So we just mark them
-    // as clobbered and hope that compiler does not emit any code that uses
-    // these registers to satisfy action constraint (we tested on clang and
-    // the generated code looks like one would expect).
-    asm volatile("bx %0;" : : "r"(action) : "r0", "r1", "r2", "memory");
-  }
-#endif  // defined(USE_SIGNAL_HANDLER_TRAMPOLINE)
 };
 
 #undef USE_SIGNAL_HANDLER_TRAMPOLINE
diff --git a/runtime/vm/signal_handler_android.cc b/runtime/vm/signal_handler_android.cc
index 47c2ded..2fba54a 100644
--- a/runtime/vm/signal_handler_android.cc
+++ b/runtime/vm/signal_handler_android.cc
@@ -94,7 +94,7 @@
   return lr;
 }
 
-void SignalHandler::InstallImpl(SignalAction action) {
+void SignalHandler::Install(SignalAction action) {
   // Bionic implementation of setjmp temporary mangles SP register
   // in place which breaks signal delivery on the thread stack - when
   // kernel tries to deliver SIGPROF and we are in the middle of
diff --git a/runtime/vm/signal_handler_fuchsia.cc b/runtime/vm/signal_handler_fuchsia.cc
index 3a2f161..9e1ddee 100644
--- a/runtime/vm/signal_handler_fuchsia.cc
+++ b/runtime/vm/signal_handler_fuchsia.cc
@@ -36,7 +36,7 @@
   return 0;
 }
 
-void SignalHandler::InstallImpl(SignalAction action) {
+void SignalHandler::Install(SignalAction action) {
   UNIMPLEMENTED();
 }
 
diff --git a/runtime/vm/signal_handler_linux.cc b/runtime/vm/signal_handler_linux.cc
index ae75730..1979da4 100644
--- a/runtime/vm/signal_handler_linux.cc
+++ b/runtime/vm/signal_handler_linux.cc
@@ -94,7 +94,7 @@
   return lr;
 }
 
-void SignalHandler::InstallImpl(SignalAction action) {
+void SignalHandler::Install(SignalAction action) {
   struct sigaction act = {};
   act.sa_handler = NULL;
   act.sa_sigaction = action;
diff --git a/runtime/vm/signal_handler_macos.cc b/runtime/vm/signal_handler_macos.cc
index 71ad84f..70e5319 100644
--- a/runtime/vm/signal_handler_macos.cc
+++ b/runtime/vm/signal_handler_macos.cc
@@ -90,7 +90,7 @@
   return lr;
 }
 
-void SignalHandler::InstallImpl(SignalAction action) {
+void SignalHandler::Install(SignalAction action) {
   struct sigaction act = {};
   act.sa_handler = NULL;
   act.sa_sigaction = action;
diff --git a/runtime/vm/signal_handler_win.cc b/runtime/vm/signal_handler_win.cc
index 254164c..4463eb9 100644
--- a/runtime/vm/signal_handler_win.cc
+++ b/runtime/vm/signal_handler_win.cc
@@ -33,7 +33,7 @@
   return 0;
 }
 
-void SignalHandler::InstallImpl(SignalAction action) {
+void SignalHandler::Install(SignalAction action) {
   UNIMPLEMENTED();
 }
 
diff --git a/runtime/vm/stub_code.cc b/runtime/vm/stub_code.cc
index 8a54e72..90576ec 100644
--- a/runtime/vm/stub_code.cc
+++ b/runtime/vm/stub_code.cc
@@ -22,7 +22,6 @@
 
 namespace dart {
 
-DEFINE_FLAG(bool, disassemble_stubs, false, "Disassemble generated stubs.");
 DECLARE_FLAG(bool, precompiled_mode);
 
 StubCode::StubCodeEntry StubCode::entries_[kNumStubEntries] = {
diff --git a/runtime/vm/stub_code.h b/runtime/vm/stub_code.h
index 3eac0b6..66d5212e 100644
--- a/runtime/vm/stub_code.h
+++ b/runtime/vm/stub_code.h
@@ -22,8 +22,6 @@
 class Isolate;
 class ObjectPointerVisitor;
 
-DECLARE_FLAG(bool, disassemble_stubs);
-
 // Is it permitted for the stubs above to refer to Object::null(), which is
 // allocated in the VM isolate and shared across all isolates.
 // However, in cases where a simple GC-safe placeholder is needed on the stack,
diff --git a/runtime/vm/thread_interrupter_android.cc b/runtime/vm/thread_interrupter_android.cc
index 367fa41..dbdd331 100644
--- a/runtime/vm/thread_interrupter_android.cc
+++ b/runtime/vm/thread_interrupter_android.cc
@@ -18,36 +18,48 @@
 
 #ifndef PRODUCT
 
+// Old linux kernels on ARM might require a trampoline to
+// work around incorrect Thumb -> ARM transitions.
+// See thread_interrupted_android_arm.S for more details.
+#if defined(HOST_ARCH_ARM) &&                                                  \
+    (defined(DART_HOST_OS_LINUX) || defined(DART_HOST_OS_ANDROID)) &&          \
+    !defined(__thumb__)
+#define USE_SIGNAL_HANDLER_TRAMPOLINE
+#endif
+
 DECLARE_FLAG(bool, trace_thread_interrupter);
 
-class ThreadInterrupterAndroid : public AllStatic {
- public:
-  static void ThreadInterruptSignalHandler(int signal,
-                                           siginfo_t* info,
-                                           void* context_) {
-    if (signal != SIGPROF) {
-      return;
-    }
-    Thread* thread = Thread::Current();
-    if (thread == NULL) {
-      return;
-    }
-    ThreadInterrupter::SampleBufferWriterScope scope;
-    if (!scope.CanSample()) {
-      return;
-    }
-    // Extract thread state.
-    ucontext_t* context = reinterpret_cast<ucontext_t*>(context_);
-    mcontext_t mcontext = context->uc_mcontext;
-    InterruptedThreadState its;
-    its.pc = SignalHandler::GetProgramCounter(mcontext);
-    its.fp = SignalHandler::GetFramePointer(mcontext);
-    its.csp = SignalHandler::GetCStackPointer(mcontext);
-    its.dsp = SignalHandler::GetDartStackPointer(mcontext);
-    its.lr = SignalHandler::GetLinkRegister(mcontext);
-    Profiler::SampleThread(thread, its);
+namespace {
+#if defined(USE_SIGNAL_HANDLER_TRAMPOLINE)
+extern "C" {
+#endif
+void ThreadInterruptSignalHandler(int signal, siginfo_t* info, void* context_) {
+  if (signal != SIGPROF) {
+    return;
   }
-};
+  Thread* thread = Thread::Current();
+  if (thread == NULL) {
+    return;
+  }
+  ThreadInterrupter::SampleBufferWriterScope scope;
+  if (!scope.CanSample()) {
+    return;
+  }
+  // Extract thread state.
+  ucontext_t* context = reinterpret_cast<ucontext_t*>(context_);
+  mcontext_t mcontext = context->uc_mcontext;
+  InterruptedThreadState its;
+  its.pc = SignalHandler::GetProgramCounter(mcontext);
+  its.fp = SignalHandler::GetFramePointer(mcontext);
+  its.csp = SignalHandler::GetCStackPointer(mcontext);
+  its.dsp = SignalHandler::GetDartStackPointer(mcontext);
+  its.lr = SignalHandler::GetLinkRegister(mcontext);
+  Profiler::SampleThread(thread, its);
+}
+#if defined(USE_SIGNAL_HANDLER_TRAMPOLINE)
+}  // extern "C"
+#endif
+}  // namespace
 
 bool ThreadInterrupter::IsDebuggerAttached() {
   return false;
@@ -62,9 +74,19 @@
   ASSERT((result == 0) || (result == ESRCH));
 }
 
+#if defined(USE_SIGNAL_HANDLER_TRAMPOLINE)
+// Defined in thread_interrupted_android_arm.S
+extern "C" void ThreadInterruptSignalHandlerTrampoline(int signal,
+                                                       siginfo_t* info,
+                                                       void* context_);
+#endif
+
 void ThreadInterrupter::InstallSignalHandler() {
-  SignalHandler::Install<
-      ThreadInterrupterAndroid::ThreadInterruptSignalHandler>();
+#if defined(USE_SIGNAL_HANDLER_TRAMPOLINE)
+  SignalHandler::Install(&ThreadInterruptSignalHandlerTrampoline);
+#else
+  SignalHandler::Install(&ThreadInterruptSignalHandler);
+#endif
 }
 
 void ThreadInterrupter::RemoveSignalHandler() {
diff --git a/runtime/vm/thread_interrupter_android_arm.S b/runtime/vm/thread_interrupter_android_arm.S
new file mode 100644
index 0000000..7c4a200
--- /dev/null
+++ b/runtime/vm/thread_interrupter_android_arm.S
@@ -0,0 +1,43 @@
+// Copyright (c) 2021, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+// Work around for a bug in old kernels (only fixed in 3.18 Android kernel):
+//
+// Kernel does not clear If-Then execution state bits when entering ARM signal
+// handler which violates requirements imposed by ARM architecture reference.
+// Some CPUs look at these bits even while in ARM mode which causes them
+// to skip some instructions in the prologue of the signal handler.
+//
+// To work around the issue we insert enough NOPs in the prologue to ensure
+// that no actual instructions are skipped and then branch to the actual
+// signal handler.
+//
+// For the kernel patch that fixes the issue see:
+// http://git.kernel.org/cgit/linux/kernel/git/torvalds/linux.git/commit/?id=6ecf830e5029598732e04067e325d946097519cb
+//
+
+// Note: can't use DART_* defines here because this file does not include
+// globals.h.
+#if !defined(PRODUCT) && defined(__ARMEL__) && defined(__ANDROID__) &&         \
+    !defined(__thumb__)
+
+.text
+
+.globl ThreadInterruptSignalHandlerTrampoline
+.hidden ThreadInterruptSignalHandlerTrampoline
+.type ThreadInterruptSignalHandlerTrampoline, %function
+ThreadInterruptSignalHandlerTrampoline:
+  // IT (If-Then) instruction makes up to four instructions that follow it
+  // conditional.
+  nop
+  nop
+  nop
+  nop
+  // Tail-call the actual handler.
+  // Note: no need to use interworking because we know that we are not
+  // compiling for Thumb.
+  b ThreadInterruptSignalHandler
+.size ThreadInterruptSignalHandlerTrampoline,.-ThreadInterruptSignalHandlerTrampoline
+
+#endif
diff --git a/runtime/vm/thread_interrupter_linux.cc b/runtime/vm/thread_interrupter_linux.cc
index 300a3dd..ee623db 100644
--- a/runtime/vm/thread_interrupter_linux.cc
+++ b/runtime/vm/thread_interrupter_linux.cc
@@ -62,8 +62,7 @@
 }
 
 void ThreadInterrupter::InstallSignalHandler() {
-  SignalHandler::Install<
-      ThreadInterrupterLinux::ThreadInterruptSignalHandler>();
+  SignalHandler::Install(&ThreadInterrupterLinux::ThreadInterruptSignalHandler);
 }
 
 void ThreadInterrupter::RemoveSignalHandler() {
diff --git a/runtime/vm/thread_interrupter_macos.cc b/runtime/vm/thread_interrupter_macos.cc
index bc81f93..ba9ce5a 100644
--- a/runtime/vm/thread_interrupter_macos.cc
+++ b/runtime/vm/thread_interrupter_macos.cc
@@ -83,8 +83,7 @@
 }
 
 void ThreadInterrupter::InstallSignalHandler() {
-  SignalHandler::Install<
-      ThreadInterrupterMacOS::ThreadInterruptSignalHandler>();
+  SignalHandler::Install(&ThreadInterrupterMacOS::ThreadInterruptSignalHandler);
 }
 
 void ThreadInterrupter::RemoveSignalHandler() {
diff --git a/runtime/vm/type_testing_stubs.cc b/runtime/vm/type_testing_stubs.cc
index 48c073e..47598e4 100644
--- a/runtime/vm/type_testing_stubs.cc
+++ b/runtime/vm/type_testing_stubs.cc
@@ -5,6 +5,7 @@
 #include <functional>
 
 #include "vm/compiler/assembler/disassembler.h"
+#include "vm/hash_map.h"
 #include "vm/longjump.h"
 #include "vm/object_store.h"
 #include "vm/stub_code.h"
@@ -20,8 +21,6 @@
 
 namespace dart {
 
-DECLARE_FLAG(bool, disassemble_stubs);
-
 TypeTestingStubNamer::TypeTestingStubNamer()
     : lib_(Library::Handle()),
       klass_(Class::Handle()),
@@ -308,6 +307,18 @@
   return code.ptr();
 }
 
+void TypeTestingStubGenerator::BuildOptimizedTypeTestStub(
+    compiler::Assembler* assembler,
+    compiler::UnresolvedPcRelativeCalls* unresolved_calls,
+    const Code& slow_type_test_stub,
+    HierarchyInfo* hi,
+    const Type& type,
+    const Class& type_class) {
+  BuildOptimizedTypeTestStubFastCases(assembler, hi, type, type_class);
+  __ Jump(compiler::Address(
+      THR, compiler::target::Thread::slow_type_test_entry_point_offset()));
+}
+
 void TypeTestingStubGenerator::BuildOptimizedTypeTestStubFastCases(
     compiler::Assembler* assembler,
     HierarchyInfo* hi,
@@ -316,21 +327,24 @@
   // These are handled via the TopTypeTypeTestStub!
   ASSERT(!type.IsTopTypeForSubtyping());
 
+  if (type.IsObjectType()) {
+    ASSERT(type.IsNonNullable() &&
+           IsolateGroup::Current()->use_strict_null_safety_checks());
+    compiler::Label is_null;
+    __ CompareObject(TypeTestABI::kInstanceReg, Object::null_object());
+    __ BranchIf(EQUAL, &is_null, compiler::Assembler::kNearJump);
+    __ Ret();
+    __ Bind(&is_null);
+    return;  // No further checks needed.
+  }
+
   // Fast case for 'int' and '_Smi' (which can appear in core libraries).
   if (type.IsIntType() || type.IsSmiType()) {
     compiler::Label non_smi_value;
-    __ BranchIfNotSmi(TypeTestABI::kInstanceReg, &non_smi_value);
+    __ BranchIfNotSmi(TypeTestABI::kInstanceReg, &non_smi_value,
+                      compiler::Assembler::kNearJump);
     __ Ret();
     __ Bind(&non_smi_value);
-  } else if (type.IsObjectType()) {
-    ASSERT(type.IsNonNullable() &&
-           IsolateGroup::Current()->use_strict_null_safety_checks());
-    compiler::Label continue_checking;
-    __ CompareObject(TypeTestABI::kInstanceReg, Object::null_object());
-    __ BranchIf(EQUAL, &continue_checking);
-    __ Ret();
-    __ Bind(&continue_checking);
-
   } else {
     // TODO(kustermann): Make more fast cases, e.g. Type::Number()
     // is implemented by Smi.
@@ -343,52 +357,226 @@
         /*include_abstract=*/false,
         /*exclude_null=*/!Instance::NullIsAssignableTo(type));
 
-    const Type& smi_type = Type::Handle(Type::SmiType());
-    const bool smi_is_ok = smi_type.IsSubtypeOf(type, Heap::kNew);
-
-    BuildOptimizedSubtypeRangeCheck(assembler, ranges, smi_is_ok);
+    compiler::Label is_subtype, is_not_subtype;
+    const bool smi_is_ok =
+        Type::Handle(Type::SmiType()).IsSubtypeOf(type, Heap::kNew);
+    if (smi_is_ok) {
+      __ LoadClassIdMayBeSmi(TTSInternalRegs::kScratchReg,
+                             TypeTestABI::kInstanceReg);
+    } else {
+      __ BranchIfSmi(TypeTestABI::kInstanceReg, &is_not_subtype);
+      __ LoadClassId(TTSInternalRegs::kScratchReg, TypeTestABI::kInstanceReg);
+    }
+    BuildOptimizedSubtypeRangeCheck(assembler, ranges,
+                                    TTSInternalRegs::kScratchReg, &is_subtype,
+                                    &is_not_subtype);
+    __ Bind(&is_subtype);
+    __ Ret();
+    __ Bind(&is_not_subtype);
   } else {
-    ASSERT(hi->CanUseGenericSubtypeRangeCheckFor(type));
-
-    const intptr_t num_type_arguments = type_class.NumTypeArguments();
-
-    const TypeArguments& ta = TypeArguments::Handle(type.arguments());
-    ASSERT(ta.Length() == num_type_arguments);
-
     BuildOptimizedSubclassRangeCheckWithTypeArguments(assembler, hi, type,
-                                                      type_class, ta);
+                                                      type_class);
   }
 
   if (Instance::NullIsAssignableTo(type)) {
     // Fast case for 'null'.
     compiler::Label non_null;
     __ CompareObject(TypeTestABI::kInstanceReg, Object::null_object());
-    __ BranchIf(NOT_EQUAL, &non_null);
+    __ BranchIf(NOT_EQUAL, &non_null, compiler::Assembler::kNearJump);
     __ Ret();
     __ Bind(&non_null);
   }
 }
 
+static void CommentCheckedClasses(compiler::Assembler* assembler,
+                                  const CidRangeVector& ranges) {
+  if (!assembler->EmittingComments()) return;
+  Thread* const thread = Thread::Current();
+  ClassTable* const class_table = thread->isolate_group()->class_table();
+  Zone* const zone = thread->zone();
+  if (ranges.is_empty()) {
+    __ Comment("No valid cids to check");
+    return;
+  }
+  if ((ranges.length() == 1) && ranges[0].IsSingleCid()) {
+    const auto& cls = Class::Handle(zone, class_table->At(ranges[0].cid_start));
+    __ Comment("Checking for cid %" Pd " (%s)", cls.id(),
+               cls.ScrubbedNameCString());
+    return;
+  }
+  __ Comment("Checking for concrete finalized classes:");
+  auto& cls = Class::Handle(zone);
+  for (const auto& range : ranges) {
+    ASSERT(!range.IsIllegalRange());
+    for (classid_t cid = range.cid_start; cid <= range.cid_end; cid++) {
+      // Invalid entries can be included to keep range count low.
+      if (!class_table->HasValidClassAt(cid)) continue;
+      cls = class_table->At(cid);
+      if (cls.is_abstract()) continue;  // Only output concrete classes.
+      __ Comment(" * %" Pd32 " (%s)", cid, cls.ScrubbedNameCString());
+    }
+  }
+}
+
+// Represents the following needs for runtime checks to see if an instance of
+// [cls] is a subtype of [type] that has type class [type_class]:
+//
+// * kCannotBeChecked: Instances of [cls] cannot be checked with any of the
+//   currently implemented runtime checks, so must fall back on the runtime.
+//
+// * kNotSubtype: A [cls] instance is guaranteed to not be a subtype of [type]
+//   regardless of any instance type arguments.
+//
+// * kCidCheckOnly: A [cls] instance is guaranteed to be a subtype of [type]
+//   regardless of any instance type arguments.
+//
+// * kNeedsFinalization: Checking that an instance of [cls] is a subtype of
+//   [type] requires instance type arguments, but [cls] is not finalized, and
+//   so the appropriate type arguments field offset cannot be determined.
+//
+// * kInstanceTypeArgumentsAreSubtypes: [cls] implements a fully uninstantiated
+//   type with type class [type_class] which can be directly instantiated with
+//   the instance type arguments. Thus, each type argument of [type] should be
+//   compared with the corresponding (index-wise) instance type argument.
+enum class CheckType {
+  kCannotBeChecked,
+  kNotSubtype,
+  kCidCheckOnly,
+  kNeedsFinalization,
+  kInstanceTypeArgumentsAreSubtypes,
+};
+
+// Returns a CheckType describing how to check instances of [to_check] as
+// subtypes of [type].
+static CheckType SubtypeChecksForClass(Zone* zone,
+                                       const Type& type,
+                                       const Class& type_class,
+                                       const Class& to_check) {
+  ASSERT_EQUAL(type.type_class_id(), type_class.id());
+  ASSERT(type_class.is_type_finalized());
+  ASSERT(!to_check.is_abstract());
+  ASSERT(to_check.is_type_finalized());
+  ASSERT(AbstractType::Handle(zone, to_check.RareType())
+             .IsSubtypeOf(AbstractType::Handle(zone, type_class.RareType()),
+                          Heap::kNew));
+  if (!type_class.IsGeneric()) {
+    // All instances of [to_check] are subtypes of [type].
+    return CheckType::kCidCheckOnly;
+  }
+  if (to_check.FindInstantiationOf(zone, type_class,
+                                   /*only_super_classes=*/true)) {
+    // No need to check for type argument consistency, as [to_check] is the same
+    // as or a subclass of [type_class].
+    return to_check.is_finalized()
+               ? CheckType::kInstanceTypeArgumentsAreSubtypes
+               : CheckType::kCannotBeChecked;
+  }
+  auto& calculated_type =
+      AbstractType::Handle(zone, to_check.GetInstantiationOf(zone, type_class));
+  if (calculated_type.IsInstantiated()) {
+    if (type.IsInstantiated()) {
+      return calculated_type.IsSubtypeOf(type, Heap::kNew)
+                 ? CheckType::kCidCheckOnly
+                 : CheckType::kNotSubtype;
+    }
+    // TODO(dartbug.com/46920): Requires walking both types, checking
+    // corresponding instantiated parts at compile time (assuming uninstantiated
+    // parts check successfully) and then creating appropriate runtime checks
+    // for uninstantiated parts of [type].
+    return CheckType::kCannotBeChecked;
+  }
+  if (!to_check.is_finalized()) {
+    return CheckType::kNeedsFinalization;
+  }
+  ASSERT(to_check.NumTypeArguments() > 0);
+  ASSERT(compiler::target::Class::TypeArgumentsFieldOffset(to_check) !=
+         compiler::target::Class::kNoTypeArguments);
+  // If the calculated type arguments are a prefix of the declaration type
+  // arguments, then we can just treat the instance type arguments as if they
+  // were used to instantiate the type class during checking.
+  const auto& decl_type_args = TypeArguments::Handle(
+      zone, Type::Handle(zone, to_check.DeclarationType()).arguments());
+  const auto& calculated_type_args =
+      TypeArguments::Handle(zone, calculated_type.arguments());
+  const bool type_args_consistent = calculated_type_args.IsSubvectorEquivalent(
+      decl_type_args, 0, type_class.NumTypeArguments(),
+      TypeEquality::kCanonical);
+  // TODO(dartbug.com/46920): Currently we require subtyping to be checkable
+  // by comparing the instance type arguments against the type arguments of
+  // [type] piecewise, but we could check other cases as well.
+  return type_args_consistent ? CheckType::kInstanceTypeArgumentsAreSubtypes
+                              : CheckType::kCannotBeChecked;
+}
+
+static void CommentSkippedClasses(compiler::Assembler* assembler,
+                                  const Type& type,
+                                  const Class& type_class,
+                                  const CidRangeVector& ranges) {
+  if (!assembler->EmittingComments() || ranges.is_empty()) return;
+  if (ranges.is_empty()) return;
+  ASSERT(type_class.is_implemented());
+  __ Comment("Not checking the following concrete implementors of %s:",
+             type_class.ScrubbedNameCString());
+  Thread* const thread = Thread::Current();
+  auto* const class_table = thread->isolate_group()->class_table();
+  Zone* const zone = thread->zone();
+  auto& cls = Class::Handle(zone);
+  auto& calculated_type = Type::Handle(zone);
+  for (const auto& range : ranges) {
+    ASSERT(!range.IsIllegalRange());
+    for (classid_t cid = range.cid_start; cid <= range.cid_end; cid++) {
+      // Invalid entries can be included to keep range count low.
+      if (!class_table->HasValidClassAt(cid)) continue;
+      cls = class_table->At(cid);
+      if (cls.is_abstract()) continue;  // Only output concrete classes.
+      ASSERT(cls.is_type_finalized());
+      TextBuffer buffer(128);
+      buffer.Printf(" * %" Pd32 "(%s): ", cid, cls.ScrubbedNameCString());
+      switch (SubtypeChecksForClass(zone, type, type_class, cls)) {
+        case CheckType::kCannotBeChecked:
+          calculated_type = cls.GetInstantiationOf(zone, type_class);
+          buffer.AddString("cannot check that ");
+          calculated_type.PrintName(Object::kScrubbedName, &buffer);
+          buffer.AddString(" is a subtype of ");
+          type.PrintName(Object::kScrubbedName, &buffer);
+          break;
+        case CheckType::kNotSubtype:
+          calculated_type = cls.GetInstantiationOf(zone, type_class);
+          calculated_type.PrintName(Object::kScrubbedName, &buffer);
+          buffer.AddString(" is not a subtype of ");
+          type.PrintName(Object::kScrubbedName, &buffer);
+          break;
+        case CheckType::kNeedsFinalization:
+          buffer.AddString("is not finalized");
+          break;
+        case CheckType::kInstanceTypeArgumentsAreSubtypes:
+          buffer.AddString("was not finalized during class splitting");
+          break;
+        default:
+          // Either the CheckType was kCidCheckOnly, which should never happen
+          // since it only requires type finalization, or a new CheckType has
+          // been added.
+          UNREACHABLE();
+          break;
+      }
+      __ Comment("%s", buffer.buffer());
+    }
+  }
+}
+
+// Builds a cid range check for the concrete subclasses and implementors of
+// type. Assumes cid to check is already in TTSInternalRegs::kScratchReg. Falls
+// through or jumps to check_succeeded if the range contains the cid, else
+// jumps to check_failed.
 void TypeTestingStubGenerator::BuildOptimizedSubtypeRangeCheck(
     compiler::Assembler* assembler,
     const CidRangeVector& ranges,
-    bool smi_is_ok) {
-  compiler::Label cid_range_failed, is_subtype;
-
-  if (smi_is_ok) {
-    __ LoadClassIdMayBeSmi(TTSInternalRegs::kScratchReg,
-                           TypeTestABI::kInstanceReg);
-  } else {
-    __ BranchIfSmi(TypeTestABI::kInstanceReg, &cid_range_failed);
-    __ LoadClassId(TTSInternalRegs::kScratchReg, TypeTestABI::kInstanceReg);
-  }
-
+    Register class_id_reg,
+    compiler::Label* check_succeeded,
+    compiler::Label* check_failed) {
+  CommentCheckedClasses(assembler, ranges);
   FlowGraphCompiler::GenerateCidRangesCheck(
-      assembler, TTSInternalRegs::kScratchReg, ranges, &is_subtype,
-      &cid_range_failed, true);
-  __ Bind(&is_subtype);
-  __ Ret();
-  __ Bind(&cid_range_failed);
+      assembler, class_id_reg, ranges, check_succeeded, check_failed, true);
 }
 
 void TypeTestingStubGenerator::
@@ -396,73 +584,343 @@
         compiler::Assembler* assembler,
         HierarchyInfo* hi,
         const Type& type,
-        const Class& type_class,
-        const TypeArguments& ta) {
-  // a) First we make a quick sub*class* cid-range check.
+        const Class& type_class) {
+  ASSERT(hi->CanUseGenericSubtypeRangeCheckFor(type));
   compiler::Label check_failed;
-  ASSERT(!type_class.is_implemented());
-  const CidRangeVector& ranges = hi->SubclassRangesForClass(type_class);
-  BuildOptimizedSubclassRangeCheck(assembler, ranges, &check_failed);
-  // fall through to continue
+  // a) First we perform subtype cid-range checks and load the instance type
+  // arguments based on which check succeeded.
+  __ LoadClassIdMayBeSmi(TTSInternalRegs::kScratchReg,
+                         TypeTestABI::kInstanceReg);
+  compiler::Label load_succeeded;
+  if (BuildLoadInstanceTypeArguments(assembler, hi, type, type_class,
+                                     TTSInternalRegs::kScratchReg,
+                                     TTSInternalRegs::kInstanceTypeArgumentsReg,
+                                     &load_succeeded, &check_failed)) {
+    // Only build type argument checking if any checked cid ranges require it.
+    __ Bind(&load_succeeded);
 
-  // b) Then we'll load the values for the type parameters.
-  __ LoadCompressedFieldFromOffset(
-      TTSInternalRegs::kInstanceTypeArgumentsReg, TypeTestABI::kInstanceReg,
-      compiler::target::Class::TypeArgumentsFieldOffset(type_class));
+    // b) We check for "rare" types, where the instance type arguments are null.
+    //
+    // The kernel frontend should fill in any non-assigned type parameters on
+    // construction with dynamic/Object, so we should never get the null type
+    // argument vector in created instances.
+    //
+    // TODO(kustermann): We could consider not using "null" as type argument
+    // vector representing all-dynamic to avoid this extra check (which will be
+    // uncommon because most Dart code in 2.0 will be strongly typed)!
+    __ CompareObject(TTSInternalRegs::kInstanceTypeArgumentsReg,
+                     Object::null_object());
+    const Type& rare_type = Type::Handle(Type::RawCast(type_class.RareType()));
+    if (rare_type.IsSubtypeOf(type, Heap::kNew)) {
+      compiler::Label process_done;
+      __ BranchIf(NOT_EQUAL, &process_done, compiler::Assembler::kNearJump);
+      __ Ret();
+      __ Bind(&process_done);
+    } else {
+      __ BranchIf(EQUAL, &check_failed);
+    }
 
-  // The kernel frontend should fill in any non-assigned type parameters on
-  // construction with dynamic/Object, so we should never get the null type
-  // argument vector in created instances.
-  //
-  // TODO(kustermann): We could consider not using "null" as type argument
-  // vector representing all-dynamic to avoid this extra check (which will be
-  // uncommon because most Dart code in 2.0 will be strongly typed)!
-  __ CompareObject(TTSInternalRegs::kInstanceTypeArgumentsReg,
-                   Object::null_object());
-  const Type& rare_type = Type::Handle(Type::RawCast(type_class.RareType()));
-  if (rare_type.IsSubtypeOf(type, Heap::kNew)) {
-    compiler::Label process_done;
-    __ BranchIf(NOT_EQUAL, &process_done);
+    // c) Then we'll check each value of the type argument.
+    AbstractType& type_arg = AbstractType::Handle();
+    const TypeArguments& ta = TypeArguments::Handle(type.arguments());
+    const intptr_t num_type_parameters = type_class.NumTypeParameters();
+    const intptr_t num_type_arguments = type_class.NumTypeArguments();
+    ASSERT(ta.Length() >= num_type_arguments);
+    for (intptr_t i = 0; i < num_type_parameters; ++i) {
+      const intptr_t type_param_value_offset_i =
+          num_type_arguments - num_type_parameters + i;
+
+      type_arg = ta.TypeAt(type_param_value_offset_i);
+      ASSERT(type_arg.IsTypeParameter() ||
+             hi->CanUseSubtypeRangeCheckFor(type_arg));
+
+      BuildOptimizedTypeArgumentValueCheck(
+          assembler, hi, type_arg, type_param_value_offset_i, &check_failed);
+    }
     __ Ret();
-    __ Bind(&process_done);
-  } else {
-    __ BranchIf(EQUAL, &check_failed);
   }
 
-  // c) Then we'll check each value of the type argument.
-  AbstractType& type_arg = AbstractType::Handle();
-
-  const intptr_t num_type_parameters = type_class.NumTypeParameters();
-  const intptr_t num_type_arguments = type_class.NumTypeArguments();
-  for (intptr_t i = 0; i < num_type_parameters; ++i) {
-    const intptr_t type_param_value_offset_i =
-        num_type_arguments - num_type_parameters + i;
-
-    type_arg = ta.TypeAt(type_param_value_offset_i);
-    ASSERT(type_arg.IsTypeParameter() ||
-           hi->CanUseSubtypeRangeCheckFor(type_arg));
-
-    BuildOptimizedTypeArgumentValueCheck(
-        assembler, hi, type_arg, type_param_value_offset_i, &check_failed);
-  }
-  __ Ret();
-
   // If anything fails.
   __ Bind(&check_failed);
 }
 
-void TypeTestingStubGenerator::BuildOptimizedSubclassRangeCheck(
-    compiler::Assembler* assembler,
+// Splits [ranges] into multiple ranges in [output], where the concrete,
+// finalized classes in each range share the same type arguments field offset.
+//
+// The first range in [output] contains [type_class], if any do, and otherwise
+// prioritizes ranges that include predefined cids before ranges that only
+// contain user-defined classes.
+//
+// Any cids that do not have valid class table entries, correspond to abstract
+// or unfinalized classes, or have no TAV field offset are treated as don't
+// cares, in that the cid may appear in any of the CidRangeVectors as needed to
+// reduce the number of ranges.
+//
+// Note that CidRangeVectors are MallocGrowableArrays, so the elements in
+// output must be freed after use!
+static void SplitByTypeArgumentsFieldOffset(
+    Thread* T,
+    const Class& type_class,
     const CidRangeVector& ranges,
-    compiler::Label* check_failed) {
-  __ LoadClassIdMayBeSmi(TTSInternalRegs::kScratchReg,
-                         TypeTestABI::kInstanceReg);
+    GrowableArray<CidRangeVector*>* output) {
+  ASSERT(output != nullptr);
+  ASSERT(!ranges.is_empty());
 
-  compiler::Label is_subtype;
-  FlowGraphCompiler::GenerateCidRangesCheck(
-      assembler, TTSInternalRegs::kScratchReg, ranges, &is_subtype,
-      check_failed, true);
-  __ Bind(&is_subtype);
+  Zone* const Z = T->zone();
+  ClassTable* const class_table = T->isolate_group()->class_table();
+  IntMap<CidRangeVector*> offset_map(Z);
+  IntMap<intptr_t> predefined_offsets(Z);
+  IntMap<intptr_t> user_defined_offsets(Z);
+
+  auto add_to_vector = [&](intptr_t tav_offset, const CidRange& range) {
+    if (range.cid_start == -1) return;
+    ASSERT(tav_offset != compiler::target::Class::kNoTypeArguments);
+    if (CidRangeVector* vector = offset_map.Lookup(tav_offset)) {
+      vector->Add(range);
+    } else {
+      vector = new CidRangeVector(1);
+      vector->Add(range);
+      offset_map.Insert(tav_offset, vector);
+    }
+  };
+
+  auto increment_count = [&](intptr_t cid, intptr_t tav_offset) {
+    if (cid <= kNumPredefinedCids) {
+      predefined_offsets.Update(
+          {tav_offset, predefined_offsets.Lookup(tav_offset) + 1});
+    } else if (auto* const kv = predefined_offsets.LookupPair(tav_offset)) {
+      predefined_offsets.Update({kv->key, kv->value + 1});
+    } else {
+      user_defined_offsets.Update(
+          {tav_offset, user_defined_offsets.Lookup(tav_offset) + 1});
+    }
+  };
+
+  // First populate offset_map.
+  auto& cls = Class::Handle(Z);
+  for (const auto& range : ranges) {
+    intptr_t last_offset = compiler::target::Class::kNoTypeArguments;
+    intptr_t cid_start = -1;
+    intptr_t cid_end = -1;
+    for (intptr_t cid = range.cid_start; cid <= range.cid_end; cid++) {
+      if (!class_table->HasValidClassAt(cid)) continue;
+      cls = class_table->At(cid);
+      if (cls.is_abstract()) continue;
+      // Only finalized concrete classes are present due to the conditions on
+      // returning kInstanceTypeArgumentsAreSubtypes in SubtypeChecksForClass.
+      ASSERT(cls.is_finalized());
+      const intptr_t tav_offset =
+          compiler::target::Class::TypeArgumentsFieldOffset(cls);
+      if (tav_offset == compiler::target::Class::kNoTypeArguments) continue;
+      if (tav_offset == last_offset && cid_start >= 0) {
+        cid_end = cid;
+        increment_count(cid, tav_offset);
+        continue;
+      }
+      add_to_vector(last_offset, {cid_start, cid_end});
+      last_offset = tav_offset;
+      cid_start = cid_end = cid;
+      increment_count(cid, tav_offset);
+    }
+    add_to_vector(last_offset, {cid_start, cid_end});
+  }
+
+  ASSERT(!offset_map.IsEmpty());
+
+  // Add the CidRangeVector for the type_class's offset, if it has one.
+  if (!type_class.is_abstract() && type_class.is_finalized()) {
+    const intptr_t type_class_offset =
+        compiler::target::Class::TypeArgumentsFieldOffset(type_class);
+    ASSERT(predefined_offsets.LookupPair(type_class_offset) != nullptr ||
+           user_defined_offsets.LookupPair(type_class_offset) != nullptr);
+    CidRangeVector* const vector = offset_map.Lookup(type_class_offset);
+    ASSERT(vector != nullptr);
+    output->Add(vector);
+    // Remove this CidRangeVector from consideration in the following loops.
+    predefined_offsets.Remove(type_class_offset);
+    user_defined_offsets.Remove(type_class_offset);
+  }
+  // Now add CidRangeVectors that include predefined cids.
+  // For now, we do this in an arbitrary order, but we could use the counts
+  // to prioritize offsets that are more shared if desired.
+  auto predefined_it = predefined_offsets.GetIterator();
+  while (auto* const kv = predefined_it.Next()) {
+    CidRangeVector* const vector = offset_map.Lookup(kv->key);
+    ASSERT(vector != nullptr);
+    output->Add(vector);
+  }
+  // Finally, add CidRangeVectors that only include user-defined cids.
+  // For now, we do this in an arbitrary order, but we could use the counts
+  // to prioritize offsets that are more shared if desired.
+  auto user_defined_it = user_defined_offsets.GetIterator();
+  while (auto* const kv = user_defined_it.Next()) {
+    CidRangeVector* const vector = offset_map.Lookup(kv->key);
+    ASSERT(vector != nullptr);
+    output->Add(vector);
+  }
+  ASSERT(output->length() > 0);
+}
+
+// Given [type], its type class [type_class], and a CidRangeVector [ranges],
+// populates the output CidRangeVectors from cids in [ranges], based on what
+// runtime checks are needed to determine whether the runtime type of
+// an instance is a subtype of [type].
+//
+// Concrete, type finalized classes whose cids are added to [cid_check_only]
+// implement a particular instantiation of [type_class] that is guaranteed to
+// be a subtype of [type]. Thus, these instances do not require any checking
+// of type arguments.
+//
+// Concrete, finalized classes whose cids are added to [type_argument_checks]
+// implement a fully uninstantiated version of [type_class] that can be directly
+// instantiated with the type arguments of the class's instance. Thus, each
+// type argument of [type] should be checked against the corresponding
+// instance type argument.
+//
+// Classes whose cids are in [not_checked]:
+// * Instances of the class are guaranteed to not be a subtype of [type].
+// * The class is not finalized.
+// * The subtype relation cannot be checked with our current approach and
+//   thus the stub must fall back to the STC/VM runtime.
+//
+// Any cids that do not have valid class table entries or correspond to
+// abstract classes are treated as don't cares, in that the cid may or may not
+// appear as needed to reduce the number of ranges.
+static void SplitOnTypeArgumentTests(HierarchyInfo* hi,
+                                     const Type& type,
+                                     const Class& type_class,
+                                     const CidRangeVector& ranges,
+                                     CidRangeVector* cid_check_only,
+                                     CidRangeVector* type_argument_checks,
+                                     CidRangeVector* not_checked) {
+  ASSERT(type_class.is_implemented());  // No need to split if not implemented.
+  ASSERT(cid_check_only->is_empty());
+  ASSERT(type_argument_checks->is_empty());
+  ASSERT(not_checked->is_empty());
+  ClassTable* const class_table = hi->thread()->isolate_group()->class_table();
+  Zone* const zone = hi->thread()->zone();
+  auto& to_check = Class::Handle(zone);
+  auto add_cid_range = [&](CheckType check, const CidRange& range) {
+    if (range.cid_start == -1) return;
+    switch (check) {
+      case CheckType::kCidCheckOnly:
+        cid_check_only->Add(range);
+        break;
+      case CheckType::kInstanceTypeArgumentsAreSubtypes:
+        type_argument_checks->Add(range);
+        break;
+      default:
+        not_checked->Add(range);
+    }
+  };
+  for (const auto& range : ranges) {
+    CheckType last_check = CheckType::kCannotBeChecked;
+    classid_t cid_start = -1, cid_end = -1;
+    for (classid_t cid = range.cid_start; cid <= range.cid_end; cid++) {
+      // Invalid entries can be included to keep range count low.
+      if (!class_table->HasValidClassAt(cid)) continue;
+      to_check = class_table->At(cid);
+      if (to_check.is_abstract()) continue;
+      const CheckType current_check =
+          SubtypeChecksForClass(zone, type, type_class, to_check);
+      ASSERT(current_check != CheckType::kInstanceTypeArgumentsAreSubtypes ||
+             to_check.is_finalized());
+      if (last_check == current_check && cid_start >= 0) {
+        cid_end = cid;
+        continue;
+      }
+      add_cid_range(last_check, {cid_start, cid_end});
+      last_check = current_check;
+      cid_start = cid_end = cid;
+    }
+    add_cid_range(last_check, {cid_start, cid_end});
+  }
+}
+
+bool TypeTestingStubGenerator::BuildLoadInstanceTypeArguments(
+    compiler::Assembler* assembler,
+    HierarchyInfo* hi,
+    const Type& type,
+    const Class& type_class,
+    const Register class_id_reg,
+    const Register instance_type_args_reg,
+    compiler::Label* load_succeeded,
+    compiler::Label* load_failed) {
+  const CidRangeVector& ranges =
+      hi->SubtypeRangesForClass(type_class, /*include_abstract=*/false,
+                                !Instance::NullIsAssignableTo(type));
+  if (!type_class.is_implemented()) {
+    ASSERT(type_class.is_finalized());
+    const intptr_t tav_offset =
+        compiler::target::Class::TypeArgumentsFieldOffset(type_class);
+    compiler::Label is_subtype;
+    BuildOptimizedSubtypeRangeCheck(assembler, ranges, class_id_reg,
+                                    &is_subtype, load_failed);
+    __ Bind(&is_subtype);
+    if (tav_offset != compiler::target::Class::kNoTypeArguments) {
+      // The class and its subclasses have trivially consistent type arguments.
+      __ LoadCompressedFieldFromOffset(instance_type_args_reg,
+                                       TypeTestABI::kInstanceReg, tav_offset);
+      return true;
+    } else {
+      // Not a generic type, so cid checks are sufficient.
+      __ Ret();
+      return false;
+    }
+  }
+  Thread* const T = hi->thread();
+  Zone* const Z = T->zone();
+  CidRangeVector cid_checks_only, type_argument_checks, not_checked;
+  SplitOnTypeArgumentTests(hi, type, type_class, ranges, &cid_checks_only,
+                           &type_argument_checks, &not_checked);
+  if (!cid_checks_only.is_empty()) {
+    compiler::Label is_subtype, keep_looking;
+    compiler::Label* check_failed =
+        type_argument_checks.is_empty() ? load_failed : &keep_looking;
+    BuildOptimizedSubtypeRangeCheck(assembler, cid_checks_only, class_id_reg,
+                                    &is_subtype, check_failed);
+    __ Bind(&is_subtype);
+    __ Ret();
+    __ Bind(&keep_looking);
+  }
+  if (!type_argument_checks.is_empty()) {
+    GrowableArray<CidRangeVector*> vectors;
+    SplitByTypeArgumentsFieldOffset(T, type_class, type_argument_checks,
+                                    &vectors);
+    ASSERT(vectors.length() > 0);
+    ClassTable* const class_table = T->isolate_group()->class_table();
+    auto& cls = Class::Handle(Z);
+    for (intptr_t i = 0; i < vectors.length(); i++) {
+      CidRangeVector* const vector = vectors[i];
+      ASSERT(!vector->is_empty());
+      const intptr_t first_cid = vector->At(0).cid_start;
+      ASSERT(class_table->HasValidClassAt(first_cid));
+      cls = class_table->At(first_cid);
+      ASSERT(cls.is_finalized());
+      const intptr_t tav_offset =
+          compiler::target::Class::TypeArgumentsFieldOffset(cls);
+      compiler::Label load_tav, keep_looking;
+      // For the last vector, just jump to load_failed if the check fails
+      // and avoid emitting a jump to load_succeeded.
+      compiler::Label* check_failed =
+          i < vectors.length() - 1 ? &keep_looking : load_failed;
+      BuildOptimizedSubtypeRangeCheck(assembler, *vector, class_id_reg,
+                                      &load_tav, check_failed);
+      __ Bind(&load_tav);
+      __ LoadCompressedFieldFromOffset(instance_type_args_reg,
+                                       TypeTestABI::kInstanceReg, tav_offset);
+      if (i < vectors.length() - 1) {
+        __ Jump(load_succeeded);
+        __ Bind(&keep_looking);
+      }
+      // Free the CidRangeVector allocated by SplitByTypeArgumentsFieldOffset.
+      delete vector;
+    }
+  }
+  if (!not_checked.is_empty()) {
+    CommentSkippedClasses(assembler, type, type_class, not_checked);
+  }
+  return !type_argument_checks.is_empty();
 }
 
 // Generate code to verify that instance's type argument is a subtype of
@@ -477,9 +935,13 @@
     return;
   }
 
-  // If the upper bound is a type parameter and its value is "dynamic"
-  // we always succeed.
-  compiler::Label is_dynamic;
+  if (assembler->EmittingComments()) {
+    TextBuffer buffer(128);
+    buffer.Printf("Generating check for type argument %" Pd ": ",
+                  type_param_value_offset_i);
+    type_arg.PrintName(Object::kScrubbedName, &buffer);
+    __ Comment("%s", buffer.buffer());
+  }
   if (type_arg.IsTypeParameter()) {
     const TypeParameter& type_param = TypeParameter::Cast(type_arg);
     const Register kTypeArgumentsReg =
@@ -487,9 +949,12 @@
             ? TypeTestABI::kInstantiatorTypeArgumentsReg
             : TypeTestABI::kFunctionTypeArgumentsReg;
 
+    compiler::Label is_dynamic;
     __ CompareObject(kTypeArgumentsReg, Object::null_object());
-    __ BranchIf(EQUAL, &is_dynamic);
+    __ BranchIf(EQUAL, &is_dynamic, compiler::Assembler::kNearJump);
 
+    // TODO(dartbug.com/46920): Currently only canonical equality (identity)
+    // is checked.
     __ LoadCompressedFieldFromOffset(
         TTSInternalRegs::kScratchReg, kTypeArgumentsReg,
         compiler::target::TypeArguments::type_at_offset(type_param.index()));
@@ -499,6 +964,7 @@
         compiler::target::TypeArguments::type_at_offset(
             type_param_value_offset_i));
     __ BranchIf(NOT_EQUAL, check_failed);
+    __ Bind(&is_dynamic);
   } else {
     const Class& type_class = Class::Handle(type_arg.type_class());
     const bool null_is_assignable = Instance::NullIsAssignableTo(type_arg);
@@ -525,9 +991,9 @@
     // Never is a bottom type.
     __ CompareImmediate(TTSInternalRegs::kScratchReg, kNeverCid);
     __ BranchIf(EQUAL, &is_subtype);
-    FlowGraphCompiler::GenerateCidRangesCheck(
-        assembler, TTSInternalRegs::kScratchReg, ranges, &is_subtype,
-        check_failed, true);
+    BuildOptimizedSubtypeRangeCheck(assembler, ranges,
+                                    TTSInternalRegs::kScratchReg, &is_subtype,
+                                    check_failed);
     __ Bind(&is_subtype);
 
     // Weak NNBD mode uses LEGACY_SUBTYPE which ignores nullability.
@@ -548,8 +1014,6 @@
       __ BranchIf(EQUAL, check_failed);
     }
   }
-
-  __ Bind(&is_dynamic);
 }
 
 void RegisterTypeArgumentsUse(const Function& function,
diff --git a/runtime/vm/type_testing_stubs.h b/runtime/vm/type_testing_stubs.h
index eaa75c4..74cc0c0 100644
--- a/runtime/vm/type_testing_stubs.h
+++ b/runtime/vm/type_testing_stubs.h
@@ -73,27 +73,29 @@
 
   static void BuildOptimizedSubtypeRangeCheck(compiler::Assembler* assembler,
                                               const CidRangeVector& ranges,
-                                              bool smi_is_ok);
+                                              Register class_id_reg,
+                                              compiler::Label* check_succeeded,
+                                              compiler::Label* check_failed);
 
   static void BuildOptimizedSubclassRangeCheckWithTypeArguments(
       compiler::Assembler* assembler,
       HierarchyInfo* hi,
       const Type& type,
-      const Class& type_class,
-      const TypeArguments& type_arguments);
+      const Class& type_class);
 
-  static void BuildOptimizedSubclassRangeCheckWithTypeArguments(
+  // Falls through or jumps to load_succeeded if load succeeds, otherwise jumps
+  // to load_failed. Returns from the stub for checked cid ranges which do not
+  // require checking the instance type arguments. Returns whether any cid
+  // ranges require type argument checking.
+  static bool BuildLoadInstanceTypeArguments(
       compiler::Assembler* assembler,
       HierarchyInfo* hi,
       const Type& type,
       const Class& type_class,
-      const TypeArguments& type_arguments,
       const Register class_id_reg,
-      const Register instance_type_args_reg);
-
-  static void BuildOptimizedSubclassRangeCheck(compiler::Assembler* assembler,
-                                               const CidRangeVector& ranges,
-                                               compiler::Label* check_failed);
+      const Register instance_type_args_reg,
+      compiler::Label* load_succeeded,
+      compiler::Label* load_failed);
 
   static void BuildOptimizedTypeArgumentValueCheck(
       compiler::Assembler* assembler,
diff --git a/runtime/vm/type_testing_stubs_test.cc b/runtime/vm/type_testing_stubs_test.cc
index 9e807ad..480c1d0 100644
--- a/runtime/vm/type_testing_stubs_test.cc
+++ b/runtime/vm/type_testing_stubs_test.cc
@@ -33,23 +33,32 @@
 
 class TraceStubInvocationScope : public ValueObject {
  public:
-  TraceStubInvocationScope() : old_flag_value_(FLAG_trace_type_checks) {
+  TraceStubInvocationScope()
+      : old_trace_type_checks_(FLAG_trace_type_checks),
+        old_disassemble_stubs_(FLAG_disassemble_stubs) {
     if (FLAG_trace_type_testing_stub_tests) {
 #if defined(DEBUG)
       FLAG_trace_type_checks = true;
 #endif
+#if defined(FORCE_INCLUDE_DISASSEMBLER) || !defined(PRODUCT)
+      FLAG_disassemble_stubs = true;
+#endif
     }
   }
   ~TraceStubInvocationScope() {
     if (FLAG_trace_type_testing_stub_tests) {
 #if defined(DEBUG)
-      FLAG_trace_type_checks = old_flag_value_;
+      FLAG_trace_type_checks = old_trace_type_checks_;
+#endif
+#if defined(FORCE_INCLUDE_DISASSEMBLER) || !defined(PRODUCT)
+      FLAG_disassemble_stubs = old_disassemble_stubs_;
 #endif
     }
   }
 
  private:
-  const bool old_flag_value_;
+  const bool old_trace_type_checks_;
+  const bool old_disassemble_stubs_;
 };
 
 #define __ assembler->
@@ -184,7 +193,7 @@
   *tav = tav->Canonicalize(Thread::Current(), nullptr);
 }
 
-struct TTSTestCase : public ValueObject {
+struct TTSTestCase {
   const Instance& instance;
   const TypeArguments& instantiator_tav;
   const TypeArguments& function_tav;
@@ -255,7 +264,7 @@
     if (cls.NumTypeArguments() == 0) {
       return true;
     }
-    return instance.GetTypeArguments() != other.instance.GetTypeArguments();
+    return instance.GetTypeArguments() == other.instance.GetTypeArguments();
   }
 
   bool HasSTCEntry(const SubtypeTestCache& cache,
@@ -291,6 +300,9 @@
                           Object::null_type_arguments(),
                           Object::null_type_arguments(), out_index, out_result);
   }
+
+ private:
+  DISALLOW_ALLOCATION();
 };
 
 // Inherits should_specialize from original.
@@ -392,8 +404,10 @@
     last_tested_type_.SetTypeTestingStub(specializing_stub);
     InvokeStubHelper(test_case,
                      /*is_lazy_specialization=*/test_case.should_specialize);
-    if (test_case.should_fail) {
-      // We only respecialize on successful checks.
+    if (test_case.should_fail || test_case.instance.IsNull()) {
+      // We only specialize if we go to runtime and the runtime check
+      // succeeds. The lazy specialization stub for nullable types has a
+      // special fast case for null that skips the runtime.
       EXPECT(new_tts_stub_.ptr() == specializing_stub.ptr());
     } else if (test_case.should_specialize) {
       // Specializing test cases should never result in a default TTS.
@@ -512,9 +526,11 @@
     if (test_case.should_fail) {
       EXPECT(last_result_.IsError());
       EXPECT(last_result_.IsUnhandledException());
-      const auto& error =
-          Instance::Handle(UnhandledException::Cast(last_result_).exception());
-      EXPECT(strstr(error.ToCString(), "_TypeError"));
+      if (last_result_.IsUnhandledException()) {
+        const auto& error = Instance::Handle(
+            UnhandledException::Cast(last_result_).exception());
+        EXPECT(strstr(error.ToCString(), "_TypeError"));
+      }
     } else {
       EXPECT(new_tts_stub_.ptr() != StubCode::LazySpecializeTypeTest().ptr());
       ReportModifiedRegisters(modified_abi_regs());
@@ -543,6 +559,39 @@
     }
   }
 
+  void ReportMissingOrChangedEntries(const SubtypeTestCache& old_cache,
+                                     const SubtypeTestCache& new_cache) {
+    auto& cid_or_sig = Object::Handle(zone());
+    auto& type = AbstractType::Handle(zone());
+    auto& instance_type_args = TypeArguments::Handle(zone());
+    auto& instantiator_type_args = TypeArguments::Handle(zone());
+    auto& function_type_args = TypeArguments::Handle(zone());
+    auto& instance_parent_type_args = TypeArguments::Handle(zone());
+    auto& instance_delayed_type_args = TypeArguments::Handle(zone());
+    auto& old_result = Bool::Handle(zone());
+    auto& new_result = Bool::Handle(zone());
+    SafepointMutexLocker ml(
+        thread_->isolate_group()->subtype_test_cache_mutex());
+    for (intptr_t i = 0; i < old_cache.NumberOfChecks(); i++) {
+      old_cache.GetCheck(0, &cid_or_sig, &type, &instance_type_args,
+                         &instantiator_type_args, &function_type_args,
+                         &instance_parent_type_args,
+                         &instance_delayed_type_args, &old_result);
+      intptr_t new_index;
+      if (!new_cache.HasCheck(
+              cid_or_sig, type, instance_type_args, instantiator_type_args,
+              function_type_args, instance_parent_type_args,
+              instance_delayed_type_args, &new_index, &new_result)) {
+        dart::Expect(__FILE__, __LINE__)
+            .Fail("New STC is missing check in old STC");
+      }
+      if (old_result.value() != new_result.value()) {
+        dart::Expect(__FILE__, __LINE__)
+            .Fail("New STC has different result from old STC");
+      }
+    }
+  }
+
   void ReportUnexpectedSTCChanges(const TTSTestCase& test_case,
                                   bool is_lazy_specialization = false) {
     ASSERT(!test_case.should_be_false_negative ||
@@ -552,47 +601,27 @@
         !is_lazy_specialization && test_case.should_be_false_negative;
     if (should_update_stc && !had_stc_entry) {
       // We should have changed the STC to include the new entry.
-      EXPECT((previous_stc_.IsNull() && !last_stc_.IsNull()) ||
-             previous_stc_.cache() != last_stc_.cache());
-      // We only should have added one check.
-      EXPECT_EQ(previous_stc_.IsNull() ? 1 : previous_stc_.NumberOfChecks() + 1,
-                last_stc_.NumberOfChecks());
-      if (!previous_stc_.IsNull()) {
-        // Make sure all the checks in the previous STC are still there.
-        auto& cid_or_sig = Object::Handle(zone());
-        auto& type = AbstractType::Handle(zone());
-        auto& instance_type_args = TypeArguments::Handle(zone());
-        auto& instantiator_type_args = TypeArguments::Handle(zone());
-        auto& function_type_args = TypeArguments::Handle(zone());
-        auto& instance_parent_type_args = TypeArguments::Handle(zone());
-        auto& instance_delayed_type_args = TypeArguments::Handle(zone());
-        auto& old_result = Bool::Handle(zone());
-        auto& new_result = Bool::Handle(zone());
-        SafepointMutexLocker ml(
-            thread_->isolate_group()->subtype_test_cache_mutex());
-        for (intptr_t i = 0; i < previous_stc_.NumberOfChecks(); i++) {
-          previous_stc_.GetCheck(0, &cid_or_sig, &type, &instance_type_args,
-                                 &instantiator_type_args, &function_type_args,
-                                 &instance_parent_type_args,
-                                 &instance_delayed_type_args, &old_result);
-          intptr_t new_index;
-          if (!last_stc_.HasCheck(
-                  cid_or_sig, type, instance_type_args, instantiator_type_args,
-                  function_type_args, instance_parent_type_args,
-                  instance_delayed_type_args, &new_index, &new_result)) {
-            dart::Expect(__FILE__, __LINE__)
-                .Fail("New STC is missing check in old STC");
-          }
-          if (old_result.value() != new_result.value()) {
-            dart::Expect(__FILE__, __LINE__)
-                .Fail("New STC has different result from old STC");
-          }
+      EXPECT(!last_stc_.IsNull());
+      if (!last_stc_.IsNull()) {
+        EXPECT(previous_stc_.IsNull() ||
+               previous_stc_.cache() != last_stc_.cache());
+        // We only should have added one check.
+        EXPECT_EQ(
+            previous_stc_.IsNull() ? 1 : previous_stc_.NumberOfChecks() + 1,
+            last_stc_.NumberOfChecks());
+        if (!previous_stc_.IsNull()) {
+          // Make sure all the checks in the previous STC are still there.
+          ReportMissingOrChangedEntries(previous_stc_, last_stc_);
         }
       }
     } else {
       // Whatever STC existed before, if any, should be unchanged.
-      EXPECT((previous_stc_.IsNull() && last_stc_.IsNull()) ||
-             previous_stc_.cache() == last_stc_.cache());
+      if (previous_stc_.IsNull()) {
+        EXPECT(last_stc_.IsNull());
+      } else {
+        EXPECT(!last_stc_.IsNull() &&
+               previous_stc_.cache() == last_stc_.cache());
+      }
     }
 
     // False negatives should always be an STC hit when not lazily
@@ -602,12 +631,18 @@
     if ((!should_update_stc && has_stc_entry) ||
         (should_update_stc && !has_stc_entry)) {
       TextBuffer buffer(128);
-      buffer.Printf("%s entry for %s, got:\n",
-                    should_update_stc ? "Expected" : "Did not expect",
-                    type_.ToCString());
-      for (intptr_t i = 0; i < last_stc_.NumberOfChecks(); i++) {
-        last_stc_.WriteCurrentEntryToBuffer(zone(), &buffer, i);
+      buffer.Printf(
+          "%s entry for %s, got:",
+          test_case.should_be_false_negative ? "Expected" : "Did not expect",
+          type_.ToCString());
+      if (last_stc_.IsNull()) {
+        buffer.AddString(" null");
+      } else {
         buffer.AddString("\n");
+        for (intptr_t i = 0; i < last_stc_.NumberOfChecks(); i++) {
+          last_stc_.WriteCurrentEntryToBuffer(zone(), &buffer, i);
+          buffer.AddString("\n");
+        }
       }
       dart::Expect(__FILE__, __LINE__).Fail("%s", buffer.buffer());
     }
@@ -629,14 +664,34 @@
   Object& last_result_;
 };
 
-// Tests three situations in turn with the same test case:
+// Tests three situations in turn with the test case and with an
+// appropriate null object test:
 // 1) Install the lazy specialization stub for JIT and test.
 // 2) Test again without installing a stub, so using the stub resulting from 1.
 // 3) Install an eagerly specialized stub, similar to AOT mode but keeping any
 //    STC created by the earlier steps, and test.
 static void RunTTSTest(const AbstractType& dst_type,
                        const TTSTestCase& test_case) {
+  bool null_should_fail = !Instance::NullIsAssignableTo(
+      dst_type, test_case.instantiator_tav, test_case.function_tav);
+
+  const TTSTestCase null_test(
+      Instance::Handle(), test_case.instantiator_tav, test_case.function_tav,
+      test_case.should_specialize, null_should_fail,
+      // Null is never a false negative.
+      /*should_be_false_negative=*/false,
+      // Since null is never a false negative, it can't trigger
+      // respecialization.
+      /*should_respecialize=*/false);
+
   TTSTestState state(Thread::Current(), dst_type);
+  // First check the null case. This should _never_ create an STC.
+  state.InvokeLazilySpecializedStub(null_test);
+  state.InvokeExistingStub(null_test);
+  state.InvokeEagerlySpecializedStub(null_test);
+  EXPECT(state.last_stc().IsNull());
+
+  // Now run the actual test case.
   state.InvokeLazilySpecializedStub(test_case);
   state.InvokeExistingStub(test_case);
   state.InvokeEagerlySpecializedStub(test_case);
@@ -824,24 +879,15 @@
   auto& type_dynamic_t =
       AbstractType::Handle(Type::New(class_i, tav_dynamic_t));
   FinalizeAndCanonicalize(&type_dynamic_t);
-  RunTTSTest(type_dynamic_t, FalseNegative({obj_i, tav_object, tav_null,
-                                            /*should_specialize=*/false}));
-  RunTTSTest(type_dynamic_t, Failure({obj_i2, tav_object, tav_null,
-                                      /*should_specialize=*/false}));
-  RunTTSTest(type_dynamic_t, Failure({obj_base_int, tav_object, tav_null,
-                                      /*should_specialize=*/false}));
-  RunTTSTest(type_dynamic_t, Failure({obj_a, tav_object, tav_null,
-                                      /*should_specialize=*/false}));
-  RunTTSTest(type_dynamic_t, Failure({obj_a1, tav_object, tav_null,
-                                      /*should_specialize=*/false}));
-  RunTTSTest(type_dynamic_t, FalseNegative({obj_a2, tav_object, tav_null,
-                                            /*should_specialize=*/false}));
-  RunTTSTest(type_dynamic_t, Failure({obj_b, tav_object, tav_null,
-                                      /*should_specialize=*/false}));
-  RunTTSTest(type_dynamic_t, Failure({obj_b1, tav_object, tav_null,
-                                      /*should_specialize=*/false}));
-  RunTTSTest(type_dynamic_t, FalseNegative({obj_b2, tav_object, tav_null,
-                                            /*should_specialize=*/false}));
+  RunTTSTest(type_dynamic_t, FalseNegative({obj_i, tav_object, tav_null}));
+  RunTTSTest(type_dynamic_t, Failure({obj_i2, tav_object, tav_null}));
+  RunTTSTest(type_dynamic_t, Failure({obj_base_int, tav_object, tav_null}));
+  RunTTSTest(type_dynamic_t, Failure({obj_a, tav_object, tav_null}));
+  RunTTSTest(type_dynamic_t, Failure({obj_a1, tav_object, tav_null}));
+  RunTTSTest(type_dynamic_t, FalseNegative({obj_a2, tav_object, tav_null}));
+  RunTTSTest(type_dynamic_t, Failure({obj_b, tav_object, tav_null}));
+  RunTTSTest(type_dynamic_t, Failure({obj_b1, tav_object, tav_null}));
+  RunTTSTest(type_dynamic_t, FalseNegative({obj_b2, tav_object, tav_null}));
 
   // obj as Object (with null safety)
   auto isolate_group = IsolateGroup::Current();
@@ -978,8 +1024,10 @@
   RunTTSTest(type_base_b, {obj_base_int, tav_null, tav_null});
   RunTTSTest(type_base_b, Failure({obj_i2, tav_null, tav_null}));
 
-  // We do not generate TTS for uninstantiated types if we would need to use
-  // subtype range checks for the class of the interface type.
+  // We generate TTS for implemented classes and uninstantiated types, but
+  // any class that implements the type class but does not match in both
+  // instance TAV offset and type argument indices is guaranteed to be a
+  // false negative.
   //
   //   obj as I<dynamic, String>       // I is generic & implemented.
   //   obj as Base<A2<T>>              // A2<T> is not instantiated.
@@ -993,11 +1041,9 @@
   type_i_dynamic_string = type_i_dynamic_string.ToNullability(
       Nullability::kNonNullable, Heap::kNew);
   FinalizeAndCanonicalize(&type_i_dynamic_string);
-  RunTTSTest(
-      type_i_dynamic_string,
-      FalseNegative({obj_i, tav_null, tav_null, /*should_specialize=*/false}));
-  RunTTSTest(type_i_dynamic_string, Failure({obj_base_int, tav_null, tav_null,
-                                             /*should_specialize=*/false}));
+  RunTTSTest(type_i_dynamic_string, {obj_i, tav_null, tav_null});
+  RunTTSTest(type_i_dynamic_string,
+             Failure({obj_base_int, tav_null, tav_null}));
 
   //   <...> as Base<A2<T>>
   const auto& tav_t = TypeArguments::Handle(TypeArguments::New(1));
@@ -1035,6 +1081,112 @@
                                        /*should_specialize=*/false}));
 }
 
+ISOLATE_UNIT_TEST_CASE(TTS_Generic_Implements_Instantiated_Interface) {
+  const char* kScript =
+      R"(
+      abstract class I<T> {}
+      class B<R> implements I<String> {}
+
+      createBInt() => B<int>();
+)";
+
+  const auto& root_library = Library::Handle(LoadTestScript(kScript));
+  const auto& class_i = Class::Handle(GetClass(root_library, "I"));
+  const auto& obj_b_int = Object::Handle(Invoke(root_library, "createBInt"));
+
+  const auto& tav_null = Object::null_type_arguments();
+  auto& tav_string = TypeArguments::Handle(TypeArguments::New(1));
+  tav_string.SetTypeAt(0, Type::Handle(Type::StringType()));
+  CanonicalizeTAV(&tav_string);
+
+  auto& type_i_string = Type::Handle(Type::New(class_i, tav_string));
+  FinalizeAndCanonicalize(&type_i_string);
+  const auto& type_i_t = Type::Handle(class_i.DeclarationType());
+
+  RunTTSTest(type_i_string, {obj_b_int, tav_null, tav_null});
+  // Optimized TTSees don't currently handle the case where the implemented
+  // type is known, but the type being checked requires instantiation at
+  // runtime.
+  RunTTSTest(type_i_t, FalseNegative({obj_b_int, tav_string, tav_null}));
+}
+
+ISOLATE_UNIT_TEST_CASE(TTS_Future) {
+  const char* kScript =
+      R"(
+      import "dart:async";
+
+      createFutureInt() => (() async => 3)();
+)";
+
+  const auto& root_library = Library::Handle(LoadTestScript(kScript));
+  const auto& class_future = Class::Handle(GetClass(root_library, "Future"));
+  const auto& obj_futureint =
+      Object::Handle(Invoke(root_library, "createFutureInt"));
+
+  const auto& type_nullable_object = Type::Handle(
+      IsolateGroup::Current()->object_store()->nullable_object_type());
+  const auto& type_int = Type::Handle(Type::IntType());
+  const auto& type_string = Type::Handle(Type::StringType());
+  const auto& type_num = Type::Handle(Type::Number());
+
+  const auto& tav_null = Object::null_type_arguments();
+  auto& tav_dynamic = TypeArguments::Handle(TypeArguments::New(1));
+  tav_dynamic.SetTypeAt(0, Object::dynamic_type());
+  CanonicalizeTAV(&tav_dynamic);
+  auto& tav_nullable_object = TypeArguments::Handle(TypeArguments::New(1));
+  tav_nullable_object.SetTypeAt(0, type_nullable_object);
+  CanonicalizeTAV(&tav_nullable_object);
+  auto& tav_int = TypeArguments::Handle(TypeArguments::New(1));
+  tav_int.SetTypeAt(0, type_int);
+  CanonicalizeTAV(&tav_int);
+  auto& tav_num = TypeArguments::Handle(TypeArguments::New(1));
+  tav_num.SetTypeAt(0, type_num);
+  CanonicalizeTAV(&tav_num);
+  auto& tav_string = TypeArguments::Handle(TypeArguments::New(1));
+  tav_string.SetTypeAt(0, type_string);
+  CanonicalizeTAV(&tav_string);
+
+  auto& type_future = Type::Handle(Type::New(class_future, tav_null));
+  FinalizeAndCanonicalize(&type_future);
+  auto& type_future_dynamic =
+      Type::Handle(Type::New(class_future, tav_dynamic));
+  FinalizeAndCanonicalize(&type_future_dynamic);
+  auto& type_future_nullable_object =
+      Type::Handle(Type::New(class_future, tav_nullable_object));
+  FinalizeAndCanonicalize(&type_future_nullable_object);
+  auto& type_future_int = Type::Handle(Type::New(class_future, tav_int));
+  FinalizeAndCanonicalize(&type_future_int);
+  auto& type_future_string = Type::Handle(Type::New(class_future, tav_string));
+  FinalizeAndCanonicalize(&type_future_string);
+  auto& type_future_num = Type::Handle(Type::New(class_future, tav_num));
+  FinalizeAndCanonicalize(&type_future_num);
+  auto& type_future_t = Type::Handle(class_future.DeclarationType());
+
+  // Some more tests of generic implemented classes, using Future. Here,
+  // obj is an object of type Future<int>.
+  //
+  //   obj as Future          : Null type args (caught by TTS)
+  //   obj as Future<dynamic> : Canonicalized to same as previous case.
+  //   obj as Future<Object?> : Type arg is top type (caught by TTS)
+  //   obj as Future<int>     : Type arg is the same type (caught by TTS)
+  //   obj as Future<String>  : Type arg is not a subtype (error via runtime)
+  //   obj as Future<num>     : Type arg is a supertype that can be matched
+  //                            with cid range (caught by TTS)
+  //   obj as Future<X>,      : Type arg is a type parameter instantiated with
+  //       X = int            :    ... the same type (caught by TTS)
+  //       X = String         :    ... an unrelated type (error via runtime)
+  //       X = num            :    ... a supertype (caught by STC/runtime)
+  RunTTSTest(type_future, {obj_futureint, tav_null, tav_null});
+  RunTTSTest(type_future_dynamic, {obj_futureint, tav_null, tav_null});
+  RunTTSTest(type_future_nullable_object, {obj_futureint, tav_null, tav_null});
+  RunTTSTest(type_future_int, {obj_futureint, tav_null, tav_null});
+  RunTTSTest(type_future_string, Failure({obj_futureint, tav_null, tav_null}));
+  RunTTSTest(type_future_num, {obj_futureint, tav_null, tav_null});
+  RunTTSTest(type_future_t, {obj_futureint, tav_int, tav_null});
+  RunTTSTest(type_future_t, Failure({obj_futureint, tav_string, tav_null}));
+  RunTTSTest(type_future_t, FalseNegative({obj_futureint, tav_num, tav_null}));
+}
+
 ISOLATE_UNIT_TEST_CASE(TTS_Regress40964) {
   const char* kScript =
       R"(
@@ -1122,15 +1274,91 @@
 
 // Check that we generate correct TTS for _Smi type.
 ISOLATE_UNIT_TEST_CASE(TTS_Smi) {
-  const auto& root_library = Library::Handle(Library::CoreLibrary());
-  const auto& smi_class = Class::Handle(GetClass(root_library, "_Smi"));
-  ClassFinalizer::FinalizeTypesInClass(smi_class);
+  const auto& type_smi = Type::Handle(Type::SmiType());
+  const auto& tav_null = Object::null_type_arguments();
 
-  const auto& dst_type = AbstractType::Handle(smi_class.RareType());
-  const auto& tav_null = TypeArguments::Handle(TypeArguments::null());
+  // Test on some easy-to-make instances.
+  RunTTSTest(type_smi, {Smi::Handle(Smi::New(0)), tav_null, tav_null});
+  RunTTSTest(type_smi, Failure({Integer::Handle(Integer::New(kMaxInt64)),
+                                tav_null, tav_null}));
+  RunTTSTest(type_smi,
+             Failure({Double::Handle(Double::New(1.0)), tav_null, tav_null}));
+  RunTTSTest(type_smi, Failure({Symbols::Empty(), tav_null, tav_null}));
+  RunTTSTest(type_smi,
+             Failure({Array::Handle(Array::New(1)), tav_null, tav_null}));
+}
 
-  THR_Print("\nTesting that instance of _Smi is a subtype of _Smi\n");
-  RunTTSTest(dst_type, {Smi::Handle(Smi::New(0)), tav_null, tav_null});
+// Check that we generate correct TTS for int type.
+ISOLATE_UNIT_TEST_CASE(TTS_Int) {
+  const auto& type_int = Type::Handle(Type::IntType());
+  const auto& tav_null = Object::null_type_arguments();
+
+  // Test on some easy-to-make instances.
+  RunTTSTest(type_int, {Smi::Handle(Smi::New(0)), tav_null, tav_null});
+  RunTTSTest(type_int,
+             {Integer::Handle(Integer::New(kMaxInt64)), tav_null, tav_null});
+  RunTTSTest(type_int,
+             Failure({Double::Handle(Double::New(1.0)), tav_null, tav_null}));
+  RunTTSTest(type_int, Failure({Symbols::Empty(), tav_null, tav_null}));
+  RunTTSTest(type_int,
+             Failure({Array::Handle(Array::New(1)), tav_null, tav_null}));
+}
+
+// Check that we generate correct TTS for num type.
+ISOLATE_UNIT_TEST_CASE(TTS_Num) {
+  const auto& type_num = Type::Handle(Type::Number());
+  const auto& tav_null = Object::null_type_arguments();
+
+  // Test on some easy-to-make instances.
+  RunTTSTest(type_num, {Smi::Handle(Smi::New(0)), tav_null, tav_null});
+  RunTTSTest(type_num,
+             {Integer::Handle(Integer::New(kMaxInt64)), tav_null, tav_null});
+  RunTTSTest(type_num, {Double::Handle(Double::New(1.0)), tav_null, tav_null});
+  RunTTSTest(type_num, Failure({Symbols::Empty(), tav_null, tav_null}));
+  RunTTSTest(type_num,
+             Failure({Array::Handle(Array::New(1)), tav_null, tav_null}));
+}
+
+// Check that we generate correct TTS for Double type.
+ISOLATE_UNIT_TEST_CASE(TTS_Double) {
+  const auto& type_num = Type::Handle(Type::Double());
+  const auto& tav_null = Object::null_type_arguments();
+
+  // Test on some easy-to-make instances.
+  RunTTSTest(type_num, Failure({Smi::Handle(Smi::New(0)), tav_null, tav_null}));
+  RunTTSTest(type_num, Failure({Integer::Handle(Integer::New(kMaxInt64)),
+                                tav_null, tav_null}));
+  RunTTSTest(type_num, {Double::Handle(Double::New(1.0)), tav_null, tav_null});
+  RunTTSTest(type_num, Failure({Symbols::Empty(), tav_null, tav_null}));
+  RunTTSTest(type_num,
+             Failure({Array::Handle(Array::New(1)), tav_null, tav_null}));
+}
+
+// Check that we generate correct TTS for Object type.
+ISOLATE_UNIT_TEST_CASE(TTS_Object) {
+  const auto& type_obj =
+      Type::Handle(IsolateGroup::Current()->object_store()->object_type());
+  const auto& tav_null = Object::null_type_arguments();
+
+  auto make_test_case = [&](const Instance& instance) -> TTSTestCase {
+    if (IsolateGroup::Current()->use_strict_null_safety_checks()) {
+      // The stub for non-nullable object should specialize, but only fails
+      // on null, which is already checked within RunTTSTest.
+      return {instance, tav_null, tav_null};
+    } else {
+      // The default type testing stub for nullable object is the top type
+      // stub, so it should neither specialize _or_ return false negatives.
+      return {instance, tav_null, tav_null, /*should_specialize=*/false};
+    }
+  };
+
+  // Test on some easy-to-make instances.
+  RunTTSTest(type_obj, make_test_case(Smi::Handle(Smi::New(0))));
+  RunTTSTest(type_obj,
+             make_test_case(Integer::Handle(Integer::New(kMaxInt64))));
+  RunTTSTest(type_obj, make_test_case(Double::Handle(Double::New(1.0))));
+  RunTTSTest(type_obj, make_test_case(Symbols::Empty()));
+  RunTTSTest(type_obj, make_test_case(Array::Handle(Array::New(1))));
 }
 
 // Check that we generate correct TTS for type Function (the non-FunctionType
@@ -1152,7 +1380,6 @@
   const auto& obj_f = Object::Handle(Invoke(root_library, "createF"));
   const auto& obj_g = Object::Handle(Invoke(root_library, "createG"));
   const auto& obj_h = Object::Handle(Invoke(root_library, "createH"));
-  const auto& obj_null = Instance::Handle();
 
   const auto& tav_null = TypeArguments::Handle(TypeArguments::null());
   const auto& type_function = Type::Handle(Type::DartFunctionType());
@@ -1160,11 +1387,6 @@
   RunTTSTest(type_function, {obj_f, tav_null, tav_null});
   RunTTSTest(type_function, {obj_g, tav_null, tav_null});
   RunTTSTest(type_function, {obj_h, tav_null, tav_null});
-  if (!thread->isolate_group()->use_strict_null_safety_checks()) {
-    RunTTSTest(type_function, {obj_null, tav_null, tav_null});
-  } else {
-    RunTTSTest(type_function, Failure({obj_null, tav_null, tav_null}));
-  }
 
   const auto& class_a = Class::Handle(GetClass(root_library, "A"));
   const auto& obj_a_int = Object::Handle(Invoke(root_library, "createAInt"));
diff --git a/sdk/lib/cli/cli.dart b/sdk/lib/cli/cli.dart
index 1dc8e0d..223dd68 100644
--- a/sdk/lib/cli/cli.dart
+++ b/sdk/lib/cli/cli.dart
@@ -4,6 +4,8 @@
 
 /// {@category VM}
 /// {@nodoc}
+@Deprecated(
+    "The functionality of this library is incomplete and may be removed in a later version")
 library dart.cli;
 
 import 'dart:async';
diff --git a/sdk/lib/cli/wait_for.dart b/sdk/lib/cli/wait_for.dart
index 58990c8..29e5386 100644
--- a/sdk/lib/cli/wait_for.dart
+++ b/sdk/lib/cli/wait_for.dart
@@ -111,7 +111,33 @@
  * Please be aware that nesting calls to [waitFor] can lead to deadlock if
  * subsequent calls block waiting for a condition that is only satisfied when
  * an earlier call returns.
+ *
+ * **NOTICE**
+ * The `waitFor` feature is deprecated.
+ * The feature was intended to solve a particular problem for existing code,
+ * a problem introduced by a breaking change to the platform libraries.
+ * The `waitFor` function is not suitable for general use.
+ * The feature has shortcomings that can affect other code
+ * running in the same isolate, including:
+ *  * A function call that looks synchronous may cause other asynchronous
+ *    events to run before it returns.
+ *    This is something synchronous code can usually assume not to happen,
+ *    and some code may have been written to take advantage of that
+ *    assumed behavior. Such code can fail in unexpected ways.
+ *  * Multiple nested calls to `waitFor` may block each other
+ *    since the most recent call always needs to complete
+ *    before any other call can complete.
+ *    Judicious use of `waitFor` is necessary to avoid unexpected deadlocks
+ *    which wouldn't happen if using `await` instead.
+ *    If more than one library in the same program is using `waitFor`,
+ *    then it's hard to avoid or control whether such blocking will happen.
+ *
+ * The feature is not actively maintained.
+ * It will remain as-is to support the original problem it was added to solve,
+ * at least until that problem can be solved in some other way.
  */
+@Deprecated(
+    "This functionality is incomplete and may be removed in a later version")
 T waitFor<T>(Future<T> future, {Duration? timeout}) {
   late T result;
   bool futureCompleted = false;
diff --git a/tools/VERSION b/tools/VERSION
index f7e936c..848e6fb 100644
--- a/tools/VERSION
+++ b/tools/VERSION
@@ -27,5 +27,5 @@
 MAJOR 2
 MINOR 15
 PATCH 0
-PRERELEASE 18
+PRERELEASE 19
 PRERELEASE_PATCH 0
\ No newline at end of file