Specialize enqueuer analyses

Change-Id: I4b0f5f24b4144df520bb1a2ffc5f430de19e965b
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java
index dbae251..5586e26 100644
--- a/src/main/java/com/android/tools/r8/R8.java
+++ b/src/main/java/com/android/tools/r8/R8.java
@@ -33,7 +33,6 @@
 import com.android.tools.r8.graph.ProgramDefinition;
 import com.android.tools.r8.graph.PrunedItems;
 import com.android.tools.r8.graph.SubtypingInfo;
-import com.android.tools.r8.graph.analysis.ClassInitializerAssertionEnablingAnalysis;
 import com.android.tools.r8.horizontalclassmerging.HorizontalClassMerger;
 import com.android.tools.r8.inspector.internal.InspectorImpl;
 import com.android.tools.r8.ir.conversion.IRConverter;
@@ -49,12 +48,10 @@
 import com.android.tools.r8.ir.desugar.records.RecordFieldValuesRewriter;
 import com.android.tools.r8.ir.desugar.records.RecordInstructionDesugaring;
 import com.android.tools.r8.ir.desugar.varhandle.VarHandleDesugaring;
-import com.android.tools.r8.ir.optimize.AssertionsRewriter;
 import com.android.tools.r8.ir.optimize.Inliner;
 import com.android.tools.r8.ir.optimize.NestReducer;
 import com.android.tools.r8.ir.optimize.SwitchMapCollector;
 import com.android.tools.r8.ir.optimize.enums.EnumUnboxingCfMethods;
-import com.android.tools.r8.ir.optimize.info.OptimizationFeedbackSimple;
 import com.android.tools.r8.ir.optimize.info.OptimizationInfoRemover;
 import com.android.tools.r8.ir.optimize.templates.CfUtilityMethodsForCodeOptimizations;
 import com.android.tools.r8.jar.CfApplicationWriter;
@@ -562,17 +559,17 @@
             }
           }
 
+          if (options.isClassMergingExtensionRequired()) {
+            finalRuntimeTypeCheckInfoBuilder = new RuntimeTypeCheckInfo.Builder(appView);
+          }
           Enqueuer enqueuer =
               EnqueuerFactory.createForFinalTreeShaking(
                   appView,
                   executorService,
                   SubtypingInfo.create(appView),
                   keptGraphConsumer,
-                  prunedTypes);
-          if (options.isClassMergingExtensionRequired(enqueuer.getMode())) {
-            finalRuntimeTypeCheckInfoBuilder = new RuntimeTypeCheckInfo.Builder(appView);
-            finalRuntimeTypeCheckInfoBuilder.attach(enqueuer);
-          }
+                  prunedTypes,
+                  finalRuntimeTypeCheckInfoBuilder);
           EnqueuerResult enqueuerResult =
               enqueuer.traceApplication(appView.rootSet(), executorService, timing);
           appView.setAppInfo(enqueuerResult.getAppInfo());
@@ -1168,13 +1165,6 @@
             appView, profileCollectionAdditions, executorService, subtypingInfo);
     enqueuer.setKeepDeclarations(keepDeclarations);
     enqueuer.setAnnotationRemoverBuilder(annotationRemoverBuilder);
-    if (AssertionsRewriter.isEnabled(appView.options())) {
-      ClassInitializerAssertionEnablingAnalysis analysis =
-          new ClassInitializerAssertionEnablingAnalysis(
-              appView, OptimizationFeedbackSimple.getInstance());
-      enqueuer.registerAnalysis(analysis);
-      enqueuer.registerFieldAccessAnalysis(analysis);
-    }
     timing.end();
     timing.begin("Trace application");
     EnqueuerResult enqueuerResult =
diff --git a/src/main/java/com/android/tools/r8/desugar/covariantreturntype/CovariantReturnTypeEnqueuerExtension.java b/src/main/java/com/android/tools/r8/desugar/covariantreturntype/CovariantReturnTypeEnqueuerExtension.java
index 4e8573f..18be316 100644
--- a/src/main/java/com/android/tools/r8/desugar/covariantreturntype/CovariantReturnTypeEnqueuerExtension.java
+++ b/src/main/java/com/android/tools/r8/desugar/covariantreturntype/CovariantReturnTypeEnqueuerExtension.java
@@ -13,7 +13,9 @@
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.ProgramDefinition;
 import com.android.tools.r8.graph.ProgramMethod;
-import com.android.tools.r8.graph.analysis.EnqueuerAnalysis;
+import com.android.tools.r8.graph.analysis.EnqueuerAnalysisCollection;
+import com.android.tools.r8.graph.analysis.FixpointEnqueuerAnalysis;
+import com.android.tools.r8.graph.analysis.NewlyLiveMethodEnqueuerAnalysis;
 import com.android.tools.r8.shaking.Enqueuer;
 import com.android.tools.r8.shaking.EnqueuerWorklist;
 import com.android.tools.r8.shaking.KeepInfo;
@@ -29,7 +31,8 @@
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.ExecutorService;
 
-public class CovariantReturnTypeEnqueuerExtension extends EnqueuerAnalysis {
+public class CovariantReturnTypeEnqueuerExtension
+    implements NewlyLiveMethodEnqueuerAnalysis, FixpointEnqueuerAnalysis {
 
   private final AppView<? extends AppInfoWithClassHierarchy> appView;
   private final CovariantReturnTypeAnnotationTransformer transformer;
@@ -44,10 +47,14 @@
   }
 
   public static void register(
-      AppView<? extends AppInfoWithClassHierarchy> appView, Enqueuer enqueuer) {
+      AppView<? extends AppInfoWithClassHierarchy> appView,
+      Enqueuer enqueuer,
+      EnqueuerAnalysisCollection.Builder builder) {
     if (enqueuer.getMode().isInitialTreeShaking()
         && CovariantReturnTypeAnnotationTransformer.shouldRun(appView)) {
-      enqueuer.registerAnalysis(new CovariantReturnTypeEnqueuerExtension(appView));
+      CovariantReturnTypeEnqueuerExtension analysis =
+          new CovariantReturnTypeEnqueuerExtension(appView);
+      builder.addNewlyLiveMethodAnalysis(analysis).addFixpointAnalysis(analysis);
     }
   }
 
diff --git a/src/main/java/com/android/tools/r8/features/IsolatedFeatureSplitsChecker.java b/src/main/java/com/android/tools/r8/features/IsolatedFeatureSplitsChecker.java
index 376acee..2b05cea 100644
--- a/src/main/java/com/android/tools/r8/features/IsolatedFeatureSplitsChecker.java
+++ b/src/main/java/com/android/tools/r8/features/IsolatedFeatureSplitsChecker.java
@@ -17,16 +17,18 @@
 import com.android.tools.r8.graph.ProgramDefinition;
 import com.android.tools.r8.graph.ProgramField;
 import com.android.tools.r8.graph.ProgramMethod;
-import com.android.tools.r8.graph.analysis.EnqueuerFieldAccessAnalysis;
-import com.android.tools.r8.graph.analysis.EnqueuerInvokeAnalysis;
+import com.android.tools.r8.graph.analysis.EnqueuerAnalysisCollection;
 import com.android.tools.r8.graph.analysis.EnqueuerTypeAccessAnalysis;
-import com.android.tools.r8.shaking.Enqueuer;
+import com.android.tools.r8.graph.analysis.TraceFieldAccessEnqueuerAnalysis;
+import com.android.tools.r8.graph.analysis.TraceInvokeEnqueuerAnalysis;
 import com.android.tools.r8.shaking.EnqueuerWorklist;
 import com.android.tools.r8.utils.InternalOptions;
 
 // TODO(b/300247439): Also trace types referenced from new-array instructions, call sites, etc.
 public class IsolatedFeatureSplitsChecker
-    implements EnqueuerFieldAccessAnalysis, EnqueuerInvokeAnalysis, EnqueuerTypeAccessAnalysis {
+    implements TraceFieldAccessEnqueuerAnalysis,
+        TraceInvokeEnqueuerAnalysis,
+        EnqueuerTypeAccessAnalysis {
 
   private final AppView<? extends AppInfoWithClassHierarchy> appView;
   private final ClassToFeatureSplitMap features;
@@ -37,13 +39,18 @@
   }
 
   public static void register(
-      AppView<? extends AppInfoWithClassHierarchy> appView, Enqueuer enqueuer) {
+      AppView<? extends AppInfoWithClassHierarchy> appView,
+      EnqueuerAnalysisCollection.Builder builder) {
     if (enabled(appView)) {
       IsolatedFeatureSplitsChecker checker = new IsolatedFeatureSplitsChecker(appView);
-      enqueuer
-          .registerFieldAccessAnalysis(checker)
-          .registerInvokeAnalysis(checker)
-          .registerTypeAccessAnalysis(checker);
+      builder
+          .addTraceFieldAccessAnalysis(checker)
+          .addTraceInvokeAnalysis(checker)
+          .addTraceCheckCastAnalysis(checker)
+          .addTraceConstClassAnalysis(checker)
+          .addTraceExceptionGuardAnalysis(checker)
+          .addTraceInstanceOfAnalysis(checker)
+          .addTraceNewInstanceAnalysis(checker);
     }
   }
 
diff --git a/src/main/java/com/android/tools/r8/graph/GenericSignatureEnqueuerAnalysis.java b/src/main/java/com/android/tools/r8/graph/GenericSignatureEnqueuerAnalysis.java
index ed13c75..5f8b573 100644
--- a/src/main/java/com/android/tools/r8/graph/GenericSignatureEnqueuerAnalysis.java
+++ b/src/main/java/com/android/tools/r8/graph/GenericSignatureEnqueuerAnalysis.java
@@ -4,14 +4,25 @@
 
 package com.android.tools.r8.graph;
 
-import com.android.tools.r8.graph.analysis.EnqueuerAnalysis;
+import com.android.tools.r8.graph.analysis.EnqueuerAnalysisCollection;
+import com.android.tools.r8.graph.analysis.NewlyLiveClassEnqueuerAnalysis;
+import com.android.tools.r8.graph.analysis.NewlyLiveFieldEnqueuerAnalysis;
+import com.android.tools.r8.graph.analysis.NewlyLiveMethodEnqueuerAnalysis;
+import com.android.tools.r8.graph.analysis.NewlyReachableFieldEnqueuerAnalysis;
+import com.android.tools.r8.graph.analysis.NewlyTargetedMethodEnqueuerAnalysis;
 import com.android.tools.r8.shaking.Enqueuer;
 import com.android.tools.r8.shaking.Enqueuer.EnqueuerDefinitionSupplier;
 import com.android.tools.r8.shaking.EnqueuerWorklist;
+import com.android.tools.r8.utils.InternalOptions;
 import com.google.common.collect.Sets;
 import java.util.Set;
 
-public class GenericSignatureEnqueuerAnalysis extends EnqueuerAnalysis {
+public class GenericSignatureEnqueuerAnalysis
+    implements NewlyLiveClassEnqueuerAnalysis,
+        NewlyLiveFieldEnqueuerAnalysis,
+        NewlyLiveMethodEnqueuerAnalysis,
+        NewlyReachableFieldEnqueuerAnalysis,
+        NewlyTargetedMethodEnqueuerAnalysis {
 
   private final EnqueuerDefinitionSupplier enqueuerDefinitionSupplier;
   private final Set<DexReference> processedSignatures = Sets.newIdentityHashSet();
@@ -20,14 +31,30 @@
     this.enqueuerDefinitionSupplier = enqueuerDefinitionSupplier;
   }
 
-  @Override
-  public void processNewlyLiveClass(DexProgramClass clazz, EnqueuerWorklist worklist) {
-    processSignature(clazz, clazz.getContext());
+  public static void register(
+      AppView<? extends AppInfoWithClassHierarchy> appView,
+      EnqueuerDefinitionSupplier enqueuerDefinitionSupplier,
+      EnqueuerAnalysisCollection.Builder builder) {
+    // TODO(b/323816623): This check does not include presence of keep declarations.
+    //  We should consider if we should always run the signature analysis and just not emit them
+    //  in the end?
+    InternalOptions options = appView.options();
+    if (options.hasProguardConfiguration()
+        && options.getProguardConfiguration().getKeepAttributes().signature) {
+      GenericSignatureEnqueuerAnalysis analysis =
+          new GenericSignatureEnqueuerAnalysis(enqueuerDefinitionSupplier);
+      builder
+          .addNewlyLiveClassAnalysis(analysis)
+          .addNewlyLiveFieldAnalysis(analysis)
+          .addNewlyLiveMethodAnalysis(analysis)
+          .addNewlyReachableFieldAnalysis(analysis)
+          .addNewlyTargetedMethodAnalysis(analysis);
+    }
   }
 
   @Override
-  public void notifyMarkFieldAsReachable(ProgramField field, EnqueuerWorklist worklist) {
-    processSignature(field, field.getContext());
+  public void processNewlyLiveClass(DexProgramClass clazz, EnqueuerWorklist worklist) {
+    processSignature(clazz, clazz.getContext());
   }
 
   @Override
@@ -37,11 +64,6 @@
   }
 
   @Override
-  public void processNewlyTargetedMethod(ProgramMethod method, EnqueuerWorklist worklist) {
-    processSignature(method, method.getContext());
-  }
-
-  @Override
   public void processNewlyLiveMethod(
       ProgramMethod method,
       ProgramDefinition context,
@@ -50,6 +72,16 @@
     processSignature(method, context);
   }
 
+  @Override
+  public void processNewlyReachableField(ProgramField field, EnqueuerWorklist worklist) {
+    processSignature(field, field.getContext());
+  }
+
+  @Override
+  public void processNewlyTargetedMethod(ProgramMethod method, EnqueuerWorklist worklist) {
+    processSignature(method, method.getContext());
+  }
+
   private void processSignature(ProgramDefinition signatureHolder, ProgramDefinition context) {
     if (!processedSignatures.add(signatureHolder.getReference())) {
       return;
diff --git a/src/main/java/com/android/tools/r8/graph/analysis/ApiModelAnalysis.java b/src/main/java/com/android/tools/r8/graph/analysis/ApiModelAnalysis.java
index f16a2d8..4b6337e 100644
--- a/src/main/java/com/android/tools/r8/graph/analysis/ApiModelAnalysis.java
+++ b/src/main/java/com/android/tools/r8/graph/analysis/ApiModelAnalysis.java
@@ -6,11 +6,11 @@
 
 import com.android.tools.r8.androidapi.AndroidApiLevelCompute;
 import com.android.tools.r8.androidapi.ComputedApiLevel;
+import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.ClasspathOrLibraryClass;
 import com.android.tools.r8.graph.DexClassAndMember;
 import com.android.tools.r8.graph.DexEncodedMethod;
-import com.android.tools.r8.graph.LookupTarget;
 import com.android.tools.r8.graph.ProgramDefinition;
 import com.android.tools.r8.graph.ProgramField;
 import com.android.tools.r8.graph.ProgramMethod;
@@ -18,7 +18,14 @@
 import com.android.tools.r8.shaking.Enqueuer;
 import com.android.tools.r8.shaking.EnqueuerWorklist;
 
-public class ApiModelAnalysis extends EnqueuerAnalysis {
+public class ApiModelAnalysis
+    implements NewlyFailedMethodResolutionEnqueuerAnalysis,
+        NewlyLiveCodeEnqueuerAnalysis,
+        NewlyLiveFieldEnqueuerAnalysis,
+        NewlyLiveMethodEnqueuerAnalysis,
+        NewlyLiveNonProgramClassEnqueuerAnalysis,
+        NewlyReachableFieldEnqueuerAnalysis,
+        NewlyTargetedMethodEnqueuerAnalysis {
 
   private final AppView<?> appView;
   private final AndroidApiLevelCompute apiCompute;
@@ -30,6 +37,22 @@
     this.minApiLevel = appView.computedMinApiLevel();
   }
 
+  public static void register(
+      AppView<? extends AppInfoWithClassHierarchy> appView,
+      EnqueuerAnalysisCollection.Builder builder) {
+    if (appView.options().apiModelingOptions().enableLibraryApiModeling) {
+      ApiModelAnalysis analysis = new ApiModelAnalysis(appView);
+      builder
+          .addNewlyFailedMethodResolutionAnalysis(analysis)
+          .addNewlyLiveCodeAnalysis(analysis)
+          .addNewlyLiveFieldAnalysis(analysis)
+          .addNewlyLiveMethodAnalysis(analysis)
+          .addNewlyLiveNonProgramClassAnalysis(analysis)
+          .addNewlyReachableFieldAnalysis(analysis)
+          .addNewlyTargetedMethodAnalysis(analysis);
+    }
+  }
+
   @Override
   public void processNewlyLiveField(
       ProgramField field, ProgramDefinition context, EnqueuerWorklist worklist) {
@@ -46,7 +69,7 @@
   }
 
   @Override
-  public void processTracedCode(
+  public void processNewlyLiveCode(
       ProgramMethod method, DefaultEnqueuerUseRegistry registry, EnqueuerWorklist worklist) {
     assert registry.getMaxApiReferenceLevel().isGreaterThanOrEqualTo(minApiLevel);
     if (appView.options().apiModelingOptions().tracedMethodApiLevelCallback != null) {
@@ -66,27 +89,17 @@
   }
 
   @Override
-  public void notifyMarkFieldAsReachable(ProgramField field, EnqueuerWorklist worklist) {
+  public void processNewlyReachableField(ProgramField field, EnqueuerWorklist worklist) {
     computeAndSetApiLevelForDefinition(field);
   }
 
   @Override
-  public void processNewLiveNonProgramType(ClasspathOrLibraryClass clazz) {
+  public void processNewlyLiveNonProgramType(ClasspathOrLibraryClass clazz) {
     clazz.forEachClassMethod(this::computeAndSetApiLevelForDefinition);
   }
 
   @Override
-  public void notifyMarkVirtualDispatchTargetAsLive(
-      LookupTarget target, EnqueuerWorklist worklist) {
-    target.accept(
-        lookupMethodTarget -> computeAndSetApiLevelForDefinition(lookupMethodTarget.getTarget()),
-        lookupLambdaTarget -> {
-          // The implementation method will be assigned an api level when visited.
-        });
-  }
-
-  @Override
-  public void notifyFailedMethodResolutionTarget(
+  public void processNewlyFailedMethodResolutionTarget(
       DexEncodedMethod method, EnqueuerWorklist worklist) {
     // We may not trace into failed resolution targets.
     method.setApiLevelForCode(ComputedApiLevel.unknown());
diff --git a/src/main/java/com/android/tools/r8/graph/analysis/ClassInitializerAssertionEnablingAnalysis.java b/src/main/java/com/android/tools/r8/graph/analysis/ClassInitializerAssertionEnablingAnalysis.java
index e5122b3..d38b615 100644
--- a/src/main/java/com/android/tools/r8/graph/analysis/ClassInitializerAssertionEnablingAnalysis.java
+++ b/src/main/java/com/android/tools/r8/graph/analysis/ClassInitializerAssertionEnablingAnalysis.java
@@ -14,6 +14,7 @@
 import com.android.tools.r8.cf.code.CfLoad;
 import com.android.tools.r8.cf.code.CfLogicalBinop;
 import com.android.tools.r8.cf.code.CfStaticFieldWrite;
+import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.CfCode;
 import com.android.tools.r8.graph.DexEncodedMethod;
@@ -24,7 +25,9 @@
 import com.android.tools.r8.graph.FieldResolutionResult.SingleFieldResolutionResult;
 import com.android.tools.r8.graph.ProgramDefinition;
 import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.ir.optimize.AssertionsRewriter;
 import com.android.tools.r8.ir.optimize.info.OptimizationFeedback;
+import com.android.tools.r8.ir.optimize.info.OptimizationFeedbackSimple;
 import com.android.tools.r8.shaking.Enqueuer;
 import com.android.tools.r8.shaking.EnqueuerWorklist;
 import com.android.tools.r8.utils.AssertionConfigurationWithDefault;
@@ -33,8 +36,8 @@
 import java.util.stream.Collectors;
 import org.objectweb.asm.Opcodes;
 
-public class ClassInitializerAssertionEnablingAnalysis extends EnqueuerAnalysis
-    implements EnqueuerFieldAccessAnalysis {
+public class ClassInitializerAssertionEnablingAnalysis
+    implements TraceFieldAccessEnqueuerAnalysis, NewlyLiveMethodEnqueuerAnalysis {
   private final DexItemFactory dexItemFactory;
   private final OptimizationFeedback feedback;
   private final DexString kotlinAssertionsEnabled;
@@ -53,17 +56,28 @@
             .collect(Collectors.toList());
   }
 
-  @SuppressWarnings("ReferenceEquality")
+  public static void register(
+      AppView<? extends AppInfoWithClassHierarchy> appView,
+      Enqueuer enqueuer,
+      EnqueuerAnalysisCollection.Builder builder) {
+    if (enqueuer.getMode().isInitialTreeShaking()
+        && AssertionsRewriter.isEnabled(appView.options())) {
+      ClassInitializerAssertionEnablingAnalysis analysis =
+          new ClassInitializerAssertionEnablingAnalysis(
+              appView, OptimizationFeedbackSimple.getInstance());
+      builder.addTraceFieldAccessAnalysis(analysis).addNewlyLiveMethodAnalysis(analysis);
+    }
+  }
+
   private boolean isUsingJavaAssertionsDisabledField(DexField field) {
     // This does not check the holder, as for inner classes the field is read from the outer class
     // and not the class itself.
-    return field.getName() == dexItemFactory.assertionsDisabled
-        && field.getType() == dexItemFactory.booleanType;
+    return field.getName().isIdenticalTo(dexItemFactory.assertionsDisabled)
+        && field.getType().isIdenticalTo(dexItemFactory.booleanType);
   }
 
-  @SuppressWarnings("ReferenceEquality")
   private boolean isUsingKotlinAssertionsEnabledField(DexField field) {
-    return field == dexItemFactory.kotlin.assertions.enabledField;
+    return field.isIdenticalTo(dexItemFactory.kotlin.assertions.enabledField);
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/graph/analysis/EnqueuerAnalysis.java b/src/main/java/com/android/tools/r8/graph/analysis/EnqueuerAnalysis.java
deleted file mode 100644
index 8fd3ed4..0000000
--- a/src/main/java/com/android/tools/r8/graph/analysis/EnqueuerAnalysis.java
+++ /dev/null
@@ -1,73 +0,0 @@
-// Copyright (c) 2019, the R8 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.
-
-package com.android.tools.r8.graph.analysis;
-
-import com.android.tools.r8.graph.ClasspathOrLibraryClass;
-import com.android.tools.r8.graph.DexEncodedMethod;
-import com.android.tools.r8.graph.DexProgramClass;
-import com.android.tools.r8.graph.LookupTarget;
-import com.android.tools.r8.graph.ProgramDefinition;
-import com.android.tools.r8.graph.ProgramField;
-import com.android.tools.r8.graph.ProgramMethod;
-import com.android.tools.r8.shaking.DefaultEnqueuerUseRegistry;
-import com.android.tools.r8.shaking.Enqueuer;
-import com.android.tools.r8.shaking.EnqueuerWorklist;
-import com.android.tools.r8.utils.Timing;
-import java.util.concurrent.ExecutionException;
-import java.util.concurrent.ExecutorService;
-
-public abstract class EnqueuerAnalysis {
-
-  /** Called when a class is found to be instantiated. */
-  public void processNewlyInstantiatedClass(
-      DexProgramClass clazz, ProgramMethod context, EnqueuerWorklist worklist) {}
-
-  /** Called when a class is found to be live. */
-  public void processNewlyLiveClass(DexProgramClass clazz, EnqueuerWorklist worklist) {}
-
-  /** Called when a field is found to be live. */
-  public void processNewlyLiveField(
-      ProgramField field, ProgramDefinition context, EnqueuerWorklist worklist) {}
-
-  public void processNewlyReferencedField(ProgramField field) {}
-
-  /** Called when a method is found to be live. */
-  public void processNewlyLiveMethod(
-      ProgramMethod method,
-      ProgramDefinition context,
-      Enqueuer enqueuer,
-      EnqueuerWorklist worklist) {}
-
-  /** Called when a non program class is visited and marked live */
-  public void processNewLiveNonProgramType(ClasspathOrLibraryClass clazz) {}
-
-  /** Called when a method's code has been processed by the registry. */
-  public void processTracedCode(
-      ProgramMethod method, DefaultEnqueuerUseRegistry registry, EnqueuerWorklist worklist) {}
-
-  public void processNewlyTargetedMethod(ProgramMethod method, EnqueuerWorklist worklist) {}
-
-  public void notifyMarkFieldAsReachable(ProgramField field, EnqueuerWorklist worklist) {}
-
-  public void notifyMarkVirtualDispatchTargetAsLive(
-      LookupTarget target, EnqueuerWorklist worklist) {}
-
-  public void notifyFailedMethodResolutionTarget(
-      DexEncodedMethod method, EnqueuerWorklist worklist) {}
-
-  /**
-   * Called when the Enqueuer reaches a fixpoint. This may happen multiple times, since each
-   * analysis may enqueue items into the worklist upon the fixpoint using {@param worklist}.
-   */
-  public void notifyFixpoint(
-      Enqueuer enqueuer, EnqueuerWorklist worklist, ExecutorService executorService, Timing timing)
-      throws ExecutionException {}
-
-  /**
-   * Called when the Enqueuer has reached the final fixpoint. Each analysis may use this callback to
-   * perform some post-processing.
-   */
-  public void done(Enqueuer enqueuer) {}
-}
diff --git a/src/main/java/com/android/tools/r8/graph/analysis/EnqueuerAnalysisCollection.java b/src/main/java/com/android/tools/r8/graph/analysis/EnqueuerAnalysisCollection.java
new file mode 100644
index 0000000..afacc0e
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/graph/analysis/EnqueuerAnalysisCollection.java
@@ -0,0 +1,496 @@
+// Copyright (c) 2024, the R8 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.
+package com.android.tools.r8.graph.analysis;
+
+import com.android.tools.r8.graph.ClasspathOrLibraryClass;
+import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexField;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.FieldResolutionResult;
+import com.android.tools.r8.graph.FieldResolutionResult.SingleFieldResolutionResult;
+import com.android.tools.r8.graph.MethodResolutionResult;
+import com.android.tools.r8.graph.ProgramDefinition;
+import com.android.tools.r8.graph.ProgramField;
+import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.shaking.DefaultEnqueuerUseRegistry;
+import com.android.tools.r8.shaking.Enqueuer;
+import com.android.tools.r8.shaking.EnqueuerWorklist;
+import com.android.tools.r8.utils.ArrayUtils;
+import com.android.tools.r8.utils.Timing;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
+
+public final class EnqueuerAnalysisCollection {
+
+  // Trace events.
+  private final TraceCheckCastEnqueuerAnalysis[] checkCastAnalyses;
+  private final TraceConstClassEnqueuerAnalysis[] constClassAnalyses;
+  private final TraceExceptionGuardEnqueuerAnalysis[] exceptionGuardAnalyses;
+  private final TraceFieldAccessEnqueuerAnalysis[] fieldAccessAnalyses;
+  private final TraceInstanceOfEnqueuerAnalysis[] instanceOfAnalyses;
+  private final TraceInvokeEnqueuerAnalysis[] invokeAnalyses;
+  private final TraceNewInstanceEnqueuerAnalysis[] newInstanceAnalyses;
+
+  // Reachability events.
+  private final NewlyFailedMethodResolutionEnqueuerAnalysis[] newlyFailedMethodResolutionAnalyses;
+  private final NewlyLiveClassEnqueuerAnalysis[] newlyLiveClassAnalyses;
+  private final NewlyLiveCodeEnqueuerAnalysis[] newlyLiveCodeAnalyses;
+  private final NewlyLiveFieldEnqueuerAnalysis[] newlyLiveFieldAnalyses;
+  private final NewlyLiveMethodEnqueuerAnalysis[] newlyLiveMethodAnalyses;
+  private final NewlyInstantiatedClassEnqueuerAnalysis[] newlyInstantiatedClassAnalyses;
+  private final NewlyLiveNonProgramClassEnqueuerAnalysis[] newlyLiveNonProgramClassAnalyses;
+  private final NewlyReachableFieldEnqueuerAnalysis[] newlyReachableFieldAnalyses;
+  private final NewlyReferencedFieldEnqueuerAnalysis[] newlyReferencedFieldAnalyses;
+  private final NewlyTargetedMethodEnqueuerAnalysis[] newlyTargetedMethodAnalyses;
+
+  // Tear down events.
+  private final FinishedEnqueuerAnalysis[] finishedAnalyses;
+  private final FixpointEnqueuerAnalysis[] fixpointAnalyses;
+
+  private EnqueuerAnalysisCollection(
+      // Trace events.
+      TraceCheckCastEnqueuerAnalysis[] checkCastAnalyses,
+      TraceConstClassEnqueuerAnalysis[] constClassAnalyses,
+      TraceExceptionGuardEnqueuerAnalysis[] exceptionGuardAnalyses,
+      TraceFieldAccessEnqueuerAnalysis[] fieldAccessAnalyses,
+      TraceInstanceOfEnqueuerAnalysis[] instanceOfAnalyses,
+      TraceInvokeEnqueuerAnalysis[] invokeAnalyses,
+      TraceNewInstanceEnqueuerAnalysis[] newInstanceAnalyses,
+      // Reachability events.
+      NewlyFailedMethodResolutionEnqueuerAnalysis[] newlyFailedMethodResolutionAnalyses,
+      NewlyLiveClassEnqueuerAnalysis[] newlyLiveClassAnalyses,
+      NewlyLiveCodeEnqueuerAnalysis[] newlyLiveCodeAnalyses,
+      NewlyLiveFieldEnqueuerAnalysis[] newlyLiveFieldAnalyses,
+      NewlyLiveMethodEnqueuerAnalysis[] newlyLiveMethodAnalyses,
+      NewlyInstantiatedClassEnqueuerAnalysis[] newlyInstantiatedClassAnalyses,
+      NewlyLiveNonProgramClassEnqueuerAnalysis[] newlyLiveNonProgramClassAnalyses,
+      NewlyReachableFieldEnqueuerAnalysis[] newlyReachableFieldAnalyses,
+      NewlyReferencedFieldEnqueuerAnalysis[] newlyReferencedFieldAnalyses,
+      NewlyTargetedMethodEnqueuerAnalysis[] newlyTargetedMethodAnalyses,
+      // Tear down events.
+      FinishedEnqueuerAnalysis[] finishedAnalyses,
+      FixpointEnqueuerAnalysis[] fixpointAnalyses) {
+    // Trace events.
+    this.checkCastAnalyses = checkCastAnalyses;
+    this.constClassAnalyses = constClassAnalyses;
+    this.exceptionGuardAnalyses = exceptionGuardAnalyses;
+    this.fieldAccessAnalyses = fieldAccessAnalyses;
+    this.instanceOfAnalyses = instanceOfAnalyses;
+    this.invokeAnalyses = invokeAnalyses;
+    this.newInstanceAnalyses = newInstanceAnalyses;
+    // Reachability events.
+    this.newlyFailedMethodResolutionAnalyses = newlyFailedMethodResolutionAnalyses;
+    this.newlyLiveClassAnalyses = newlyLiveClassAnalyses;
+    this.newlyLiveCodeAnalyses = newlyLiveCodeAnalyses;
+    this.newlyLiveFieldAnalyses = newlyLiveFieldAnalyses;
+    this.newlyLiveMethodAnalyses = newlyLiveMethodAnalyses;
+    this.newlyInstantiatedClassAnalyses = newlyInstantiatedClassAnalyses;
+    this.newlyLiveNonProgramClassAnalyses = newlyLiveNonProgramClassAnalyses;
+    this.newlyReachableFieldAnalyses = newlyReachableFieldAnalyses;
+    this.newlyReferencedFieldAnalyses = newlyReferencedFieldAnalyses;
+    this.newlyTargetedMethodAnalyses = newlyTargetedMethodAnalyses;
+    // Tear down events.
+    this.finishedAnalyses = finishedAnalyses;
+    this.fixpointAnalyses = fixpointAnalyses;
+  }
+
+  public static Builder builder() {
+    return new Builder();
+  }
+
+  public boolean isEmpty() {
+    return ArrayUtils.isEmpty(checkCastAnalyses)
+        && ArrayUtils.isEmpty(constClassAnalyses)
+        && ArrayUtils.isEmpty(exceptionGuardAnalyses)
+        && ArrayUtils.isEmpty(fieldAccessAnalyses)
+        && ArrayUtils.isEmpty(instanceOfAnalyses)
+        && ArrayUtils.isEmpty(invokeAnalyses)
+        && ArrayUtils.isEmpty(newInstanceAnalyses)
+        && ArrayUtils.isEmpty(newlyFailedMethodResolutionAnalyses)
+        && ArrayUtils.isEmpty(newlyLiveClassAnalyses)
+        && ArrayUtils.isEmpty(newlyLiveCodeAnalyses)
+        && ArrayUtils.isEmpty(newlyLiveFieldAnalyses)
+        && ArrayUtils.isEmpty(newlyLiveMethodAnalyses)
+        && ArrayUtils.isEmpty(newlyInstantiatedClassAnalyses)
+        && ArrayUtils.isEmpty(newlyLiveNonProgramClassAnalyses)
+        && ArrayUtils.isEmpty(newlyReachableFieldAnalyses)
+        && ArrayUtils.isEmpty(newlyReferencedFieldAnalyses)
+        && ArrayUtils.isEmpty(newlyTargetedMethodAnalyses)
+        && ArrayUtils.isEmpty(finishedAnalyses)
+        && ArrayUtils.isEmpty(fixpointAnalyses);
+  }
+
+  // Trace events.
+
+  public void traceCheckCast(DexType type, DexClass clazz, ProgramMethod context) {
+    for (TraceCheckCastEnqueuerAnalysis analysis : checkCastAnalyses) {
+      analysis.traceCheckCast(type, clazz, context);
+    }
+  }
+
+  public void traceSafeCheckCast(DexType type, DexClass clazz, ProgramMethod context) {
+    for (TraceCheckCastEnqueuerAnalysis analysis : checkCastAnalyses) {
+      analysis.traceSafeCheckCast(type, clazz, context);
+    }
+  }
+
+  public void traceConstClass(DexType type, DexClass clazz, ProgramMethod context) {
+    for (TraceConstClassEnqueuerAnalysis analysis : constClassAnalyses) {
+      analysis.traceConstClass(type, clazz, context);
+    }
+  }
+
+  public void traceExceptionGuard(DexType guard, DexClass clazz, ProgramMethod context) {
+    for (TraceExceptionGuardEnqueuerAnalysis analysis : exceptionGuardAnalyses) {
+      analysis.traceExceptionGuard(guard, clazz, context);
+    }
+  }
+
+  public void traceInstanceFieldRead(
+      DexField field,
+      FieldResolutionResult resolutionResult,
+      ProgramMethod context,
+      EnqueuerWorklist worklist) {
+    for (TraceFieldAccessEnqueuerAnalysis analysis : fieldAccessAnalyses) {
+      analysis.traceInstanceFieldRead(field, resolutionResult, context, worklist);
+    }
+  }
+
+  public void traceInstanceFieldWrite(
+      DexField field,
+      FieldResolutionResult resolutionResult,
+      ProgramMethod context,
+      EnqueuerWorklist worklist) {
+    for (TraceFieldAccessEnqueuerAnalysis analysis : fieldAccessAnalyses) {
+      analysis.traceInstanceFieldWrite(field, resolutionResult, context, worklist);
+    }
+  }
+
+  public void traceStaticFieldRead(
+      DexField field,
+      SingleFieldResolutionResult<?> resolutionResult,
+      ProgramMethod context,
+      EnqueuerWorklist worklist) {
+    for (TraceFieldAccessEnqueuerAnalysis analysis : fieldAccessAnalyses) {
+      analysis.traceStaticFieldRead(field, resolutionResult, context, worklist);
+    }
+  }
+
+  public void traceStaticFieldWrite(
+      DexField field,
+      FieldResolutionResult resolutionResult,
+      ProgramMethod context,
+      EnqueuerWorklist worklist) {
+    for (TraceFieldAccessEnqueuerAnalysis analysis : fieldAccessAnalyses) {
+      analysis.traceStaticFieldWrite(field, resolutionResult, context, worklist);
+    }
+  }
+
+  public void traceInstanceOf(DexType type, DexClass clazz, ProgramMethod context) {
+    for (TraceInstanceOfEnqueuerAnalysis analysis : instanceOfAnalyses) {
+      analysis.traceInstanceOf(type, clazz, context);
+    }
+  }
+
+  public void traceInvokeStatic(
+      DexMethod invokedMethod, MethodResolutionResult resolutionResult, ProgramMethod context) {
+    for (TraceInvokeEnqueuerAnalysis analysis : invokeAnalyses) {
+      analysis.traceInvokeStatic(invokedMethod, resolutionResult, context);
+    }
+  }
+
+  public void traceInvokeDirect(
+      DexMethod invokedMethod, MethodResolutionResult resolutionResult, ProgramMethod context) {
+    for (TraceInvokeEnqueuerAnalysis analysis : invokeAnalyses) {
+      analysis.traceInvokeDirect(invokedMethod, resolutionResult, context);
+    }
+  }
+
+  public void traceInvokeInterface(
+      DexMethod invokedMethod, MethodResolutionResult resolutionResult, ProgramMethod context) {
+    for (TraceInvokeEnqueuerAnalysis analysis : invokeAnalyses) {
+      analysis.traceInvokeInterface(invokedMethod, resolutionResult, context);
+    }
+  }
+
+  public void traceInvokeSuper(
+      DexMethod invokedMethod, MethodResolutionResult resolutionResult, ProgramMethod context) {
+    for (TraceInvokeEnqueuerAnalysis analysis : invokeAnalyses) {
+      analysis.traceInvokeSuper(invokedMethod, resolutionResult, context);
+    }
+  }
+
+  public void traceInvokeVirtual(
+      DexMethod invokedMethod, MethodResolutionResult resolutionResult, ProgramMethod context) {
+    for (TraceInvokeEnqueuerAnalysis analysis : invokeAnalyses) {
+      analysis.traceInvokeVirtual(invokedMethod, resolutionResult, context);
+    }
+  }
+
+  public void traceNewInstance(DexType type, DexClass clazz, ProgramMethod context) {
+    for (TraceNewInstanceEnqueuerAnalysis analysis : newInstanceAnalyses) {
+      analysis.traceNewInstance(type, clazz, context);
+    }
+  }
+
+  // Reachability events.
+
+  public void processNewlyFailedMethodResolutionTarget(
+      DexEncodedMethod method, EnqueuerWorklist worklist) {
+    for (NewlyFailedMethodResolutionEnqueuerAnalysis analysis :
+        newlyFailedMethodResolutionAnalyses) {
+      analysis.processNewlyFailedMethodResolutionTarget(method, worklist);
+    }
+  }
+
+  public void processNewlyLiveClass(DexProgramClass clazz, EnqueuerWorklist worklist) {
+    for (NewlyLiveClassEnqueuerAnalysis analysis : newlyLiveClassAnalyses) {
+      analysis.processNewlyLiveClass(clazz, worklist);
+    }
+  }
+
+  public void processNewlyLiveCode(
+      ProgramMethod method, DefaultEnqueuerUseRegistry registry, EnqueuerWorklist worklist) {
+    for (NewlyLiveCodeEnqueuerAnalysis analysis : newlyLiveCodeAnalyses) {
+      analysis.processNewlyLiveCode(method, registry, worklist);
+    }
+  }
+
+  public void processNewlyLiveField(
+      ProgramField field, ProgramDefinition context, EnqueuerWorklist worklist) {
+    for (NewlyLiveFieldEnqueuerAnalysis analysis : newlyLiveFieldAnalyses) {
+      analysis.processNewlyLiveField(field, context, worklist);
+    }
+  }
+
+  public void processNewlyLiveMethod(
+      ProgramMethod method,
+      ProgramDefinition context,
+      Enqueuer enqueuer,
+      EnqueuerWorklist worklist) {
+    for (NewlyLiveMethodEnqueuerAnalysis analysis : newlyLiveMethodAnalyses) {
+      analysis.processNewlyLiveMethod(method, context, enqueuer, worklist);
+    }
+  }
+
+  public void processNewlyInstantiatedClass(
+      DexProgramClass clazz, ProgramMethod context, EnqueuerWorklist worklist) {
+    for (NewlyInstantiatedClassEnqueuerAnalysis analysis : newlyInstantiatedClassAnalyses) {
+      analysis.processNewlyInstantiatedClass(clazz, context, worklist);
+    }
+  }
+
+  public void processNewlyLiveNonProgramType(ClasspathOrLibraryClass clazz) {
+    for (NewlyLiveNonProgramClassEnqueuerAnalysis analysis : newlyLiveNonProgramClassAnalyses) {
+      analysis.processNewlyLiveNonProgramType(clazz);
+    }
+  }
+
+  public void processNewlyReachableField(ProgramField field, EnqueuerWorklist worklist) {
+    for (NewlyReachableFieldEnqueuerAnalysis analysis : newlyReachableFieldAnalyses) {
+      analysis.processNewlyReachableField(field, worklist);
+    }
+  }
+
+  public void processNewlyReferencedField(ProgramField field) {
+    for (NewlyReferencedFieldEnqueuerAnalysis analysis : newlyReferencedFieldAnalyses) {
+      analysis.processNewlyReferencedField(field);
+    }
+  }
+
+  public void processNewlyTargetedMethod(ProgramMethod method, EnqueuerWorklist worklist) {
+    for (NewlyTargetedMethodEnqueuerAnalysis analysis : newlyTargetedMethodAnalyses) {
+      analysis.processNewlyTargetedMethod(method, worklist);
+    }
+  }
+
+  // Tear down events.
+
+  public void done(Enqueuer enqueuer) {
+    for (FinishedEnqueuerAnalysis analysis : finishedAnalyses) {
+      analysis.done(enqueuer);
+    }
+  }
+
+  public void notifyFixpoint(
+      Enqueuer enqueuer, EnqueuerWorklist worklist, ExecutorService executorService, Timing timing)
+      throws ExecutionException {
+    for (FixpointEnqueuerAnalysis analysis : fixpointAnalyses) {
+      analysis.notifyFixpoint(enqueuer, worklist, executorService, timing);
+    }
+  }
+
+  public static class Builder {
+
+    // Trace events.
+    private final List<TraceCheckCastEnqueuerAnalysis> checkCastAnalyses = new ArrayList<>();
+    private final List<TraceConstClassEnqueuerAnalysis> constClassAnalyses = new ArrayList<>();
+    private final List<TraceExceptionGuardEnqueuerAnalysis> exceptionGuardAnalyses =
+        new ArrayList<>();
+    private final List<TraceFieldAccessEnqueuerAnalysis> fieldAccessAnalyses = new ArrayList<>();
+    private final List<TraceInstanceOfEnqueuerAnalysis> instanceOfAnalyses = new ArrayList<>();
+    private final List<TraceInvokeEnqueuerAnalysis> invokeAnalyses = new ArrayList<>();
+    private final List<TraceNewInstanceEnqueuerAnalysis> newInstanceAnalyses = new ArrayList<>();
+
+    // Reachability events.
+    private final List<NewlyFailedMethodResolutionEnqueuerAnalysis>
+        newlyFailedMethodResolutionAnalyses = new ArrayList<>();
+    private final List<NewlyLiveClassEnqueuerAnalysis> newlyLiveClassAnalyses = new ArrayList<>();
+    private final List<NewlyLiveCodeEnqueuerAnalysis> newlyLiveCodeAnalyses = new ArrayList<>();
+    private final List<NewlyLiveFieldEnqueuerAnalysis> newlyLiveFieldAnalyses = new ArrayList<>();
+    private final List<NewlyLiveMethodEnqueuerAnalysis> newlyLiveMethodAnalyses = new ArrayList<>();
+    private final List<NewlyInstantiatedClassEnqueuerAnalysis> newlyInstantiatedClassAnalyses =
+        new ArrayList<>();
+    private final List<NewlyLiveNonProgramClassEnqueuerAnalysis> newlyLiveNonProgramClassAnalyses =
+        new ArrayList<>();
+    private final List<NewlyReachableFieldEnqueuerAnalysis> newlyReachableFieldAnalyses =
+        new ArrayList<>();
+    private final List<NewlyReferencedFieldEnqueuerAnalysis> newlyReferencedFieldAnalyses =
+        new ArrayList<>();
+    private final List<NewlyTargetedMethodEnqueuerAnalysis> newlyTargetedMethodAnalyses =
+        new ArrayList<>();
+
+    // Tear down events.
+    private final List<FinishedEnqueuerAnalysis> finishedAnalyses = new ArrayList<>();
+    private final List<FixpointEnqueuerAnalysis> fixpointAnalyses = new ArrayList<>();
+
+    private Builder() {}
+
+    // Trace events.
+
+    public Builder addTraceCheckCastAnalysis(TraceCheckCastEnqueuerAnalysis analysis) {
+      checkCastAnalyses.add(analysis);
+      return this;
+    }
+
+    public Builder addTraceConstClassAnalysis(TraceConstClassEnqueuerAnalysis analysis) {
+      constClassAnalyses.add(analysis);
+      return this;
+    }
+
+    public Builder addTraceExceptionGuardAnalysis(TraceExceptionGuardEnqueuerAnalysis analysis) {
+      exceptionGuardAnalyses.add(analysis);
+      return this;
+    }
+
+    public Builder addTraceFieldAccessAnalysis(TraceFieldAccessEnqueuerAnalysis analysis) {
+      fieldAccessAnalyses.add(analysis);
+      return this;
+    }
+
+    public Builder addTraceInstanceOfAnalysis(TraceInstanceOfEnqueuerAnalysis analysis) {
+      instanceOfAnalyses.add(analysis);
+      return this;
+    }
+
+    public Builder addTraceInvokeAnalysis(TraceInvokeEnqueuerAnalysis analysis) {
+      invokeAnalyses.add(analysis);
+      return this;
+    }
+
+    public Builder addTraceNewInstanceAnalysis(TraceNewInstanceEnqueuerAnalysis analysis) {
+      newInstanceAnalyses.add(analysis);
+      return this;
+    }
+
+    // Reachability events.
+
+    public Builder addNewlyFailedMethodResolutionAnalysis(
+        NewlyFailedMethodResolutionEnqueuerAnalysis analysis) {
+      newlyFailedMethodResolutionAnalyses.add(analysis);
+      return this;
+    }
+
+    public Builder addNewlyLiveClassAnalysis(NewlyLiveClassEnqueuerAnalysis analysis) {
+      newlyLiveClassAnalyses.add(analysis);
+      return this;
+    }
+
+    public Builder addNewlyLiveCodeAnalysis(NewlyLiveCodeEnqueuerAnalysis analysis) {
+      newlyLiveCodeAnalyses.add(analysis);
+      return this;
+    }
+
+    public Builder addNewlyLiveFieldAnalysis(NewlyLiveFieldEnqueuerAnalysis analysis) {
+      newlyLiveFieldAnalyses.add(analysis);
+      return this;
+    }
+
+    public Builder addNewlyLiveMethodAnalysis(NewlyLiveMethodEnqueuerAnalysis analysis) {
+      newlyLiveMethodAnalyses.add(analysis);
+      return this;
+    }
+
+    public Builder addNewlyInstantiatedClassAnalysis(
+        NewlyInstantiatedClassEnqueuerAnalysis analysis) {
+      newlyInstantiatedClassAnalyses.add(analysis);
+      return this;
+    }
+
+    public Builder addNewlyLiveNonProgramClassAnalysis(
+        NewlyLiveNonProgramClassEnqueuerAnalysis analysis) {
+      newlyLiveNonProgramClassAnalyses.add(analysis);
+      return this;
+    }
+
+    public Builder addNewlyReachableFieldAnalysis(NewlyReachableFieldEnqueuerAnalysis analysis) {
+      newlyReachableFieldAnalyses.add(analysis);
+      return this;
+    }
+
+    public Builder addNewlyReferencedFieldAnalysis(NewlyReferencedFieldEnqueuerAnalysis analysis) {
+      newlyReferencedFieldAnalyses.add(analysis);
+      return this;
+    }
+
+    public Builder addNewlyTargetedMethodAnalysis(NewlyTargetedMethodEnqueuerAnalysis analysis) {
+      newlyTargetedMethodAnalyses.add(analysis);
+      return this;
+    }
+
+    // Tear down events.
+
+    public Builder addFinishedAnalysis(FinishedEnqueuerAnalysis analysis) {
+      finishedAnalyses.add(analysis);
+      return this;
+    }
+
+    public Builder addFixpointAnalysis(FixpointEnqueuerAnalysis analysis) {
+      fixpointAnalyses.add(analysis);
+      return this;
+    }
+
+    public EnqueuerAnalysisCollection build() {
+      return new EnqueuerAnalysisCollection(
+          // Trace events.
+          checkCastAnalyses.toArray(TraceCheckCastEnqueuerAnalysis[]::new),
+          constClassAnalyses.toArray(TraceConstClassEnqueuerAnalysis[]::new),
+          exceptionGuardAnalyses.toArray(TraceExceptionGuardEnqueuerAnalysis[]::new),
+          fieldAccessAnalyses.toArray(TraceFieldAccessEnqueuerAnalysis[]::new),
+          instanceOfAnalyses.toArray(TraceInstanceOfEnqueuerAnalysis[]::new),
+          invokeAnalyses.toArray(TraceInvokeEnqueuerAnalysis[]::new),
+          newInstanceAnalyses.toArray(TraceNewInstanceEnqueuerAnalysis[]::new),
+          // Reachability events.
+          newlyFailedMethodResolutionAnalyses.toArray(
+              NewlyFailedMethodResolutionEnqueuerAnalysis[]::new),
+          newlyLiveClassAnalyses.toArray(NewlyLiveClassEnqueuerAnalysis[]::new),
+          newlyLiveCodeAnalyses.toArray(NewlyLiveCodeEnqueuerAnalysis[]::new),
+          newlyLiveFieldAnalyses.toArray(NewlyLiveFieldEnqueuerAnalysis[]::new),
+          newlyLiveMethodAnalyses.toArray(NewlyLiveMethodEnqueuerAnalysis[]::new),
+          newlyInstantiatedClassAnalyses.toArray(NewlyInstantiatedClassEnqueuerAnalysis[]::new),
+          newlyLiveNonProgramClassAnalyses.toArray(NewlyLiveNonProgramClassEnqueuerAnalysis[]::new),
+          newlyReachableFieldAnalyses.toArray(NewlyReachableFieldEnqueuerAnalysis[]::new),
+          newlyReferencedFieldAnalyses.toArray(NewlyReferencedFieldEnqueuerAnalysis[]::new),
+          newlyTargetedMethodAnalyses.toArray(NewlyTargetedMethodEnqueuerAnalysis[]::new),
+          // Tear down events.
+          finishedAnalyses.toArray(FinishedEnqueuerAnalysis[]::new),
+          fixpointAnalyses.toArray(FixpointEnqueuerAnalysis[]::new));
+    }
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/graph/analysis/EnqueuerTypeAccessAnalysis.java b/src/main/java/com/android/tools/r8/graph/analysis/EnqueuerTypeAccessAnalysis.java
index 3d44ef1..cb76002 100644
--- a/src/main/java/com/android/tools/r8/graph/analysis/EnqueuerTypeAccessAnalysis.java
+++ b/src/main/java/com/android/tools/r8/graph/analysis/EnqueuerTypeAccessAnalysis.java
@@ -4,8 +4,8 @@
 package com.android.tools.r8.graph.analysis;
 
 public interface EnqueuerTypeAccessAnalysis
-    extends EnqueuerCheckCastAnalysis,
-        EnqueuerConstClassAnalysis,
-        EnqueuerExceptionGuardAnalysis,
-        EnqueuerInstanceOfAnalysis,
-        EnqueuerNewInstanceAnalysis {}
+    extends TraceCheckCastEnqueuerAnalysis,
+        TraceConstClassEnqueuerAnalysis,
+        TraceExceptionGuardEnqueuerAnalysis,
+        TraceInstanceOfEnqueuerAnalysis,
+        TraceNewInstanceEnqueuerAnalysis {}
diff --git a/src/main/java/com/android/tools/r8/graph/analysis/FinishedEnqueuerAnalysis.java b/src/main/java/com/android/tools/r8/graph/analysis/FinishedEnqueuerAnalysis.java
new file mode 100644
index 0000000..54cabca
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/graph/analysis/FinishedEnqueuerAnalysis.java
@@ -0,0 +1,15 @@
+// Copyright (c) 2024, the R8 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.
+package com.android.tools.r8.graph.analysis;
+
+import com.android.tools.r8.shaking.Enqueuer;
+
+public interface FinishedEnqueuerAnalysis {
+
+  /**
+   * Called when the Enqueuer has reached the final fixpoint. Each analysis may use this callback to
+   * perform some post-processing.
+   */
+  default void done(Enqueuer enqueuer) {}
+}
diff --git a/src/main/java/com/android/tools/r8/graph/analysis/FixpointEnqueuerAnalysis.java b/src/main/java/com/android/tools/r8/graph/analysis/FixpointEnqueuerAnalysis.java
new file mode 100644
index 0000000..9f67337
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/graph/analysis/FixpointEnqueuerAnalysis.java
@@ -0,0 +1,21 @@
+// Copyright (c) 2024, the R8 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.
+package com.android.tools.r8.graph.analysis;
+
+import com.android.tools.r8.shaking.Enqueuer;
+import com.android.tools.r8.shaking.EnqueuerWorklist;
+import com.android.tools.r8.utils.Timing;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
+
+public interface FixpointEnqueuerAnalysis {
+
+  /**
+   * Called when the Enqueuer reaches a fixpoint. This may happen multiple times, since each
+   * analysis may enqueue items into the worklist upon the fixpoint using {@param worklist}.
+   */
+  default void notifyFixpoint(
+      Enqueuer enqueuer, EnqueuerWorklist worklist, ExecutorService executorService, Timing timing)
+      throws ExecutionException {}
+}
diff --git a/src/main/java/com/android/tools/r8/graph/analysis/GetArrayOfMissingTypeVerifyErrorWorkaround.java b/src/main/java/com/android/tools/r8/graph/analysis/GetArrayOfMissingTypeVerifyErrorWorkaround.java
index ed49777..bace0d4 100644
--- a/src/main/java/com/android/tools/r8/graph/analysis/GetArrayOfMissingTypeVerifyErrorWorkaround.java
+++ b/src/main/java/com/android/tools/r8/graph/analysis/GetArrayOfMissingTypeVerifyErrorWorkaround.java
@@ -34,7 +34,8 @@
  * or class merging), and thereby causes new classes to no longer verify on Dalvik, we soft-pin
  * methods that reads such fields.
  */
-public class GetArrayOfMissingTypeVerifyErrorWorkaround implements EnqueuerFieldAccessAnalysis {
+public class GetArrayOfMissingTypeVerifyErrorWorkaround
+    implements TraceFieldAccessEnqueuerAnalysis {
 
   private final DexItemFactory dexItemFactory;
   private final Enqueuer enqueuer;
@@ -48,9 +49,11 @@
   }
 
   public static void register(
-      AppView<? extends AppInfoWithClassHierarchy> appView, Enqueuer enqueuer) {
+      AppView<? extends AppInfoWithClassHierarchy> appView,
+      Enqueuer enqueuer,
+      EnqueuerAnalysisCollection.Builder builder) {
     if (!isNoop(appView)) {
-      enqueuer.registerFieldAccessAnalysis(
+      builder.addTraceFieldAccessAnalysis(
           new GetArrayOfMissingTypeVerifyErrorWorkaround(appView, enqueuer));
     }
   }
diff --git a/src/main/java/com/android/tools/r8/graph/analysis/InitializedClassesInInstanceMethodsAnalysis.java b/src/main/java/com/android/tools/r8/graph/analysis/InitializedClassesInInstanceMethodsAnalysis.java
index ddb64d7..2a84623 100644
--- a/src/main/java/com/android/tools/r8/graph/analysis/InitializedClassesInInstanceMethodsAnalysis.java
+++ b/src/main/java/com/android/tools/r8/graph/analysis/InitializedClassesInInstanceMethodsAnalysis.java
@@ -17,7 +17,8 @@
 import java.util.IdentityHashMap;
 import java.util.Map;
 
-public class InitializedClassesInInstanceMethodsAnalysis extends EnqueuerAnalysis {
+public class InitializedClassesInInstanceMethodsAnalysis
+    implements FinishedEnqueuerAnalysis, NewlyInstantiatedClassEnqueuerAnalysis {
 
   // A simple structure that stores the result of the analysis.
   public static class InitializedClassesInInstanceMethods {
@@ -86,10 +87,14 @@
   }
 
   public static void register(
-      AppView<? extends AppInfoWithClassHierarchy> appView, Enqueuer enqueuer) {
+      AppView<? extends AppInfoWithClassHierarchy> appView,
+      Enqueuer enqueuer,
+      EnqueuerAnalysisCollection.Builder builder) {
     if (appView.options().enableInitializedClassesInInstanceMethodsAnalysis
         && enqueuer.getMode().isInitialTreeShaking()) {
-      enqueuer.registerAnalysis(new InitializedClassesInInstanceMethodsAnalysis(appView));
+      InitializedClassesInInstanceMethodsAnalysis analysis =
+          new InitializedClassesInInstanceMethodsAnalysis(appView);
+      builder.addNewlyInstantiatedClassAnalysis(analysis).addFinishedAnalysis(analysis);
     } else {
       appView.setInitializedClassesInInstanceMethods(null);
     }
diff --git a/src/main/java/com/android/tools/r8/graph/analysis/InvokeVirtualToInterfaceVerifyErrorWorkaround.java b/src/main/java/com/android/tools/r8/graph/analysis/InvokeVirtualToInterfaceVerifyErrorWorkaround.java
index 70fca76..b5bf374 100644
--- a/src/main/java/com/android/tools/r8/graph/analysis/InvokeVirtualToInterfaceVerifyErrorWorkaround.java
+++ b/src/main/java/com/android/tools/r8/graph/analysis/InvokeVirtualToInterfaceVerifyErrorWorkaround.java
@@ -27,7 +27,7 @@
 // TODO(b/206891715): This only mitigates the issue. The user may still need to manually outline
 //  virtual invokes to classes that was once an interface. To avoid this in general (including D8)
 //  the compiler should outline the problematic invokes.
-public class InvokeVirtualToInterfaceVerifyErrorWorkaround implements EnqueuerInvokeAnalysis {
+public class InvokeVirtualToInterfaceVerifyErrorWorkaround implements TraceInvokeEnqueuerAnalysis {
 
   private final DexType androidHardwareCamera2CameraDeviceType;
   private final Enqueuer enqueuer;
@@ -42,9 +42,11 @@
   }
 
   public static void register(
-      AppView<? extends AppInfoWithClassHierarchy> appView, Enqueuer enqueuer) {
+      AppView<? extends AppInfoWithClassHierarchy> appView,
+      Enqueuer enqueuer,
+      EnqueuerAnalysisCollection.Builder builder) {
     if (!isNoop(appView)) {
-      enqueuer.registerInvokeAnalysis(
+      builder.addTraceInvokeAnalysis(
           new InvokeVirtualToInterfaceVerifyErrorWorkaround(appView, enqueuer));
     }
   }
@@ -61,36 +63,11 @@
     }
   }
 
-  @SuppressWarnings("ReferenceEquality")
   private boolean isInterfaceInSomeApiLevel(DexType type) {
     // CameraDevice was added as a class in API 21 (L), but was defined as an interface in the
     // framework before then.
-    return type == androidHardwareCamera2CameraDeviceType
+    return type.isIdenticalTo(androidHardwareCamera2CameraDeviceType)
         && (options.isGeneratingClassFiles()
             || options.getMinApiLevel().isLessThan(AndroidApiLevel.L));
   }
-
-  @Override
-  public void traceInvokeDirect(
-      DexMethod invokedMethod, MethodResolutionResult resolutionResult, ProgramMethod context) {
-    // Intentionally empty.
-  }
-
-  @Override
-  public void traceInvokeInterface(
-      DexMethod invokedMethod, MethodResolutionResult resolutionResult, ProgramMethod context) {
-    // Intentionally empty.
-  }
-
-  @Override
-  public void traceInvokeStatic(
-      DexMethod invokedMethod, MethodResolutionResult resolutionResult, ProgramMethod context) {
-    // Intentionally empty.
-  }
-
-  @Override
-  public void traceInvokeSuper(
-      DexMethod invokedMethod, MethodResolutionResult resolutionResult, ProgramMethod context) {
-    // Intentionally empty.
-  }
 }
diff --git a/src/main/java/com/android/tools/r8/graph/analysis/NewlyFailedMethodResolutionEnqueuerAnalysis.java b/src/main/java/com/android/tools/r8/graph/analysis/NewlyFailedMethodResolutionEnqueuerAnalysis.java
new file mode 100644
index 0000000..4d6fc73
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/graph/analysis/NewlyFailedMethodResolutionEnqueuerAnalysis.java
@@ -0,0 +1,13 @@
+// Copyright (c) 2024, the R8 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.
+package com.android.tools.r8.graph.analysis;
+
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.shaking.EnqueuerWorklist;
+
+public interface NewlyFailedMethodResolutionEnqueuerAnalysis {
+
+  default void processNewlyFailedMethodResolutionTarget(
+      DexEncodedMethod method, EnqueuerWorklist worklist) {}
+}
diff --git a/src/main/java/com/android/tools/r8/graph/analysis/NewlyInstantiatedClassEnqueuerAnalysis.java b/src/main/java/com/android/tools/r8/graph/analysis/NewlyInstantiatedClassEnqueuerAnalysis.java
new file mode 100644
index 0000000..c3eb799
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/graph/analysis/NewlyInstantiatedClassEnqueuerAnalysis.java
@@ -0,0 +1,15 @@
+// Copyright (c) 2024, the R8 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.
+package com.android.tools.r8.graph.analysis;
+
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.shaking.EnqueuerWorklist;
+
+public interface NewlyInstantiatedClassEnqueuerAnalysis {
+
+  /** Called when a class is found to be instantiated. */
+  default void processNewlyInstantiatedClass(
+      DexProgramClass clazz, ProgramMethod context, EnqueuerWorklist worklist) {}
+}
diff --git a/src/main/java/com/android/tools/r8/graph/analysis/NewlyLiveClassEnqueuerAnalysis.java b/src/main/java/com/android/tools/r8/graph/analysis/NewlyLiveClassEnqueuerAnalysis.java
new file mode 100644
index 0000000..e681269
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/graph/analysis/NewlyLiveClassEnqueuerAnalysis.java
@@ -0,0 +1,13 @@
+// Copyright (c) 2024, the R8 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.
+package com.android.tools.r8.graph.analysis;
+
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.shaking.EnqueuerWorklist;
+
+public interface NewlyLiveClassEnqueuerAnalysis {
+
+  /** Called when a class is found to be live. */
+  default void processNewlyLiveClass(DexProgramClass clazz, EnqueuerWorklist worklist) {}
+}
diff --git a/src/main/java/com/android/tools/r8/graph/analysis/NewlyLiveCodeEnqueuerAnalysis.java b/src/main/java/com/android/tools/r8/graph/analysis/NewlyLiveCodeEnqueuerAnalysis.java
new file mode 100644
index 0000000..50f934f
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/graph/analysis/NewlyLiveCodeEnqueuerAnalysis.java
@@ -0,0 +1,15 @@
+// Copyright (c) 2024, the R8 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.
+package com.android.tools.r8.graph.analysis;
+
+import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.shaking.DefaultEnqueuerUseRegistry;
+import com.android.tools.r8.shaking.EnqueuerWorklist;
+
+public interface NewlyLiveCodeEnqueuerAnalysis {
+
+  /** Called when a method's code has been processed by the registry. */
+  default void processNewlyLiveCode(
+      ProgramMethod method, DefaultEnqueuerUseRegistry registry, EnqueuerWorklist worklist) {}
+}
diff --git a/src/main/java/com/android/tools/r8/graph/analysis/NewlyLiveFieldEnqueuerAnalysis.java b/src/main/java/com/android/tools/r8/graph/analysis/NewlyLiveFieldEnqueuerAnalysis.java
new file mode 100644
index 0000000..22c0d70
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/graph/analysis/NewlyLiveFieldEnqueuerAnalysis.java
@@ -0,0 +1,15 @@
+// Copyright (c) 2024, the R8 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.
+package com.android.tools.r8.graph.analysis;
+
+import com.android.tools.r8.graph.ProgramDefinition;
+import com.android.tools.r8.graph.ProgramField;
+import com.android.tools.r8.shaking.EnqueuerWorklist;
+
+public interface NewlyLiveFieldEnqueuerAnalysis {
+
+  /** Called when a field is found to be live. */
+  default void processNewlyLiveField(
+      ProgramField field, ProgramDefinition context, EnqueuerWorklist worklist) {}
+}
diff --git a/src/main/java/com/android/tools/r8/graph/analysis/NewlyLiveMethodEnqueuerAnalysis.java b/src/main/java/com/android/tools/r8/graph/analysis/NewlyLiveMethodEnqueuerAnalysis.java
new file mode 100644
index 0000000..6c6df87
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/graph/analysis/NewlyLiveMethodEnqueuerAnalysis.java
@@ -0,0 +1,19 @@
+// Copyright (c) 2024, the R8 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.
+package com.android.tools.r8.graph.analysis;
+
+import com.android.tools.r8.graph.ProgramDefinition;
+import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.shaking.Enqueuer;
+import com.android.tools.r8.shaking.EnqueuerWorklist;
+
+public interface NewlyLiveMethodEnqueuerAnalysis {
+
+  /** Called when a method is found to be live. */
+  default void processNewlyLiveMethod(
+      ProgramMethod method,
+      ProgramDefinition context,
+      Enqueuer enqueuer,
+      EnqueuerWorklist worklist) {}
+}
diff --git a/src/main/java/com/android/tools/r8/graph/analysis/NewlyLiveNonProgramClassEnqueuerAnalysis.java b/src/main/java/com/android/tools/r8/graph/analysis/NewlyLiveNonProgramClassEnqueuerAnalysis.java
new file mode 100644
index 0000000..dbf980a
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/graph/analysis/NewlyLiveNonProgramClassEnqueuerAnalysis.java
@@ -0,0 +1,12 @@
+// Copyright (c) 2024, the R8 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.
+package com.android.tools.r8.graph.analysis;
+
+import com.android.tools.r8.graph.ClasspathOrLibraryClass;
+
+public interface NewlyLiveNonProgramClassEnqueuerAnalysis {
+
+  /** Called when a non program class is visited and marked live */
+  default void processNewlyLiveNonProgramType(ClasspathOrLibraryClass clazz) {}
+}
diff --git a/src/main/java/com/android/tools/r8/graph/analysis/NewlyReachableFieldEnqueuerAnalysis.java b/src/main/java/com/android/tools/r8/graph/analysis/NewlyReachableFieldEnqueuerAnalysis.java
new file mode 100644
index 0000000..bfa942a
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/graph/analysis/NewlyReachableFieldEnqueuerAnalysis.java
@@ -0,0 +1,12 @@
+// Copyright (c) 2024, the R8 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.
+package com.android.tools.r8.graph.analysis;
+
+import com.android.tools.r8.graph.ProgramField;
+import com.android.tools.r8.shaking.EnqueuerWorklist;
+
+public interface NewlyReachableFieldEnqueuerAnalysis {
+
+  default void processNewlyReachableField(ProgramField field, EnqueuerWorklist worklist) {}
+}
diff --git a/src/main/java/com/android/tools/r8/graph/analysis/NewlyReferencedFieldEnqueuerAnalysis.java b/src/main/java/com/android/tools/r8/graph/analysis/NewlyReferencedFieldEnqueuerAnalysis.java
new file mode 100644
index 0000000..96c15fc
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/graph/analysis/NewlyReferencedFieldEnqueuerAnalysis.java
@@ -0,0 +1,11 @@
+// Copyright (c) 2024, the R8 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.
+package com.android.tools.r8.graph.analysis;
+
+import com.android.tools.r8.graph.ProgramField;
+
+public interface NewlyReferencedFieldEnqueuerAnalysis {
+
+  default void processNewlyReferencedField(ProgramField field) {}
+}
diff --git a/src/main/java/com/android/tools/r8/graph/analysis/NewlyTargetedMethodEnqueuerAnalysis.java b/src/main/java/com/android/tools/r8/graph/analysis/NewlyTargetedMethodEnqueuerAnalysis.java
new file mode 100644
index 0000000..5bd7789
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/graph/analysis/NewlyTargetedMethodEnqueuerAnalysis.java
@@ -0,0 +1,12 @@
+// Copyright (c) 2024, the R8 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.
+package com.android.tools.r8.graph.analysis;
+
+import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.shaking.EnqueuerWorklist;
+
+public interface NewlyTargetedMethodEnqueuerAnalysis {
+
+  default void processNewlyTargetedMethod(ProgramMethod method, EnqueuerWorklist worklist) {}
+}
diff --git a/src/main/java/com/android/tools/r8/graph/analysis/ResourceAccessAnalysis.java b/src/main/java/com/android/tools/r8/graph/analysis/ResourceAccessAnalysis.java
index 3d1cb69..14aa18a 100644
--- a/src/main/java/com/android/tools/r8/graph/analysis/ResourceAccessAnalysis.java
+++ b/src/main/java/com/android/tools/r8/graph/analysis/ResourceAccessAnalysis.java
@@ -31,7 +31,7 @@
 import java.util.IdentityHashMap;
 import java.util.Map;
 
-public class ResourceAccessAnalysis implements EnqueuerFieldAccessAnalysis {
+public class ResourceAccessAnalysis implements TraceFieldAccessEnqueuerAnalysis {
 
   private final R8ResourceShrinkerState resourceShrinkerState;
   private final Map<DexType, RClassFieldToValueStore> fieldToValueMapping = new IdentityHashMap<>();
@@ -46,13 +46,15 @@
   }
 
   public static void register(
-      AppView<? extends AppInfoWithClassHierarchy> appView, Enqueuer enqueuer) {
+      AppView<? extends AppInfoWithClassHierarchy> appView,
+      Enqueuer enqueuer,
+      EnqueuerAnalysisCollection.Builder builder) {
     if (fieldAccessAnalysisEnabled(appView, enqueuer)) {
-      enqueuer.registerFieldAccessAnalysis(new ResourceAccessAnalysis(appView, enqueuer));
+      builder.addTraceFieldAccessAnalysis(new ResourceAccessAnalysis(appView, enqueuer));
     }
     if (liveFieldAnalysisEnabled(appView, enqueuer)) {
-      enqueuer.registerAnalysis(
-          new EnqueuerAnalysis() {
+      builder.addNewlyLiveFieldAnalysis(
+          new NewlyLiveFieldEnqueuerAnalysis() {
             @Override
             public void processNewlyLiveField(
                 ProgramField field, ProgramDefinition context, EnqueuerWorklist worklist) {
@@ -69,11 +71,6 @@
     }
   }
 
-  @Override
-  public void done(Enqueuer enqueuer) {
-    EnqueuerFieldAccessAnalysis.super.done(enqueuer);
-  }
-
   private static boolean liveFieldAnalysisEnabled(
       AppView<? extends AppInfoWithClassHierarchy> appView, Enqueuer enqueuer) {
     return appView.options().androidResourceProvider != null
diff --git a/src/main/java/com/android/tools/r8/graph/analysis/EnqueuerCheckCastAnalysis.java b/src/main/java/com/android/tools/r8/graph/analysis/TraceCheckCastEnqueuerAnalysis.java
similarity index 81%
rename from src/main/java/com/android/tools/r8/graph/analysis/EnqueuerCheckCastAnalysis.java
rename to src/main/java/com/android/tools/r8/graph/analysis/TraceCheckCastEnqueuerAnalysis.java
index 1ff19a6..a6362a2 100644
--- a/src/main/java/com/android/tools/r8/graph/analysis/EnqueuerCheckCastAnalysis.java
+++ b/src/main/java/com/android/tools/r8/graph/analysis/TraceCheckCastEnqueuerAnalysis.java
@@ -1,14 +1,13 @@
-// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// Copyright (c) 2024, the R8 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.
-
 package com.android.tools.r8.graph.analysis;
 
 import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.ProgramMethod;
 
-public interface EnqueuerCheckCastAnalysis {
+public interface TraceCheckCastEnqueuerAnalysis {
 
   void traceCheckCast(DexType type, DexClass clazz, ProgramMethod context);
 
diff --git a/src/main/java/com/android/tools/r8/graph/analysis/EnqueuerConstClassAnalysis.java b/src/main/java/com/android/tools/r8/graph/analysis/TraceConstClassEnqueuerAnalysis.java
similarity index 78%
rename from src/main/java/com/android/tools/r8/graph/analysis/EnqueuerConstClassAnalysis.java
rename to src/main/java/com/android/tools/r8/graph/analysis/TraceConstClassEnqueuerAnalysis.java
index 61f127a..259fe33 100644
--- a/src/main/java/com/android/tools/r8/graph/analysis/EnqueuerConstClassAnalysis.java
+++ b/src/main/java/com/android/tools/r8/graph/analysis/TraceConstClassEnqueuerAnalysis.java
@@ -1,4 +1,4 @@
-// Copyright (c) 2023, the R8 project authors. Please see the AUTHORS file
+// Copyright (c) 2024, the R8 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.
 package com.android.tools.r8.graph.analysis;
@@ -7,7 +7,7 @@
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.ProgramMethod;
 
-public interface EnqueuerConstClassAnalysis {
+public interface TraceConstClassEnqueuerAnalysis {
 
   void traceConstClass(DexType type, DexClass clazz, ProgramMethod context);
 }
diff --git a/src/main/java/com/android/tools/r8/graph/analysis/EnqueuerExceptionGuardAnalysis.java b/src/main/java/com/android/tools/r8/graph/analysis/TraceExceptionGuardEnqueuerAnalysis.java
similarity index 77%
rename from src/main/java/com/android/tools/r8/graph/analysis/EnqueuerExceptionGuardAnalysis.java
rename to src/main/java/com/android/tools/r8/graph/analysis/TraceExceptionGuardEnqueuerAnalysis.java
index 752e4a5..94c5a50 100644
--- a/src/main/java/com/android/tools/r8/graph/analysis/EnqueuerExceptionGuardAnalysis.java
+++ b/src/main/java/com/android/tools/r8/graph/analysis/TraceExceptionGuardEnqueuerAnalysis.java
@@ -1,13 +1,12 @@
-// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// Copyright (c) 2024, the R8 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.
-
 package com.android.tools.r8.graph.analysis;
 
 import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.ProgramMethod;
 
-public interface EnqueuerExceptionGuardAnalysis {
+public interface TraceExceptionGuardEnqueuerAnalysis {
   void traceExceptionGuard(DexType guard, DexClass clazz, ProgramMethod context);
 }
diff --git a/src/main/java/com/android/tools/r8/graph/analysis/EnqueuerFieldAccessAnalysis.java b/src/main/java/com/android/tools/r8/graph/analysis/TraceFieldAccessEnqueuerAnalysis.java
similarity index 77%
rename from src/main/java/com/android/tools/r8/graph/analysis/EnqueuerFieldAccessAnalysis.java
rename to src/main/java/com/android/tools/r8/graph/analysis/TraceFieldAccessEnqueuerAnalysis.java
index 69eb560..316df40 100644
--- a/src/main/java/com/android/tools/r8/graph/analysis/EnqueuerFieldAccessAnalysis.java
+++ b/src/main/java/com/android/tools/r8/graph/analysis/TraceFieldAccessEnqueuerAnalysis.java
@@ -1,17 +1,15 @@
-// Copyright (c) 2021, the R8 project authors. Please see the AUTHORS file
+// Copyright (c) 2024, the R8 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.
-
 package com.android.tools.r8.graph.analysis;
 
 import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.graph.FieldResolutionResult;
 import com.android.tools.r8.graph.FieldResolutionResult.SingleFieldResolutionResult;
 import com.android.tools.r8.graph.ProgramMethod;
-import com.android.tools.r8.shaking.Enqueuer;
 import com.android.tools.r8.shaking.EnqueuerWorklist;
 
-public interface EnqueuerFieldAccessAnalysis {
+public interface TraceFieldAccessEnqueuerAnalysis {
 
   default void traceInstanceFieldRead(
       DexField field,
@@ -36,10 +34,4 @@
       FieldResolutionResult resolutionResult,
       ProgramMethod context,
       EnqueuerWorklist worklist) {}
-
-  /**
-   * Called when the Enqueuer has reached the final fixpoint. Each analysis may use this callback to
-   * perform some post-processing.
-   */
-  default void done(Enqueuer enqueuer) {}
 }
diff --git a/src/main/java/com/android/tools/r8/graph/analysis/EnqueuerInstanceOfAnalysis.java b/src/main/java/com/android/tools/r8/graph/analysis/TraceInstanceOfEnqueuerAnalysis.java
similarity index 78%
rename from src/main/java/com/android/tools/r8/graph/analysis/EnqueuerInstanceOfAnalysis.java
rename to src/main/java/com/android/tools/r8/graph/analysis/TraceInstanceOfEnqueuerAnalysis.java
index 485a311..3a7bcc8 100644
--- a/src/main/java/com/android/tools/r8/graph/analysis/EnqueuerInstanceOfAnalysis.java
+++ b/src/main/java/com/android/tools/r8/graph/analysis/TraceInstanceOfEnqueuerAnalysis.java
@@ -1,13 +1,12 @@
-// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// Copyright (c) 2024, the R8 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.
-
 package com.android.tools.r8.graph.analysis;
 
 import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.ProgramMethod;
 
-public interface EnqueuerInstanceOfAnalysis {
+public interface TraceInstanceOfEnqueuerAnalysis {
   void traceInstanceOf(DexType type, DexClass clazz, ProgramMethod context);
 }
diff --git a/src/main/java/com/android/tools/r8/graph/analysis/EnqueuerInvokeAnalysis.java b/src/main/java/com/android/tools/r8/graph/analysis/TraceInvokeEnqueuerAnalysis.java
similarity index 63%
rename from src/main/java/com/android/tools/r8/graph/analysis/EnqueuerInvokeAnalysis.java
rename to src/main/java/com/android/tools/r8/graph/analysis/TraceInvokeEnqueuerAnalysis.java
index d2ca27d..d5b1568 100644
--- a/src/main/java/com/android/tools/r8/graph/analysis/EnqueuerInvokeAnalysis.java
+++ b/src/main/java/com/android/tools/r8/graph/analysis/TraceInvokeEnqueuerAnalysis.java
@@ -1,4 +1,4 @@
-// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// Copyright (c) 2024, the R8 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.
 package com.android.tools.r8.graph.analysis;
@@ -7,24 +7,24 @@
 import com.android.tools.r8.graph.MethodResolutionResult;
 import com.android.tools.r8.graph.ProgramMethod;
 
-public interface EnqueuerInvokeAnalysis {
+public interface TraceInvokeEnqueuerAnalysis {
 
   /**
    * Each traceInvokeXX method is called when a corresponding invoke is found while tracing a live
    * method.
    */
-  void traceInvokeStatic(
-      DexMethod invokedMethod, MethodResolutionResult resolutionResult, ProgramMethod context);
+  default void traceInvokeStatic(
+      DexMethod invokedMethod, MethodResolutionResult resolutionResult, ProgramMethod context) {}
 
-  void traceInvokeDirect(
-      DexMethod invokedMethod, MethodResolutionResult resolutionResult, ProgramMethod context);
+  default void traceInvokeDirect(
+      DexMethod invokedMethod, MethodResolutionResult resolutionResult, ProgramMethod context) {}
 
-  void traceInvokeInterface(
-      DexMethod invokedMethod, MethodResolutionResult resolutionResult, ProgramMethod context);
+  default void traceInvokeInterface(
+      DexMethod invokedMethod, MethodResolutionResult resolutionResult, ProgramMethod context) {}
 
-  void traceInvokeSuper(
-      DexMethod invokedMethod, MethodResolutionResult resolutionResult, ProgramMethod context);
+  default void traceInvokeSuper(
+      DexMethod invokedMethod, MethodResolutionResult resolutionResult, ProgramMethod context) {}
 
-  void traceInvokeVirtual(
-      DexMethod invokedMethod, MethodResolutionResult resolutionResult, ProgramMethod context);
+  default void traceInvokeVirtual(
+      DexMethod invokedMethod, MethodResolutionResult resolutionResult, ProgramMethod context) {}
 }
diff --git a/src/main/java/com/android/tools/r8/graph/analysis/EnqueuerNewInstanceAnalysis.java b/src/main/java/com/android/tools/r8/graph/analysis/TraceNewInstanceEnqueuerAnalysis.java
similarity index 77%
rename from src/main/java/com/android/tools/r8/graph/analysis/EnqueuerNewInstanceAnalysis.java
rename to src/main/java/com/android/tools/r8/graph/analysis/TraceNewInstanceEnqueuerAnalysis.java
index 880c91a..41bb477 100644
--- a/src/main/java/com/android/tools/r8/graph/analysis/EnqueuerNewInstanceAnalysis.java
+++ b/src/main/java/com/android/tools/r8/graph/analysis/TraceNewInstanceEnqueuerAnalysis.java
@@ -1,4 +1,4 @@
-// Copyright (c) 2023, the R8 project authors. Please see the AUTHORS file
+// Copyright (c) 2024, the R8 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.
 package com.android.tools.r8.graph.analysis;
@@ -7,7 +7,7 @@
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.ProgramMethod;
 
-public interface EnqueuerNewInstanceAnalysis {
+public interface TraceNewInstanceEnqueuerAnalysis {
 
   void traceNewInstance(DexType type, DexClass clazz, ProgramMethod context);
 }
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/proto/GeneratedMessageLiteBuilderShrinker.java b/src/main/java/com/android/tools/r8/ir/analysis/proto/GeneratedMessageLiteBuilderShrinker.java
index e66ee34..f925f86 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/proto/GeneratedMessageLiteBuilderShrinker.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/proto/GeneratedMessageLiteBuilderShrinker.java
@@ -20,7 +20,8 @@
 import com.android.tools.r8.graph.MethodAccessFlags;
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.graph.SubtypingInfo;
-import com.android.tools.r8.graph.analysis.EnqueuerAnalysis;
+import com.android.tools.r8.graph.analysis.EnqueuerAnalysisCollection;
+import com.android.tools.r8.graph.analysis.FixpointEnqueuerAnalysis;
 import com.android.tools.r8.ir.analysis.type.ClassTypeElement;
 import com.android.tools.r8.ir.code.CheckCast;
 import com.android.tools.r8.ir.code.IRCode;
@@ -82,6 +83,13 @@
     assert enableAggressiveBuilderOptimization;
   }
 
+  public static void register(
+      AppView<? extends AppInfoWithClassHierarchy> appView,
+      EnqueuerAnalysisCollection.Builder builder) {
+    appView.withGeneratedMessageLiteBuilderShrinker(
+        shrinker -> builder.addFixpointAnalysis(shrinker.createEnqueuerAnalysis()));
+  }
+
   private boolean computeEnableAggressiveBuilderOptimization() {
     DexClass generatedMessageLiteBuilderClass =
         appView
@@ -131,11 +139,11 @@
     return true;
   }
 
-  public EnqueuerAnalysis createEnqueuerAnalysis() {
+  public FixpointEnqueuerAnalysis createEnqueuerAnalysis() {
     Set<DexProgramClass> seen = Sets.newIdentityHashSet();
-    return new EnqueuerAnalysis() {
+    return new FixpointEnqueuerAnalysis() {
+
       @Override
-      @SuppressWarnings("ReferenceEquality")
       public void notifyFixpoint(
           Enqueuer enqueuer,
           EnqueuerWorklist worklist,
@@ -171,7 +179,9 @@
                 }
 
                 superClass.accessFlags.demoteFromAbstract();
-                if (superClass.type == references.generatedMessageLiteBuilderType) {
+                if (superClass
+                    .getType()
+                    .isIdenticalTo(references.generatedMessageLiteBuilderType)) {
                   // Manually trace `new GeneratedMessageLite.Builder(DEFAULT_INSTANCE)` since we
                   // haven't rewritten the code yet.
                   worklist.enqueueTraceNewInstanceAction(
@@ -181,7 +191,9 @@
                       dynamicMethod,
                       null);
                 } else {
-                  assert superClass.type == references.generatedMessageLiteExtendableBuilderType;
+                  assert superClass
+                      .getType()
+                      .isIdenticalTo(references.generatedMessageLiteExtendableBuilderType);
                   // Manually trace `new GeneratedMessageLite.ExtendableBuilder(DEFAULT_INSTANCE)`
                   // since we haven't rewritten the code yet.
                   worklist.enqueueTraceNewInstanceAction(
@@ -200,7 +212,6 @@
   }
 
   /** Returns true if an action was deferred. */
-  @SuppressWarnings("ReferenceEquality")
   public boolean deferDeadProtoBuilders(
       DexProgramClass clazz, ProgramMethod method, BooleanSupplier register) {
     if (!enableAggressiveBuilderOptimization) {
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/proto/schema/ProtoEnqueuerExtension.java b/src/main/java/com/android/tools/r8/ir/analysis/proto/schema/ProtoEnqueuerExtension.java
index 0ce0317..b2b08f0 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/proto/schema/ProtoEnqueuerExtension.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/proto/schema/ProtoEnqueuerExtension.java
@@ -19,7 +19,10 @@
 import com.android.tools.r8.graph.ProgramDefinition;
 import com.android.tools.r8.graph.ProgramField;
 import com.android.tools.r8.graph.ProgramMethod;
-import com.android.tools.r8.graph.analysis.EnqueuerAnalysis;
+import com.android.tools.r8.graph.analysis.EnqueuerAnalysisCollection;
+import com.android.tools.r8.graph.analysis.FixpointEnqueuerAnalysis;
+import com.android.tools.r8.graph.analysis.NewlyLiveClassEnqueuerAnalysis;
+import com.android.tools.r8.graph.analysis.NewlyLiveMethodEnqueuerAnalysis;
 import com.android.tools.r8.ir.analysis.proto.GeneratedMessageLiteShrinker;
 import com.android.tools.r8.ir.analysis.proto.ProtoEnqueuerUseRegistry;
 import com.android.tools.r8.ir.analysis.proto.ProtoReferences;
@@ -64,7 +67,10 @@
 // TODO(b/112437944): Handle incomplete information about extensions + add a test that fails with
 //  the current implementation. If there are some extensions that cannot be resolved, then we should
 //  keep fields that could reach extensions to be conservative.
-public class ProtoEnqueuerExtension extends EnqueuerAnalysis {
+public class ProtoEnqueuerExtension
+    implements NewlyLiveClassEnqueuerAnalysis,
+        NewlyLiveMethodEnqueuerAnalysis,
+        FixpointEnqueuerAnalysis {
 
   private final AppView<? extends AppInfoWithClassHierarchy> appView;
   private final RawMessageInfoDecoder decoder;
@@ -103,6 +109,18 @@
     this.references = protoShrinker.references;
   }
 
+  public static void register(
+      AppView<? extends AppInfoWithClassHierarchy> appView,
+      EnqueuerAnalysisCollection.Builder builder) {
+    if (appView.options().protoShrinking().enableGeneratedMessageLiteShrinking) {
+      ProtoEnqueuerExtension analysis = new ProtoEnqueuerExtension(appView);
+      builder
+          .addNewlyLiveClassAnalysis(analysis)
+          .addNewlyLiveMethodAnalysis(analysis)
+          .addFixpointAnalysis(analysis);
+    }
+  }
+
   @Override
   public void processNewlyLiveClass(DexProgramClass clazz, EnqueuerWorklist worklist) {
     assert appView.appInfo().hasClassHierarchy();
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataEnqueuerExtension.java b/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataEnqueuerExtension.java
index e583aea..26a1258 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataEnqueuerExtension.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataEnqueuerExtension.java
@@ -8,6 +8,7 @@
 import static com.android.tools.r8.kotlin.KotlinMetadataUtils.getNoKotlinInfo;
 
 import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.ClassResolutionResult;
 import com.android.tools.r8.graph.DexClass;
@@ -20,17 +21,19 @@
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.EnclosingMethodAttribute;
 import com.android.tools.r8.graph.ProgramDefinition;
-import com.android.tools.r8.graph.analysis.EnqueuerAnalysis;
+import com.android.tools.r8.graph.analysis.EnqueuerAnalysisCollection;
+import com.android.tools.r8.graph.analysis.FinishedEnqueuerAnalysis;
 import com.android.tools.r8.ir.optimize.info.OptimizationFeedback;
 import com.android.tools.r8.ir.optimize.info.OptimizationFeedbackSimple;
 import com.android.tools.r8.shaking.Enqueuer;
 import com.android.tools.r8.shaking.Enqueuer.EnqueuerDefinitionSupplier;
 import com.android.tools.r8.shaking.KeepClassInfo;
+import com.android.tools.r8.utils.InternalOptions;
 import com.google.common.collect.Sets;
 import java.util.Set;
 import java.util.concurrent.atomic.AtomicBoolean;
 
-public class KotlinMetadataEnqueuerExtension extends EnqueuerAnalysis {
+public class KotlinMetadataEnqueuerExtension implements FinishedEnqueuerAnalysis {
 
   private static final OptimizationFeedback feedback = OptimizationFeedbackSimple.getInstance();
 
@@ -48,6 +51,23 @@
     this.prunedTypes = prunedTypes;
   }
 
+  public static void register(
+      AppView<? extends AppInfoWithClassHierarchy> appView,
+      EnqueuerDefinitionSupplier enqueuerDefinitionSupplier,
+      Set<DexType> initialPrunedTypes,
+      EnqueuerAnalysisCollection.Builder builder) {
+    // TODO(b/323816623): This check does not include presence of keep declarations.
+    //  The non-presense of PG config seems like a exeedingly rare corner case so maybe just
+    //  make this conditional on tree shaking and the specific option flag.
+    InternalOptions options = appView.options();
+    if (options.hasProguardConfiguration()
+        && !options.kotlinOptimizationOptions().disableKotlinSpecificOptimizations) {
+      builder.addFinishedAnalysis(
+          new KotlinMetadataEnqueuerExtension(
+              appView, enqueuerDefinitionSupplier, initialPrunedTypes));
+    }
+  }
+
   private KotlinMetadataDefinitionSupplier definitionsForContext(ProgramDefinition context) {
     return new KotlinMetadataDefinitionSupplier(context, enqueuerDefinitionSupplier, prunedTypes);
   }
diff --git a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
index 96be3c1..be7b139 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -90,19 +90,13 @@
 import com.android.tools.r8.graph.SubtypingInfo;
 import com.android.tools.r8.graph.UseRegistry.MethodHandleUse;
 import com.android.tools.r8.graph.analysis.ApiModelAnalysis;
-import com.android.tools.r8.graph.analysis.EnqueuerAnalysis;
-import com.android.tools.r8.graph.analysis.EnqueuerCheckCastAnalysis;
-import com.android.tools.r8.graph.analysis.EnqueuerConstClassAnalysis;
-import com.android.tools.r8.graph.analysis.EnqueuerExceptionGuardAnalysis;
-import com.android.tools.r8.graph.analysis.EnqueuerFieldAccessAnalysis;
-import com.android.tools.r8.graph.analysis.EnqueuerInstanceOfAnalysis;
-import com.android.tools.r8.graph.analysis.EnqueuerInvokeAnalysis;
-import com.android.tools.r8.graph.analysis.EnqueuerNewInstanceAnalysis;
-import com.android.tools.r8.graph.analysis.EnqueuerTypeAccessAnalysis;
+import com.android.tools.r8.graph.analysis.ClassInitializerAssertionEnablingAnalysis;
+import com.android.tools.r8.graph.analysis.EnqueuerAnalysisCollection;
 import com.android.tools.r8.graph.analysis.GetArrayOfMissingTypeVerifyErrorWorkaround;
 import com.android.tools.r8.graph.analysis.InitializedClassesInInstanceMethodsAnalysis;
 import com.android.tools.r8.graph.analysis.InvokeVirtualToInterfaceVerifyErrorWorkaround;
 import com.android.tools.r8.graph.analysis.ResourceAccessAnalysis;
+import com.android.tools.r8.ir.analysis.proto.GeneratedMessageLiteBuilderShrinker;
 import com.android.tools.r8.ir.analysis.proto.ProtoEnqueuerUseRegistry;
 import com.android.tools.r8.ir.analysis.proto.schema.ProtoEnqueuerExtension;
 import com.android.tools.r8.ir.code.ArrayPut;
@@ -187,7 +181,6 @@
 import java.util.HashMap;
 import java.util.IdentityHashMap;
 import java.util.LinkedHashMap;
-import java.util.LinkedHashSet;
 import java.util.LinkedList;
 import java.util.List;
 import java.util.ListIterator;
@@ -259,14 +252,7 @@
   private final boolean forceProguardCompatibility;
   private final Mode mode;
 
-  private final Set<EnqueuerAnalysis> analyses = new LinkedHashSet<>();
-  private final Set<EnqueuerFieldAccessAnalysis> fieldAccessAnalyses = new LinkedHashSet<>();
-  private final Set<EnqueuerInvokeAnalysis> invokeAnalyses = new LinkedHashSet<>();
-  private final Set<EnqueuerInstanceOfAnalysis> instanceOfAnalyses = new LinkedHashSet<>();
-  private final Set<EnqueuerExceptionGuardAnalysis> exceptionGuardAnalyses = new LinkedHashSet<>();
-  private final Set<EnqueuerCheckCastAnalysis> checkCastAnalyses = new LinkedHashSet<>();
-  private final Set<EnqueuerConstClassAnalysis> constClassAnalyses = new LinkedHashSet<>();
-  private final Set<EnqueuerNewInstanceAnalysis> newInstanceAnalyses = new LinkedHashSet<>();
+  private final EnqueuerAnalysisCollection analyses;
 
   private final Map<DexProgramClass, Boolean> rClassLookupCache = new IdentityHashMap<>();
 
@@ -280,8 +266,6 @@
   private RootSet rootSet;
   private final EnqueuerUseRegistryFactory useRegistryFactory;
   private AnnotationRemover.Builder annotationRemoverBuilder;
-  private final EnqueuerDefinitionSupplier enqueuerDefinitionSupplier =
-      new EnqueuerDefinitionSupplier(this);
 
   private final FieldAccessInfoCollectionImpl fieldAccessInfoCollection =
       new FieldAccessInfoCollectionImpl();
@@ -361,7 +345,7 @@
   private Set<DexType> initialDeadProtoTypes = Sets.newIdentityHashSet();
 
   /** Set of types that was pruned during the first round of tree shaking. */
-  private Set<DexType> initialPrunedTypes;
+  private final Set<DexType> initialPrunedTypes;
 
   private final Set<DexType> noClassMerging = Sets.newIdentityHashSet();
 
@@ -498,6 +482,26 @@
       SubtypingInfo subtypingInfo,
       GraphConsumer keptGraphConsumer,
       Mode mode) {
+    this(
+        appView,
+        profileCollectionAdditions,
+        executorService,
+        subtypingInfo,
+        keptGraphConsumer,
+        mode,
+        null,
+        null);
+  }
+
+  Enqueuer(
+      AppView<? extends AppInfoWithClassHierarchy> appView,
+      ProfileCollectionAdditions profileCollectionAdditions,
+      ExecutorService executorService,
+      SubtypingInfo subtypingInfo,
+      GraphConsumer keptGraphConsumer,
+      Mode mode,
+      Set<DexType> initialPrunedTypes,
+      RuntimeTypeCheckInfo.Builder runtimeTypeCheckInfoBuilder) {
     assert appView.appServices() != null;
     InternalOptions options = appView.options();
     this.appInfo = appView.appInfo();
@@ -517,25 +521,33 @@
         mode.isInitialTreeShaking() && options.forceProguardCompatibility
             ? ProguardCompatibilityActions.builder()
             : null;
+    this.initialPrunedTypes = initialPrunedTypes;
 
     if (options.isOptimizedResourceShrinking()) {
       appView.getResourceShrinkerState().setEnqueuerCallback(this::recordReferenceFromResources);
     }
+
+    EnqueuerAnalysisCollection.Builder analysesBuilder = EnqueuerAnalysisCollection.builder();
     if (mode.isTreeShaking()) {
-      InitializedClassesInInstanceMethodsAnalysis.register(appView, this);
-      GetArrayOfMissingTypeVerifyErrorWorkaround.register(appView, this);
-      InitializedClassesInInstanceMethodsAnalysis.register(appView, this);
-      InvokeVirtualToInterfaceVerifyErrorWorkaround.register(appView, this);
-      if (options.protoShrinking().enableGeneratedMessageLiteShrinking) {
-        registerAnalysis(new ProtoEnqueuerExtension(appView));
-      }
-      appView.withGeneratedMessageLiteBuilderShrinker(
-          shrinker -> registerAnalysis(shrinker.createEnqueuerAnalysis()));
-      IsolatedFeatureSplitsChecker.register(appView, this);
-      ResourceAccessAnalysis.register(appView, this);
-      CovariantReturnTypeEnqueuerExtension.register(appView, this);
+      EnqueuerDefinitionSupplier enqueuerDefinitionSupplier = new EnqueuerDefinitionSupplier(this);
+      ApiModelAnalysis.register(appView, analysesBuilder);
+      ClassInitializerAssertionEnablingAnalysis.register(appView, this, analysesBuilder);
+      CovariantReturnTypeEnqueuerExtension.register(appView, this, analysesBuilder);
+      GeneratedMessageLiteBuilderShrinker.register(appView, analysesBuilder);
+      GenericSignatureEnqueuerAnalysis.register(
+          appView, enqueuerDefinitionSupplier, analysesBuilder);
+      GetArrayOfMissingTypeVerifyErrorWorkaround.register(appView, this, analysesBuilder);
+      IfRuleEvaluatorFactory.register(appView, this, analysesBuilder, executorService);
+      InitializedClassesInInstanceMethodsAnalysis.register(appView, this, analysesBuilder);
+      InvokeVirtualToInterfaceVerifyErrorWorkaround.register(appView, this, analysesBuilder);
+      IsolatedFeatureSplitsChecker.register(appView, analysesBuilder);
+      KotlinMetadataEnqueuerExtension.register(
+          appView, enqueuerDefinitionSupplier, initialPrunedTypes, analysesBuilder);
+      ProtoEnqueuerExtension.register(appView, analysesBuilder);
+      ResourceAccessAnalysis.register(appView, this, analysesBuilder);
+      RuntimeTypeCheckInfo.register(runtimeTypeCheckInfoBuilder, analysesBuilder);
     }
-    IfRuleEvaluatorFactory.register(appView, this, executorService);
+    analyses = analysesBuilder.build();
 
     targetedMethods = new LiveMethodsSet(graphReporter::registerMethod);
     failedClassResolutionTargets = SetUtils.newIdentityHashSet(0);
@@ -586,54 +598,6 @@
     return useRegistryFactory;
   }
 
-  public Enqueuer registerAnalysis(EnqueuerAnalysis analysis) {
-    analyses.add(analysis);
-    return this;
-  }
-
-  public Enqueuer registerFieldAccessAnalysis(EnqueuerFieldAccessAnalysis analysis) {
-    fieldAccessAnalyses.add(analysis);
-    return this;
-  }
-
-  public Enqueuer registerInvokeAnalysis(EnqueuerInvokeAnalysis analysis) {
-    invokeAnalyses.add(analysis);
-    return this;
-  }
-
-  public Enqueuer registerInstanceOfAnalysis(EnqueuerInstanceOfAnalysis analysis) {
-    instanceOfAnalyses.add(analysis);
-    return this;
-  }
-
-  public Enqueuer registerCheckCastAnalysis(EnqueuerCheckCastAnalysis analysis) {
-    checkCastAnalyses.add(analysis);
-    return this;
-  }
-
-  public Enqueuer registerConstClassAnalysis(EnqueuerConstClassAnalysis analysis) {
-    constClassAnalyses.add(analysis);
-    return this;
-  }
-
-  public Enqueuer registerExceptionGuardAnalysis(EnqueuerExceptionGuardAnalysis analysis) {
-    exceptionGuardAnalyses.add(analysis);
-    return this;
-  }
-
-  public Enqueuer registerNewInstanceAnalysis(EnqueuerNewInstanceAnalysis analysis) {
-    newInstanceAnalyses.add(analysis);
-    return this;
-  }
-
-  public Enqueuer registerTypeAccessAnalysis(EnqueuerTypeAccessAnalysis analysis) {
-    return registerCheckCastAnalysis(analysis)
-        .registerConstClassAnalysis(analysis)
-        .registerExceptionGuardAnalysis(analysis)
-        .registerInstanceOfAnalysis(analysis)
-        .registerNewInstanceAnalysis(analysis);
-  }
-
   public void setKeepDeclarations(List<KeepDeclaration> keepDeclarations) {
     // Keep declarations are used during initial tree shaking. Re-runs use the rule instance sets.
     assert mode.isInitialTreeShaking();
@@ -650,11 +614,6 @@
     this.initialDeadProtoTypes = initialDeadProtoTypes;
   }
 
-  public void setInitialPrunedTypes(Set<DexType> initialPrunedTypes) {
-    assert mode.isFinalTreeShaking();
-    this.initialPrunedTypes = initialPrunedTypes;
-  }
-
   public void addDeadProtoTypeCandidate(DexType type) {
     DexProgramClass clazz = asProgramClassOrNull(appView.definitionFor(type));
     if (clazz != null) {
@@ -897,7 +856,7 @@
         // rules.
         handleLibraryTypeInheritingFromProgramType(clazz.asLibraryClass());
       }
-      analyses.forEach(analysis -> analysis.processNewLiveNonProgramType(clazz));
+      analyses.processNewlyLiveNonProgramType(clazz);
       clazz.forEachClassField(
           field ->
               addNonProgramClassToWorklist(
@@ -1191,8 +1150,7 @@
 
       // Notify analyses.
       if (field.isProgramField()) {
-        ProgramField programField = field.asProgramField();
-        analyses.forEach(analysis -> analysis.processNewlyReferencedField(programField));
+        analyses.processNewlyReferencedField(field.asProgramField());
       }
     }
 
@@ -1326,12 +1284,12 @@
 
   void traceCheckCast(DexType type, ProgramMethod currentMethod, boolean ignoreCompatRules) {
     DexClass clazz = internalTraceConstClassOrCheckCast(type, currentMethod, ignoreCompatRules);
-    checkCastAnalyses.forEach(analysis -> analysis.traceCheckCast(type, clazz, currentMethod));
+    analyses.traceCheckCast(type, clazz, currentMethod);
   }
 
   void traceSafeCheckCast(DexType type, ProgramMethod currentMethod) {
     DexClass clazz = internalTraceConstClassOrCheckCast(type, currentMethod, true);
-    checkCastAnalyses.forEach(analysis -> analysis.traceSafeCheckCast(type, clazz, currentMethod));
+    analyses.traceSafeCheckCast(type, clazz, currentMethod);
   }
 
   void traceConstClass(
@@ -1341,7 +1299,7 @@
       boolean ignoreCompatRules) {
     handleLockCandidate(type, currentMethod, iterator);
     DexClass clazz = internalTraceConstClassOrCheckCast(type, currentMethod, ignoreCompatRules);
-    constClassAnalyses.forEach(analysis -> analysis.traceConstClass(type, clazz, currentMethod));
+    analyses.traceConstClass(type, clazz, currentMethod);
   }
 
   private void handleLockCandidate(
@@ -1504,14 +1462,13 @@
   void traceInstanceOf(DexType type, ProgramMethod currentMethod) {
     DexClass clazz = resolveBaseType(type, currentMethod);
     traceTypeReference(type, currentMethod);
-    instanceOfAnalyses.forEach(analysis -> analysis.traceInstanceOf(type, clazz, currentMethod));
+    analyses.traceInstanceOf(type, clazz, currentMethod);
   }
 
   void traceExceptionGuard(DexType type, ProgramMethod currentMethod) {
     DexClass clazz = resolveBaseType(type, currentMethod);
     traceTypeReference(type, currentMethod);
-    exceptionGuardAnalyses.forEach(
-        analysis -> analysis.traceExceptionGuard(type, clazz, currentMethod));
+    analyses.traceExceptionGuard(type, clazz, currentMethod);
   }
 
   void traceInvokeDirect(
@@ -1560,8 +1517,7 @@
     markTypeAsLive(invokedMethod.getHolderType(), context);
     MethodResolutionResult resolutionResult =
         handleInvokeOfDirectTarget(invokedMethod, context, reason);
-    invokeAnalyses.forEach(
-        analysis -> analysis.traceInvokeDirect(invokedMethod, resolutionResult, context));
+    analyses.traceInvokeDirect(invokedMethod, resolutionResult, context);
   }
 
   void traceInvokeInterface(
@@ -1586,8 +1542,7 @@
     markTypeAsLive(invokedMethod.getHolderType(), context);
     MethodResolutionResult result =
         markVirtualMethodAsReachable(invokedMethod, true, context, keepReason);
-    invokeAnalyses.forEach(
-        analysis -> analysis.traceInvokeInterface(invokedMethod, result, context));
+    analyses.traceInvokeInterface(invokedMethod, result, context);
   }
 
   void traceInvokeStatic(
@@ -1632,8 +1587,7 @@
     markTypeAsLive(invokedMethod.getHolderType(), context);
     MethodResolutionResult resolutionResult =
         handleInvokeOfStaticTarget(invokedMethod, context, reason);
-    invokeAnalyses.forEach(
-        analysis -> analysis.traceInvokeStatic(invokedMethod, resolutionResult, context));
+    analyses.traceInvokeStatic(invokedMethod, resolutionResult, context);
   }
 
   void traceInvokeSuper(
@@ -1679,8 +1633,7 @@
     markTypeAsLive(invokedMethod.getHolderType(), context);
     MethodResolutionResult resolutionResult =
         markVirtualMethodAsReachable(invokedMethod, false, context, reason);
-    invokeAnalyses.forEach(
-        analysis -> analysis.traceInvokeVirtual(invokedMethod, resolutionResult, context));
+    analyses.traceInvokeVirtual(invokedMethod, resolutionResult, context);
   }
 
   void traceMethodPosition(com.android.tools.r8.ir.code.Position position, ProgramMethod context) {
@@ -1729,7 +1682,7 @@
             context,
             InstantiationReason.NEW_INSTANCE_INSTRUCTION,
             KeepReason.instantiatedIn(context));
-    newInstanceAnalyses.forEach(analysis -> analysis.traceNewInstance(type, clazz, context));
+    analyses.traceNewInstance(type, clazz, context);
   }
 
   void traceNewInstanceFromLambda(DexType type, ProgramMethod context) {
@@ -1880,10 +1833,8 @@
 
     resolutionResult.visitFieldResolutionResults(
         singleResolutionResult -> {
-          fieldAccessAnalyses.forEach(
-              analysis ->
-                  analysis.traceInstanceFieldRead(
-                      fieldReference, singleResolutionResult, currentMethod, worklist));
+          analyses.traceInstanceFieldRead(
+              fieldReference, singleResolutionResult, currentMethod, worklist);
 
           ProgramField field = singleResolutionResult.getProgramField();
           if (field == null) {
@@ -1946,10 +1897,8 @@
 
     resolutionResult.visitFieldResolutionResults(
         singleResolutionResult -> {
-          fieldAccessAnalyses.forEach(
-              analysis ->
-                  analysis.traceInstanceFieldWrite(
-                      fieldReference, singleResolutionResult, currentMethod, worklist));
+          analyses.traceInstanceFieldWrite(
+              fieldReference, singleResolutionResult, currentMethod, worklist);
 
           ProgramField field = singleResolutionResult.getProgramField();
           if (field == null) {
@@ -2025,10 +1974,8 @@
 
     resolutionResult.visitFieldResolutionResults(
         singleResolutionResult -> {
-          fieldAccessAnalyses.forEach(
-              analysis ->
-                  analysis.traceStaticFieldRead(
-                      fieldReference, singleResolutionResult, currentMethod, worklist));
+          analyses.traceStaticFieldRead(
+              fieldReference, singleResolutionResult, currentMethod, worklist);
 
           ProgramField field = singleResolutionResult.getProgramField();
           if (field == null) {
@@ -2115,10 +2062,8 @@
 
     resolutionResult.visitFieldResolutionResults(
         singleResolutionResult -> {
-          fieldAccessAnalyses.forEach(
-              analysis ->
-                  analysis.traceStaticFieldWrite(
-                      fieldReference, singleResolutionResult, currentMethod, worklist));
+          analyses.traceStaticFieldWrite(
+              fieldReference, singleResolutionResult, currentMethod, worklist);
 
           ProgramField field = singleResolutionResult.getProgramField();
           if (field == null) {
@@ -2362,7 +2307,7 @@
     compatEnqueueHolderIfDependentNonStaticMember(
         clazz, rootSet.getDependentKeepClassCompatRule(clazz.getType()));
 
-    analyses.forEach(analysis -> analysis.processNewlyLiveClass(clazz, worklist));
+    analyses.processNewlyLiveClass(clazz, worklist);
   }
 
   private void processDeferredAnnotations(
@@ -2950,9 +2895,7 @@
     // Notify analyses. This is done even if `clazz` has already been marked as instantiated,
     // because each analysis may depend on seeing all the (clazz, reason) pairs. Thus, not doing so
     // could lead to nondeterminism.
-    analyses.forEach(
-        analysis ->
-            analysis.processNewlyInstantiatedClass(clazz.asProgramClass(), context, worklist));
+    analyses.processNewlyInstantiatedClass(clazz.asProgramClass(), context, worklist);
 
     if (!markInstantiatedClass(clazz, context, instantiationReason, keepReason)) {
       return;
@@ -3363,7 +3306,7 @@
     }
 
     // Notify analyses.
-    analyses.forEach(analysis -> analysis.processNewlyLiveField(field, context, worklist));
+    analyses.processNewlyLiveField(field, context, worklist);
   }
 
   // Package protected due to entry point from worklist.
@@ -3391,7 +3334,7 @@
     addEffectivelyLiveOriginalField(field);
     traceFieldDefinition(field);
 
-    analyses.forEach(analysis -> analysis.notifyMarkFieldAsReachable(field, worklist));
+    analyses.processNewlyReachableField(field, worklist);
   }
 
   private void handleFieldAccessWithInaccessibleFieldType(
@@ -3672,7 +3615,6 @@
     target.accept(
         method -> markVirtualDispatchMethodTargetAsLive(method, reason),
         lambda -> markVirtualDispatchLambdaTargetAsLive(lambda, reason));
-    analyses.forEach(analysis -> analysis.notifyMarkVirtualDispatchTargetAsLive(target, worklist));
   }
 
   private void markVirtualDispatchMethodTargetAsLive(
@@ -3804,10 +3746,8 @@
               resolution.lookupInvokeSuperTarget(context.getHolder(), appView);
           if (target == null) {
             failedMethodResolutionTargets.add(resolution.getResolvedMethod().getReference());
-            analyses.forEach(
-                analyses ->
-                    analyses.notifyFailedMethodResolutionTarget(
-                        resolution.getResolvedMethod(), worklist));
+            analyses.processNewlyFailedMethodResolutionTarget(
+                resolution.getResolvedMethod(), worklist);
             return;
           }
 
@@ -3829,8 +3769,7 @@
             }
           }
         });
-    invokeAnalyses.forEach(
-        analysis -> analysis.traceInvokeSuper(reference, resolutionResults, context));
+    analyses.traceInvokeSuper(reference, resolutionResults, context);
   }
 
   public boolean isRClass(DexProgramClass dexProgramClass) {
@@ -3867,29 +3806,6 @@
       RootSet rootSet, ExecutorService executorService, Timing timing) throws ExecutionException {
     this.rootSet = rootSet;
     rootSet.pendingMethodMoveInverse.forEach(pendingMethodMoveInverse::put);
-    // Translate the result of root-set computation into enqueuer actions.
-    timing.begin("Register analysis");
-    // TODO(b/323816623): This check does not include presence of keep declarations.
-    //  The non-presense of PG config seems like a exeedingly rare corner case so maybe just
-    //  make this conditional on tree shaking and the specific option flag.
-    if (mode.isTreeShaking()
-        && appView.options().hasProguardConfiguration()
-        && !options.kotlinOptimizationOptions().disableKotlinSpecificOptimizations) {
-      registerAnalysis(
-          new KotlinMetadataEnqueuerExtension(
-              appView, enqueuerDefinitionSupplier, initialPrunedTypes));
-    }
-    // TODO(b/323816623): This check does not include presence of keep declarations.
-    //  We should consider if we should always run the signature analysis and just not emit them
-    //  in the end?
-    if (appView.options().getProguardConfiguration() != null
-        && appView.options().getProguardConfiguration().getKeepAttributes().signature) {
-      registerAnalysis(new GenericSignatureEnqueuerAnalysis(enqueuerDefinitionSupplier));
-    }
-    if (options.apiModelingOptions().enableLibraryApiModeling) {
-      registerAnalysis(new ApiModelAnalysis(appView));
-    }
-    timing.end();
 
     // Transfer the minimum keep info from the root set into the Enqueuer state.
     timing.begin("Transfer minimum keep info");
@@ -3935,8 +3851,7 @@
     finalizeLibraryMethodOverrideInformation();
     timing.end();
     timing.begin("Finish analysis");
-    analyses.forEach(analyses -> analyses.done(this));
-    fieldAccessAnalyses.forEach(fieldAccessAnalyses -> fieldAccessAnalyses.done(this));
+    analyses.done(this);
     if (appView.options().isOptimizedResourceShrinking()) {
       appView.getResourceShrinkerState().enqueuerDone(this.mode.isFinalTreeShaking());
     }
@@ -4770,9 +4685,7 @@
 
         // Notify each analysis that a fixpoint has been reached, and give each analysis an
         // opportunity to add items to the worklist.
-        for (EnqueuerAnalysis analysis : analyses) {
-          analysis.notifyFixpoint(this, worklist, executorService, timing);
-        }
+        analyses.notifyFixpoint(this, worklist, executorService, timing);
         if (!worklist.isEmpty()) {
           continue;
         }
@@ -5069,8 +4982,7 @@
       }
     }
 
-    // Notify analyses.
-    analyses.forEach(analysis -> analysis.processNewlyLiveMethod(method, context, this, worklist));
+    analyses.processNewlyLiveMethod(method, context, this, worklist);
   }
 
   private void markMethodAsTargeted(ProgramMethod method, KeepReason reason) {
@@ -5090,7 +5002,7 @@
         markMethodAsLiveWithCompatRule(method);
       }
     }
-    analyses.forEach(analysis -> analysis.processNewlyTargetedMethod(method, worklist));
+    analyses.processNewlyTargetedMethod(method, worklist);
   }
 
   void traceMethodDefinitionExcludingCode(ProgramMethod method) {
@@ -5124,8 +5036,7 @@
     DefaultEnqueuerUseRegistry registry =
         useRegistryFactory.create(appView, method, this, appView.apiLevelCompute());
     method.registerCodeReferences(registry);
-    // Notify analyses.
-    analyses.forEach(analysis -> analysis.processTracedCode(method, registry, worklist));
+    analyses.processNewlyLiveCode(method, registry, worklist);
   }
 
   private void markReferencedTypesAsLive(ProgramMethod method) {
diff --git a/src/main/java/com/android/tools/r8/shaking/EnqueuerFactory.java b/src/main/java/com/android/tools/r8/shaking/EnqueuerFactory.java
index e5f90c2..09ea894 100644
--- a/src/main/java/com/android/tools/r8/shaking/EnqueuerFactory.java
+++ b/src/main/java/com/android/tools/r8/shaking/EnqueuerFactory.java
@@ -35,7 +35,8 @@
       ExecutorService executorService,
       SubtypingInfo subtypingInfo,
       GraphConsumer keptGraphConsumer,
-      Set<DexType> initialPrunedTypes) {
+      Set<DexType> initialPrunedTypes,
+      RuntimeTypeCheckInfo.Builder runtimeTypeCheckInfoBuilder) {
     ProfileCollectionAdditions profileCollectionAdditions =
         ProfileCollectionAdditions.create(appView);
     Enqueuer enqueuer =
@@ -45,10 +46,11 @@
             executorService,
             subtypingInfo,
             keptGraphConsumer,
-            Mode.FINAL_TREE_SHAKING);
+            Mode.FINAL_TREE_SHAKING,
+            initialPrunedTypes,
+            runtimeTypeCheckInfoBuilder);
     appView.withProtoShrinker(
         shrinker -> enqueuer.setInitialDeadProtoTypes(shrinker.getDeadProtoTypes()));
-    enqueuer.setInitialPrunedTypes(initialPrunedTypes);
     return enqueuer;
   }
 
diff --git a/src/main/java/com/android/tools/r8/shaking/IfRuleEvaluatorFactory.java b/src/main/java/com/android/tools/r8/shaking/IfRuleEvaluatorFactory.java
index 9fe386a..497f691 100644
--- a/src/main/java/com/android/tools/r8/shaking/IfRuleEvaluatorFactory.java
+++ b/src/main/java/com/android/tools/r8/shaking/IfRuleEvaluatorFactory.java
@@ -14,7 +14,13 @@
 import com.android.tools.r8.graph.ProgramField;
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.graph.SubtypingInfo;
-import com.android.tools.r8.graph.analysis.EnqueuerAnalysis;
+import com.android.tools.r8.graph.analysis.EnqueuerAnalysisCollection;
+import com.android.tools.r8.graph.analysis.FixpointEnqueuerAnalysis;
+import com.android.tools.r8.graph.analysis.NewlyLiveClassEnqueuerAnalysis;
+import com.android.tools.r8.graph.analysis.NewlyLiveFieldEnqueuerAnalysis;
+import com.android.tools.r8.graph.analysis.NewlyLiveMethodEnqueuerAnalysis;
+import com.android.tools.r8.graph.analysis.NewlyReferencedFieldEnqueuerAnalysis;
+import com.android.tools.r8.graph.analysis.NewlyTargetedMethodEnqueuerAnalysis;
 import com.android.tools.r8.shaking.RootSetUtils.ConsequentRootSet;
 import com.android.tools.r8.shaking.RootSetUtils.ConsequentRootSetBuilder;
 import com.android.tools.r8.threading.TaskCollection;
@@ -30,7 +36,13 @@
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.ExecutorService;
 
-public class IfRuleEvaluatorFactory extends EnqueuerAnalysis {
+public class IfRuleEvaluatorFactory
+    implements NewlyLiveClassEnqueuerAnalysis,
+        NewlyLiveFieldEnqueuerAnalysis,
+        NewlyLiveMethodEnqueuerAnalysis,
+        NewlyReferencedFieldEnqueuerAnalysis,
+        NewlyTargetedMethodEnqueuerAnalysis,
+        FixpointEnqueuerAnalysis {
 
   private final AppView<? extends AppInfoWithClassHierarchy> appView;
 
@@ -61,11 +73,20 @@
   public static void register(
       AppView<? extends AppInfoWithClassHierarchy> appView,
       Enqueuer enqueuer,
+      EnqueuerAnalysisCollection.Builder builder,
       ExecutorService executorService) {
     Set<ProguardIfRule> ifRules =
         appView.hasRootSet() ? appView.rootSet().ifRules : Collections.emptySet();
     if (ifRules != null && !ifRules.isEmpty()) {
-      enqueuer.registerAnalysis(new IfRuleEvaluatorFactory(appView, enqueuer, executorService));
+      IfRuleEvaluatorFactory factory =
+          new IfRuleEvaluatorFactory(appView, enqueuer, executorService);
+      builder
+          .addNewlyLiveClassAnalysis(factory)
+          .addNewlyLiveFieldAnalysis(factory)
+          .addNewlyLiveMethodAnalysis(factory)
+          .addNewlyReferencedFieldAnalysis(factory)
+          .addNewlyTargetedMethodAnalysis(factory)
+          .addFixpointAnalysis(factory);
     }
   }
 
diff --git a/src/main/java/com/android/tools/r8/shaking/RuntimeTypeCheckInfo.java b/src/main/java/com/android/tools/r8/shaking/RuntimeTypeCheckInfo.java
index 045227d..705edd8 100644
--- a/src/main/java/com/android/tools/r8/shaking/RuntimeTypeCheckInfo.java
+++ b/src/main/java/com/android/tools/r8/shaking/RuntimeTypeCheckInfo.java
@@ -10,9 +10,10 @@
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.ProgramMethod;
-import com.android.tools.r8.graph.analysis.EnqueuerCheckCastAnalysis;
-import com.android.tools.r8.graph.analysis.EnqueuerExceptionGuardAnalysis;
-import com.android.tools.r8.graph.analysis.EnqueuerInstanceOfAnalysis;
+import com.android.tools.r8.graph.analysis.EnqueuerAnalysisCollection;
+import com.android.tools.r8.graph.analysis.TraceCheckCastEnqueuerAnalysis;
+import com.android.tools.r8.graph.analysis.TraceExceptionGuardEnqueuerAnalysis;
+import com.android.tools.r8.graph.analysis.TraceInstanceOfEnqueuerAnalysis;
 import com.android.tools.r8.graph.lens.GraphLens;
 import com.android.tools.r8.graph.lens.NonIdentityGraphLens;
 import com.android.tools.r8.utils.SetUtils;
@@ -33,6 +34,15 @@
     this.exceptionGuardTypes = exceptionGuardTypes;
   }
 
+  public static void register(Builder builder, EnqueuerAnalysisCollection.Builder analysesBuilder) {
+    if (builder != null) {
+      analysesBuilder
+          .addTraceCheckCastAnalysis(builder)
+          .addTraceExceptionGuardAnalysis(builder)
+          .addTraceInstanceOfAnalysis(builder);
+    }
+  }
+
   public boolean isCheckCastType(DexProgramClass clazz) {
     return checkCastTypes.contains(clazz.type);
   }
@@ -59,9 +69,9 @@
   }
 
   public static class Builder
-      implements EnqueuerInstanceOfAnalysis,
-          EnqueuerCheckCastAnalysis,
-          EnqueuerExceptionGuardAnalysis {
+      implements TraceInstanceOfEnqueuerAnalysis,
+          TraceCheckCastEnqueuerAnalysis,
+          TraceExceptionGuardEnqueuerAnalysis {
 
     private final GraphLens appliedGraphLens;
     private final DexItemFactory factory;
@@ -109,12 +119,5 @@
         set.add(baseType);
       }
     }
-
-    public void attach(Enqueuer enqueuer) {
-      enqueuer
-          .registerInstanceOfAnalysis(this)
-          .registerCheckCastAnalysis(this)
-          .registerExceptionGuardAnalysis(this);
-    }
   }
 }
diff --git a/src/main/java/com/android/tools/r8/utils/InternalOptions.java b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
index 0462ec8..29bab19 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -941,8 +941,7 @@
    * If any non-static class merging is enabled, information about types referred to by instanceOf
    * and check cast instructions needs to be collected.
    */
-  public boolean isClassMergingExtensionRequired(Enqueuer.Mode mode) {
-    assert mode.isFinalTreeShaking();
+  public boolean isClassMergingExtensionRequired() {
     WholeProgramOptimizations wholeProgramOptimizations = WholeProgramOptimizations.ON;
     return horizontalClassMergerOptions.isEnabled(wholeProgramOptimizations)
         && !horizontalClassMergerOptions.isRestrictedToSynthetics();