Optimize `ClassHierarchyBuilder.isInheritedInSubtypeOf`.

This fixes the compile-time regression introduced by
https://dart-review.googlesource.com/c/sdk/+/72643

Change-Id: I55d441592cd710daeca980a3eb902284751575dc
Reviewed-on: https://dart-review.googlesource.com/75001
Commit-Queue: Johnni Winther <johnniwinther@google.com>
Reviewed-by: Stephen Adams <sra@google.com>
diff --git a/pkg/compiler/lib/src/compiler.dart b/pkg/compiler/lib/src/compiler.dart
index 1e2a298..be24ef3 100644
--- a/pkg/compiler/lib/src/compiler.dart
+++ b/pkg/compiler/lib/src/compiler.dart
@@ -290,6 +290,15 @@
 
   JClosedWorld computeClosedWorld(LoadedLibraries loadedLibraries) {
     ResolutionEnqueuer resolutionEnqueuer = startResolution();
+    for (LibraryEntity library
+        in frontendStrategy.elementEnvironment.libraries) {
+      frontendStrategy.elementEnvironment.forEachClass(library,
+          (ClassEntity cls) {
+        // Register all classes eagerly to optimize closed world computation in
+        // `ClassWorldBuilder.isInheritedInSubtypeOf`.
+        resolutionEnqueuer.worldBuilder.registerClass(cls);
+      });
+    }
     WorldImpactBuilderImpl mainImpact = new WorldImpactBuilderImpl();
     FunctionEntity mainFunction = frontendStrategy.computeMain(mainImpact);
 
diff --git a/pkg/compiler/lib/src/js_emitter/program_builder/program_builder.dart b/pkg/compiler/lib/src/js_emitter/program_builder/program_builder.dart
index 00cdf32..de9d367 100644
--- a/pkg/compiler/lib/src/js_emitter/program_builder/program_builder.dart
+++ b/pkg/compiler/lib/src/js_emitter/program_builder/program_builder.dart
@@ -690,7 +690,7 @@
       callStubs.add(_buildStubMethod(name, function));
     }
 
-    if (_commonElements.isInstantiationClass(cls)) {
+    if (_commonElements.isInstantiationClass(cls) && !onlyForRti) {
       callStubs.addAll(_generateInstantiationStubs(cls));
     }
 
diff --git a/pkg/compiler/lib/src/library_loader.dart b/pkg/compiler/lib/src/library_loader.dart
index a941245..b4832ee 100644
--- a/pkg/compiler/lib/src/library_loader.dart
+++ b/pkg/compiler/lib/src/library_loader.dart
@@ -41,6 +41,8 @@
       : initializedCompilerState = _options.kernelInitializedCompilerState,
         super(measurer);
 
+  String get name => 'Library loader';
+
   /// Loads an entire Kernel [Component] from a file on disk.
   Future<LoadedLibraries> loadLibraries(Uri resolvedUri) {
     return measure(() async {
diff --git a/pkg/compiler/lib/src/universe/class_hierarchy.dart b/pkg/compiler/lib/src/universe/class_hierarchy.dart
index cc91490..c351f1e 100644
--- a/pkg/compiler/lib/src/universe/class_hierarchy.dart
+++ b/pkg/compiler/lib/src/universe/class_hierarchy.dart
@@ -625,32 +625,112 @@
     return classSet.hasSubtype(classHierarchyNode);
   }
 
+  Map<ClassEntity, _InheritedCache> _inheritedCacheMap = {};
+
   bool isInheritedInSubtypeOf(ClassEntity x, ClassEntity y) {
-    ClassSet classSet = classSets[x];
-    assert(classSet != null,
-        failedAt(x, "No ClassSet for $x (${x.runtimeType}): ${classSets}"));
+    _InheritedCache cache = _inheritedCacheMap[x] ??= new _InheritedCache();
+    return cache.isInheritedInSubtypeOf(this, x, y);
+  }
+}
 
-    if (_isSubtypeOf(x, y)) {
+/// A cache object used for [ClassHierarchyBuilder.isInheritedInSubtypeOf].
+class _InheritedCache {
+  Map<ClassEntity, _InheritingSet> _map;
+
+  /// Returns whether a live class currently known to inherit from [x] and
+  /// implement [y].
+  bool isInheritedInSubtypeOf(
+      ClassHierarchyBuilder builder, ClassEntity x, ClassEntity y) {
+    _InheritingSet set;
+    if (_map == null) {
+      _map = {};
+    } else {
+      set = _map[y];
+    }
+    if (set == null) {
+      set = _map[y] = _computeInheritingSet(builder, x, y);
+    }
+    return set.hasLiveClass(builder);
+  }
+
+  /// Creates an [_InheritingSet] of classes that inherit members of a class [x]
+  /// while implementing class [y].
+  _InheritingSet _computeInheritingSet(
+      ClassHierarchyBuilder builder, ClassEntity x, ClassEntity y) {
+    ClassSet classSet = builder.classSets[x];
+
+    assert(
+        classSet != null,
+        failedAt(
+            x, "No ClassSet for $x (${x.runtimeType}): ${builder.classSets}"));
+
+    Set<ClassEntity> classes = new Set<ClassEntity>();
+
+    if (builder._isSubtypeOf(x, y)) {
       // [x] implements [y] itself, possible through supertypes.
-      return true;
+      classes.add(x);
     }
 
-    /// Returns `true` if any live subclass of [node] implements [y].
-    bool subclassImplements(ClassHierarchyNode node, {bool strict}) {
-      return node.anySubclass((ClassEntity z) => _isSubtypeOf(z, y),
-          ClassHierarchyNode.INSTANTIATED,
-          strict: strict);
+    /// Add subclasses of [node] that implement [y].
+    void subclassImplements(ClassHierarchyNode node, {bool strict}) {
+      node.forEachSubclass((ClassEntity z) {
+        if (builder._isSubtypeOf(z, y)) {
+          classes.add(z);
+        }
+      }, ClassHierarchyNode.ALL, strict: strict);
     }
 
-    if (subclassImplements(classSet.node, strict: true)) {
-      // A subclass of [x] implements [y].
-      return true;
-    }
+    // A subclasses of [x] that implement [y].
+    subclassImplements(classSet.node, strict: true);
 
     for (ClassHierarchyNode mixinApplication
         in classSet.mixinApplicationNodes) {
-      if (subclassImplements(mixinApplication, strict: false)) {
-        // A subclass of [mixinApplication] implements [y].
+      // A subclass of [mixinApplication] implements [y].
+      subclassImplements(mixinApplication, strict: false);
+    }
+
+    return new _InheritingSet(classes);
+  }
+}
+
+/// A set of classes that inherit members of a class 'x' while implementing
+/// class 'y'.
+///
+/// The set is used [ClassHierarchyBuilder.isInheritedInSubtypeOf] to determine
+/// when members of a class is live.
+class _InheritingSet {
+  /// If `true` the set of classes is known to contain a live class. In this
+  /// case [_classes] is `null`. If `false` the set of classes is empty and
+  /// therefore known never to contain live classes. In this case [_classes]
+  /// is `null`. If `null` [_classes] is a non-empty set containing classes
+  /// that are not yet known to be live.
+  bool _result;
+  Set<ClassEntity> _classes;
+
+  _InheritingSet(Set<ClassEntity> classes)
+      : _result = classes.isEmpty ? false : null,
+        _classes = classes.isNotEmpty ? classes : null;
+
+  /// Returns whether the set of classes is currently known to contain a live
+  /// classes.
+  ///
+  /// The result of this method changes during the closed world computation.
+  /// Initially, we haven't seen any live classes so we will return `false` even
+  /// for a non-empty set of classes. As more classes are marked as
+  /// instantiated, during tree-shaking, the result might change to `true` if
+  /// one of the [_classes] has been marked as live.
+  ///
+  /// The result of this method _is_ monotone, though; when we have returned
+  /// `true` (because at least one class is known to be live) we will continue
+  /// to return `true`.
+  bool hasLiveClass(ClassHierarchyBuilder builder) {
+    if (_result != null) return _result;
+    for (ClassEntity cls in _classes) {
+      if (builder.classHierarchyNodes[cls].isInstantiated) {
+        // We now know this set contains a live class and done need to remember
+        // that set of classes anymore.
+        _result = true;
+        _classes = null;
         return true;
       }
     }
diff --git a/pkg/compiler/lib/src/universe/resolution_world_builder.dart b/pkg/compiler/lib/src/universe/resolution_world_builder.dart
index 6f7d4bd..b203dc9 100644
--- a/pkg/compiler/lib/src/universe/resolution_world_builder.dart
+++ b/pkg/compiler/lib/src/universe/resolution_world_builder.dart
@@ -851,7 +851,7 @@
       memberUsed(usage.entity, useSet);
       return usage;
     });
-    if (!newUsage) {
+    if (!usage.fullyUsed && !newUsage) {
       EnumSet<MemberUse> useSet = new EnumSet<MemberUse>();
       if (!usage.hasRead && _hasInvokedGetter(member)) {
         useSet.addAll(usage.read());
@@ -945,8 +945,6 @@
   bool isInheritedInSubtypeOf(MemberEntity member, ClassEntity type) {
     // TODO(johnniwinther): Use the [member] itself to avoid enqueueing members
     // that are overridden.
-    _classHierarchyBuilder.registerClass(member.enclosingClass);
-    _classHierarchyBuilder.registerClass(type);
     return _classHierarchyBuilder.isInheritedInSubtypeOf(
         member.enclosingClass, type);
   }
@@ -972,12 +970,7 @@
     Map<ClassEntity, Set<ClassEntity>> typesImplementedBySubclasses =
         populateHierarchyNodes();
 
-    var backendUsage = _backendUsageBuilder.close();
-    backendUsage.helperClassesUsed
-        .forEach(_classHierarchyBuilder.registerClass);
-    _nativeResolutionEnqueuer.liveNativeClasses
-        .forEach(_classHierarchyBuilder.registerClass);
-
+    BackendUsage backendUsage = _backendUsageBuilder.close();
     _closed = true;
     assert(
         _classHierarchyBuilder.classHierarchyNodes.length ==
@@ -996,7 +989,7 @@
         commonElements: _commonElements,
         nativeData: _nativeDataBuilder.close(),
         interceptorData: _interceptorDataBuilder.close(),
-        backendUsage: _backendUsageBuilder.close(),
+        backendUsage: backendUsage,
         noSuchMethodData: _noSuchMethodRegistry.close(),
         resolutionWorldBuilder: this,
         rtiNeedBuilder: _rtiNeedBuilder,
diff --git a/pkg/compiler/lib/src/universe/world_builder.dart b/pkg/compiler/lib/src/universe/world_builder.dart
index 5107b28..84aa6d3 100644
--- a/pkg/compiler/lib/src/universe/world_builder.dart
+++ b/pkg/compiler/lib/src/universe/world_builder.dart
@@ -14,7 +14,8 @@
 import '../elements/types.dart';
 import '../js_backend/annotations.dart';
 import '../js_backend/allocator_analysis.dart' show KAllocatorAnalysis;
-import '../js_backend/backend_usage.dart' show BackendUsageBuilder;
+import '../js_backend/backend_usage.dart'
+    show BackendUsage, BackendUsageBuilder;
 import '../js_backend/interceptor_data.dart' show InterceptorDataBuilder;
 import '../js_backend/native_data.dart' show NativeBasicData, NativeDataBuilder;
 import '../js_backend/no_such_method_registry.dart';
diff --git a/tests/compiler/dart2js/model/class_set_test.dart b/tests/compiler/dart2js/model/class_set_test.dart
index ada6c0c..790a8a5 100644
--- a/tests/compiler/dart2js/model/class_set_test.dart
+++ b/tests/compiler/dart2js/model/class_set_test.dart
@@ -36,8 +36,8 @@
       ///    D   E F G
       ///
       class A {}
-      class B extends A {}
       class C extends A {}
+      class B extends A {}
       class D extends B {}
       class E extends C {}
       class F extends C {}
@@ -361,8 +361,8 @@
       ///         H I
       ///
       class A implements X {}
-      class B extends A {}
       class C extends A {}
+      class B extends A {}
       class D extends B {}
       class E extends C {}
       class F extends C implements B {}