Revert "Move if rule evalation logic to own class"

This reverts commit b25d85e6d3801d5c8bf7f1e7a66dbda3b5438c3f.


Revert "Introduce method to evaluate if-rule on a single class"

This reverts commit c6197cba8c46fb6e048b2c38231700f3cb099e27.


Revert "Cleanup if rule evaluator by removing ifRules field and timing"

This reverts commit 9ce7e6f1f0539141911ccfa83b108d35f331e251.


Revert "Avoid computing effectively live info in each if rule evaluation"

This reverts commit 791fefe834aece2aa5ea915b73b310a25c96679e.


Revert "Rewrite if rule evaluator to enqueuer extension"

This reverts commit d0804fd1926db0699ebf920fa3f83f24d3592fa3.


Revert "Only evaluate if rules without members once per class"

This reverts commit ef2f5f75e13f51538d57d90273276d9d1b0ef881.


Revert "Group if rules without members by equivalence for shared class evaluation"

This reverts commit 39ec4b131fbe745ef0f2100d439074b3804601aa.


Revert "Fix inadequate grouping of if rules during evaluation"

This reverts commit 7ce7d18051cf960c577d48189ad0953b8ef8534b.


Revert "Only re-evaluate if rules with members on classes with newly live members"

This reverts commit c3d27dec4e48f97502a83d440c987c7a54f46c7b.


Revert "Specialize enqueuer analyses"

This reverts commit 3dd731623af6a3eb8e9bdccee95afe964bbe4780.

Change-Id: I5bd91b6cb44203d023100bca774e1d27062bd2fb
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java
index 4b76e69..8184741 100644
--- a/src/main/java/com/android/tools/r8/R8.java
+++ b/src/main/java/com/android/tools/r8/R8.java
@@ -33,6 +33,7 @@
 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;
@@ -48,10 +49,12 @@
 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;
@@ -559,17 +562,17 @@
             }
           }
 
-          if (options.isClassMergingExtensionRequired()) {
-            finalRuntimeTypeCheckInfoBuilder = new RuntimeTypeCheckInfo.Builder(appView);
-          }
           Enqueuer enqueuer =
               EnqueuerFactory.createForFinalTreeShaking(
                   appView,
                   executorService,
                   SubtypingInfo.create(appView),
                   keptGraphConsumer,
-                  prunedTypes,
-                  finalRuntimeTypeCheckInfoBuilder);
+                  prunedTypes);
+          if (options.isClassMergingExtensionRequired(enqueuer.getMode())) {
+            finalRuntimeTypeCheckInfoBuilder = new RuntimeTypeCheckInfo.Builder(appView);
+            finalRuntimeTypeCheckInfoBuilder.attach(enqueuer);
+          }
           EnqueuerResult enqueuerResult =
               enqueuer.traceApplication(appView.rootSet(), executorService, timing);
           appView.setAppInfo(enqueuerResult.getAppInfo());
@@ -1165,6 +1168,13 @@
             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 18be316..4e8573f 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,9 +13,7 @@
 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.EnqueuerAnalysisCollection;
-import com.android.tools.r8.graph.analysis.FixpointEnqueuerAnalysis;
-import com.android.tools.r8.graph.analysis.NewlyLiveMethodEnqueuerAnalysis;
+import com.android.tools.r8.graph.analysis.EnqueuerAnalysis;
 import com.android.tools.r8.shaking.Enqueuer;
 import com.android.tools.r8.shaking.EnqueuerWorklist;
 import com.android.tools.r8.shaking.KeepInfo;
@@ -31,8 +29,7 @@
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.ExecutorService;
 
-public class CovariantReturnTypeEnqueuerExtension
-    implements NewlyLiveMethodEnqueuerAnalysis, FixpointEnqueuerAnalysis {
+public class CovariantReturnTypeEnqueuerExtension extends EnqueuerAnalysis {
 
   private final AppView<? extends AppInfoWithClassHierarchy> appView;
   private final CovariantReturnTypeAnnotationTransformer transformer;
@@ -47,14 +44,10 @@
   }
 
   public static void register(
-      AppView<? extends AppInfoWithClassHierarchy> appView,
-      Enqueuer enqueuer,
-      EnqueuerAnalysisCollection.Builder builder) {
+      AppView<? extends AppInfoWithClassHierarchy> appView, Enqueuer enqueuer) {
     if (enqueuer.getMode().isInitialTreeShaking()
         && CovariantReturnTypeAnnotationTransformer.shouldRun(appView)) {
-      CovariantReturnTypeEnqueuerExtension analysis =
-          new CovariantReturnTypeEnqueuerExtension(appView);
-      builder.addNewlyLiveMethodAnalysis(analysis).addFixpointAnalysis(analysis);
+      enqueuer.registerAnalysis(new CovariantReturnTypeEnqueuerExtension(appView));
     }
   }
 
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 2b05cea..376acee 100644
--- a/src/main/java/com/android/tools/r8/features/IsolatedFeatureSplitsChecker.java
+++ b/src/main/java/com/android/tools/r8/features/IsolatedFeatureSplitsChecker.java
@@ -17,18 +17,16 @@
 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.EnqueuerAnalysisCollection;
+import com.android.tools.r8.graph.analysis.EnqueuerFieldAccessAnalysis;
+import com.android.tools.r8.graph.analysis.EnqueuerInvokeAnalysis;
 import com.android.tools.r8.graph.analysis.EnqueuerTypeAccessAnalysis;
-import com.android.tools.r8.graph.analysis.TraceFieldAccessEnqueuerAnalysis;
-import com.android.tools.r8.graph.analysis.TraceInvokeEnqueuerAnalysis;
+import com.android.tools.r8.shaking.Enqueuer;
 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 TraceFieldAccessEnqueuerAnalysis,
-        TraceInvokeEnqueuerAnalysis,
-        EnqueuerTypeAccessAnalysis {
+    implements EnqueuerFieldAccessAnalysis, EnqueuerInvokeAnalysis, EnqueuerTypeAccessAnalysis {
 
   private final AppView<? extends AppInfoWithClassHierarchy> appView;
   private final ClassToFeatureSplitMap features;
@@ -39,18 +37,13 @@
   }
 
   public static void register(
-      AppView<? extends AppInfoWithClassHierarchy> appView,
-      EnqueuerAnalysisCollection.Builder builder) {
+      AppView<? extends AppInfoWithClassHierarchy> appView, Enqueuer enqueuer) {
     if (enabled(appView)) {
       IsolatedFeatureSplitsChecker checker = new IsolatedFeatureSplitsChecker(appView);
-      builder
-          .addTraceFieldAccessAnalysis(checker)
-          .addTraceInvokeAnalysis(checker)
-          .addTraceCheckCastAnalysis(checker)
-          .addTraceConstClassAnalysis(checker)
-          .addTraceExceptionGuardAnalysis(checker)
-          .addTraceInstanceOfAnalysis(checker)
-          .addTraceNewInstanceAnalysis(checker);
+      enqueuer
+          .registerFieldAccessAnalysis(checker)
+          .registerInvokeAnalysis(checker)
+          .registerTypeAccessAnalysis(checker);
     }
   }
 
diff --git a/src/main/java/com/android/tools/r8/graph/DexProgramClass.java b/src/main/java/com/android/tools/r8/graph/DexProgramClass.java
index c9ca7e4..e890a6a 100644
--- a/src/main/java/com/android/tools/r8/graph/DexProgramClass.java
+++ b/src/main/java/com/android/tools/r8/graph/DexProgramClass.java
@@ -838,7 +838,7 @@
   public static Iterable<DexProgramClass> asProgramClasses(
       Iterable<DexType> types, DexDefinitionSupplier definitions) {
     return () ->
-        new Iterator<>() {
+        new Iterator<DexProgramClass>() {
 
           private final Iterator<DexType> iterator = types.iterator();
 
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 5f8b573..2599a50 100644
--- a/src/main/java/com/android/tools/r8/graph/GenericSignatureEnqueuerAnalysis.java
+++ b/src/main/java/com/android/tools/r8/graph/GenericSignatureEnqueuerAnalysis.java
@@ -4,25 +4,14 @@
 
 package com.android.tools.r8.graph;
 
-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.graph.analysis.EnqueuerAnalysis;
 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
-    implements NewlyLiveClassEnqueuerAnalysis,
-        NewlyLiveFieldEnqueuerAnalysis,
-        NewlyLiveMethodEnqueuerAnalysis,
-        NewlyReachableFieldEnqueuerAnalysis,
-        NewlyTargetedMethodEnqueuerAnalysis {
+public class GenericSignatureEnqueuerAnalysis extends EnqueuerAnalysis {
 
   private final EnqueuerDefinitionSupplier enqueuerDefinitionSupplier;
   private final Set<DexReference> processedSignatures = Sets.newIdentityHashSet();
@@ -31,39 +20,28 @@
     this.enqueuerDefinitionSupplier = enqueuerDefinitionSupplier;
   }
 
-  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 processNewlyLiveClass(DexProgramClass clazz, EnqueuerWorklist worklist) {
     processSignature(clazz, clazz.getContext());
   }
 
   @Override
+  public void notifyMarkFieldAsReachable(ProgramField field, EnqueuerWorklist worklist) {
+    processSignature(field, field.getContext());
+  }
+
+  @Override
   public void processNewlyLiveField(
       ProgramField field, ProgramDefinition context, EnqueuerWorklist worklist) {
     processSignature(field, context);
   }
 
   @Override
+  public void notifyMarkMethodAsTargeted(ProgramMethod method, EnqueuerWorklist worklist) {
+    processSignature(method, method.getContext());
+  }
+
+  @Override
   public void processNewlyLiveMethod(
       ProgramMethod method,
       ProgramDefinition context,
@@ -72,16 +50,6 @@
     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 4b6337e..b1ca241 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,14 +18,7 @@
 import com.android.tools.r8.shaking.Enqueuer;
 import com.android.tools.r8.shaking.EnqueuerWorklist;
 
-public class ApiModelAnalysis
-    implements NewlyFailedMethodResolutionEnqueuerAnalysis,
-        NewlyLiveCodeEnqueuerAnalysis,
-        NewlyLiveFieldEnqueuerAnalysis,
-        NewlyLiveMethodEnqueuerAnalysis,
-        NewlyLiveNonProgramClassEnqueuerAnalysis,
-        NewlyReachableFieldEnqueuerAnalysis,
-        NewlyTargetedMethodEnqueuerAnalysis {
+public class ApiModelAnalysis extends EnqueuerAnalysis {
 
   private final AppView<?> appView;
   private final AndroidApiLevelCompute apiCompute;
@@ -37,22 +30,6 @@
     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) {
@@ -69,7 +46,7 @@
   }
 
   @Override
-  public void processNewlyLiveCode(
+  public void processTracedCode(
       ProgramMethod method, DefaultEnqueuerUseRegistry registry, EnqueuerWorklist worklist) {
     assert registry.getMaxApiReferenceLevel().isGreaterThanOrEqualTo(minApiLevel);
     if (appView.options().apiModelingOptions().tracedMethodApiLevelCallback != null) {
@@ -84,22 +61,32 @@
   }
 
   @Override
-  public void processNewlyTargetedMethod(ProgramMethod method, EnqueuerWorklist worklist) {
+  public void notifyMarkMethodAsTargeted(ProgramMethod method, EnqueuerWorklist worklist) {
     computeAndSetApiLevelForDefinition(method);
   }
 
   @Override
-  public void processNewlyReachableField(ProgramField field, EnqueuerWorklist worklist) {
+  public void notifyMarkFieldAsReachable(ProgramField field, EnqueuerWorklist worklist) {
     computeAndSetApiLevelForDefinition(field);
   }
 
   @Override
-  public void processNewlyLiveNonProgramType(ClasspathOrLibraryClass clazz) {
+  public void processNewLiveNonProgramType(ClasspathOrLibraryClass clazz) {
     clazz.forEachClassMethod(this::computeAndSetApiLevelForDefinition);
   }
 
   @Override
-  public void processNewlyFailedMethodResolutionTarget(
+  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(
       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 d38b615..e5122b3 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,7 +14,6 @@
 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;
@@ -25,9 +24,7 @@
 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;
@@ -36,8 +33,8 @@
 import java.util.stream.Collectors;
 import org.objectweb.asm.Opcodes;
 
-public class ClassInitializerAssertionEnablingAnalysis
-    implements TraceFieldAccessEnqueuerAnalysis, NewlyLiveMethodEnqueuerAnalysis {
+public class ClassInitializerAssertionEnablingAnalysis extends EnqueuerAnalysis
+    implements EnqueuerFieldAccessAnalysis {
   private final DexItemFactory dexItemFactory;
   private final OptimizationFeedback feedback;
   private final DexString kotlinAssertionsEnabled;
@@ -56,28 +53,17 @@
             .collect(Collectors.toList());
   }
 
-  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);
-    }
-  }
-
+  @SuppressWarnings("ReferenceEquality")
   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().isIdenticalTo(dexItemFactory.assertionsDisabled)
-        && field.getType().isIdenticalTo(dexItemFactory.booleanType);
+    return field.getName() == dexItemFactory.assertionsDisabled
+        && field.getType() == dexItemFactory.booleanType;
   }
 
+  @SuppressWarnings("ReferenceEquality")
   private boolean isUsingKotlinAssertionsEnabledField(DexField field) {
-    return field.isIdenticalTo(dexItemFactory.kotlin.assertions.enabledField);
+    return field == 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
new file mode 100644
index 0000000..34f58f6
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/graph/analysis/EnqueuerAnalysis.java
@@ -0,0 +1,71 @@
+// 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.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) {}
+
+  /** 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 notifyMarkMethodAsTargeted(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
deleted file mode 100644
index afacc0e..0000000
--- a/src/main/java/com/android/tools/r8/graph/analysis/EnqueuerAnalysisCollection.java
+++ /dev/null
@@ -1,496 +0,0 @@
-// 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/TraceCheckCastEnqueuerAnalysis.java b/src/main/java/com/android/tools/r8/graph/analysis/EnqueuerCheckCastAnalysis.java
similarity index 91%
rename from src/main/java/com/android/tools/r8/graph/analysis/TraceCheckCastEnqueuerAnalysis.java
rename to src/main/java/com/android/tools/r8/graph/analysis/EnqueuerCheckCastAnalysis.java
index a6362a2..42f4367 100644
--- a/src/main/java/com/android/tools/r8/graph/analysis/TraceCheckCastEnqueuerAnalysis.java
+++ b/src/main/java/com/android/tools/r8/graph/analysis/EnqueuerCheckCastAnalysis.java
@@ -1,13 +1,14 @@
 // 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 TraceCheckCastEnqueuerAnalysis {
+public interface EnqueuerCheckCastAnalysis {
 
   void traceCheckCast(DexType type, DexClass clazz, ProgramMethod context);
 
diff --git a/src/main/java/com/android/tools/r8/graph/analysis/TraceConstClassEnqueuerAnalysis.java b/src/main/java/com/android/tools/r8/graph/analysis/EnqueuerConstClassAnalysis.java
similarity index 90%
rename from src/main/java/com/android/tools/r8/graph/analysis/TraceConstClassEnqueuerAnalysis.java
rename to src/main/java/com/android/tools/r8/graph/analysis/EnqueuerConstClassAnalysis.java
index 259fe33..c9204dd 100644
--- a/src/main/java/com/android/tools/r8/graph/analysis/TraceConstClassEnqueuerAnalysis.java
+++ b/src/main/java/com/android/tools/r8/graph/analysis/EnqueuerConstClassAnalysis.java
@@ -7,7 +7,7 @@
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.ProgramMethod;
 
-public interface TraceConstClassEnqueuerAnalysis {
+public interface EnqueuerConstClassAnalysis {
 
   void traceConstClass(DexType type, DexClass clazz, ProgramMethod context);
 }
diff --git a/src/main/java/com/android/tools/r8/graph/analysis/TraceExceptionGuardEnqueuerAnalysis.java b/src/main/java/com/android/tools/r8/graph/analysis/EnqueuerExceptionGuardAnalysis.java
similarity index 89%
rename from src/main/java/com/android/tools/r8/graph/analysis/TraceExceptionGuardEnqueuerAnalysis.java
rename to src/main/java/com/android/tools/r8/graph/analysis/EnqueuerExceptionGuardAnalysis.java
index 94c5a50..717cdc0 100644
--- a/src/main/java/com/android/tools/r8/graph/analysis/TraceExceptionGuardEnqueuerAnalysis.java
+++ b/src/main/java/com/android/tools/r8/graph/analysis/EnqueuerExceptionGuardAnalysis.java
@@ -1,12 +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.DexClass;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.ProgramMethod;
 
-public interface TraceExceptionGuardEnqueuerAnalysis {
+public interface EnqueuerExceptionGuardAnalysis {
   void traceExceptionGuard(DexType guard, DexClass clazz, ProgramMethod context);
 }
diff --git a/src/main/java/com/android/tools/r8/graph/analysis/TraceFieldAccessEnqueuerAnalysis.java b/src/main/java/com/android/tools/r8/graph/analysis/EnqueuerFieldAccessAnalysis.java
similarity index 81%
rename from src/main/java/com/android/tools/r8/graph/analysis/TraceFieldAccessEnqueuerAnalysis.java
rename to src/main/java/com/android/tools/r8/graph/analysis/EnqueuerFieldAccessAnalysis.java
index 316df40..04ac9a9 100644
--- a/src/main/java/com/android/tools/r8/graph/analysis/TraceFieldAccessEnqueuerAnalysis.java
+++ b/src/main/java/com/android/tools/r8/graph/analysis/EnqueuerFieldAccessAnalysis.java
@@ -1,15 +1,17 @@
 // 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 TraceFieldAccessEnqueuerAnalysis {
+public interface EnqueuerFieldAccessAnalysis {
 
   default void traceInstanceFieldRead(
       DexField field,
@@ -34,4 +36,10 @@
       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/TraceInstanceOfEnqueuerAnalysis.java b/src/main/java/com/android/tools/r8/graph/analysis/EnqueuerInstanceOfAnalysis.java
similarity index 90%
rename from src/main/java/com/android/tools/r8/graph/analysis/TraceInstanceOfEnqueuerAnalysis.java
rename to src/main/java/com/android/tools/r8/graph/analysis/EnqueuerInstanceOfAnalysis.java
index 3a7bcc8..fd8c33a 100644
--- a/src/main/java/com/android/tools/r8/graph/analysis/TraceInstanceOfEnqueuerAnalysis.java
+++ b/src/main/java/com/android/tools/r8/graph/analysis/EnqueuerInstanceOfAnalysis.java
@@ -1,12 +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.DexClass;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.ProgramMethod;
 
-public interface TraceInstanceOfEnqueuerAnalysis {
+public interface EnqueuerInstanceOfAnalysis {
   void traceInstanceOf(DexType type, DexClass clazz, ProgramMethod context);
 }
diff --git a/src/main/java/com/android/tools/r8/graph/analysis/TraceInvokeEnqueuerAnalysis.java b/src/main/java/com/android/tools/r8/graph/analysis/EnqueuerInvokeAnalysis.java
similarity index 68%
rename from src/main/java/com/android/tools/r8/graph/analysis/TraceInvokeEnqueuerAnalysis.java
rename to src/main/java/com/android/tools/r8/graph/analysis/EnqueuerInvokeAnalysis.java
index d5b1568..4d34db9 100644
--- a/src/main/java/com/android/tools/r8/graph/analysis/TraceInvokeEnqueuerAnalysis.java
+++ b/src/main/java/com/android/tools/r8/graph/analysis/EnqueuerInvokeAnalysis.java
@@ -7,24 +7,24 @@
 import com.android.tools.r8.graph.MethodResolutionResult;
 import com.android.tools.r8.graph.ProgramMethod;
 
-public interface TraceInvokeEnqueuerAnalysis {
+public interface EnqueuerInvokeAnalysis {
 
   /**
    * Each traceInvokeXX method is called when a corresponding invoke is found while tracing a live
    * method.
    */
-  default void traceInvokeStatic(
-      DexMethod invokedMethod, MethodResolutionResult resolutionResult, ProgramMethod context) {}
+  void traceInvokeStatic(
+      DexMethod invokedMethod, MethodResolutionResult resolutionResult, ProgramMethod context);
 
-  default void traceInvokeDirect(
-      DexMethod invokedMethod, MethodResolutionResult resolutionResult, ProgramMethod context) {}
+  void traceInvokeDirect(
+      DexMethod invokedMethod, MethodResolutionResult resolutionResult, ProgramMethod context);
 
-  default void traceInvokeInterface(
-      DexMethod invokedMethod, MethodResolutionResult resolutionResult, ProgramMethod context) {}
+  void traceInvokeInterface(
+      DexMethod invokedMethod, MethodResolutionResult resolutionResult, ProgramMethod context);
 
-  default void traceInvokeSuper(
-      DexMethod invokedMethod, MethodResolutionResult resolutionResult, ProgramMethod context) {}
+  void traceInvokeSuper(
+      DexMethod invokedMethod, MethodResolutionResult resolutionResult, ProgramMethod context);
 
-  default void traceInvokeVirtual(
-      DexMethod invokedMethod, MethodResolutionResult resolutionResult, ProgramMethod context) {}
+  void traceInvokeVirtual(
+      DexMethod invokedMethod, MethodResolutionResult resolutionResult, ProgramMethod context);
 }
diff --git a/src/main/java/com/android/tools/r8/graph/analysis/TraceNewInstanceEnqueuerAnalysis.java b/src/main/java/com/android/tools/r8/graph/analysis/EnqueuerNewInstanceAnalysis.java
similarity index 89%
rename from src/main/java/com/android/tools/r8/graph/analysis/TraceNewInstanceEnqueuerAnalysis.java
rename to src/main/java/com/android/tools/r8/graph/analysis/EnqueuerNewInstanceAnalysis.java
index 41bb477..10fe6bc 100644
--- a/src/main/java/com/android/tools/r8/graph/analysis/TraceNewInstanceEnqueuerAnalysis.java
+++ b/src/main/java/com/android/tools/r8/graph/analysis/EnqueuerNewInstanceAnalysis.java
@@ -7,7 +7,7 @@
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.ProgramMethod;
 
-public interface TraceNewInstanceEnqueuerAnalysis {
+public interface EnqueuerNewInstanceAnalysis {
 
   void traceNewInstance(DexType type, DexClass clazz, ProgramMethod context);
 }
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 cb76002..3d44ef1 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 TraceCheckCastEnqueuerAnalysis,
-        TraceConstClassEnqueuerAnalysis,
-        TraceExceptionGuardEnqueuerAnalysis,
-        TraceInstanceOfEnqueuerAnalysis,
-        TraceNewInstanceEnqueuerAnalysis {}
+    extends EnqueuerCheckCastAnalysis,
+        EnqueuerConstClassAnalysis,
+        EnqueuerExceptionGuardAnalysis,
+        EnqueuerInstanceOfAnalysis,
+        EnqueuerNewInstanceAnalysis {}
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
deleted file mode 100644
index 54cabca..0000000
--- a/src/main/java/com/android/tools/r8/graph/analysis/FinishedEnqueuerAnalysis.java
+++ /dev/null
@@ -1,15 +0,0 @@
-// 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
deleted file mode 100644
index 9f67337..0000000
--- a/src/main/java/com/android/tools/r8/graph/analysis/FixpointEnqueuerAnalysis.java
+++ /dev/null
@@ -1,21 +0,0 @@
-// 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 bace0d4..ed49777 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,8 +34,7 @@
  * 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 TraceFieldAccessEnqueuerAnalysis {
+public class GetArrayOfMissingTypeVerifyErrorWorkaround implements EnqueuerFieldAccessAnalysis {
 
   private final DexItemFactory dexItemFactory;
   private final Enqueuer enqueuer;
@@ -49,11 +48,9 @@
   }
 
   public static void register(
-      AppView<? extends AppInfoWithClassHierarchy> appView,
-      Enqueuer enqueuer,
-      EnqueuerAnalysisCollection.Builder builder) {
+      AppView<? extends AppInfoWithClassHierarchy> appView, Enqueuer enqueuer) {
     if (!isNoop(appView)) {
-      builder.addTraceFieldAccessAnalysis(
+      enqueuer.registerFieldAccessAnalysis(
           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 2a84623..ddb64d7 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,8 +17,7 @@
 import java.util.IdentityHashMap;
 import java.util.Map;
 
-public class InitializedClassesInInstanceMethodsAnalysis
-    implements FinishedEnqueuerAnalysis, NewlyInstantiatedClassEnqueuerAnalysis {
+public class InitializedClassesInInstanceMethodsAnalysis extends EnqueuerAnalysis {
 
   // A simple structure that stores the result of the analysis.
   public static class InitializedClassesInInstanceMethods {
@@ -87,14 +86,10 @@
   }
 
   public static void register(
-      AppView<? extends AppInfoWithClassHierarchy> appView,
-      Enqueuer enqueuer,
-      EnqueuerAnalysisCollection.Builder builder) {
+      AppView<? extends AppInfoWithClassHierarchy> appView, Enqueuer enqueuer) {
     if (appView.options().enableInitializedClassesInInstanceMethodsAnalysis
         && enqueuer.getMode().isInitialTreeShaking()) {
-      InitializedClassesInInstanceMethodsAnalysis analysis =
-          new InitializedClassesInInstanceMethodsAnalysis(appView);
-      builder.addNewlyInstantiatedClassAnalysis(analysis).addFinishedAnalysis(analysis);
+      enqueuer.registerAnalysis(new InitializedClassesInInstanceMethodsAnalysis(appView));
     } 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 b5bf374..70fca76 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 TraceInvokeEnqueuerAnalysis {
+public class InvokeVirtualToInterfaceVerifyErrorWorkaround implements EnqueuerInvokeAnalysis {
 
   private final DexType androidHardwareCamera2CameraDeviceType;
   private final Enqueuer enqueuer;
@@ -42,11 +42,9 @@
   }
 
   public static void register(
-      AppView<? extends AppInfoWithClassHierarchy> appView,
-      Enqueuer enqueuer,
-      EnqueuerAnalysisCollection.Builder builder) {
+      AppView<? extends AppInfoWithClassHierarchy> appView, Enqueuer enqueuer) {
     if (!isNoop(appView)) {
-      builder.addTraceInvokeAnalysis(
+      enqueuer.registerInvokeAnalysis(
           new InvokeVirtualToInterfaceVerifyErrorWorkaround(appView, enqueuer));
     }
   }
@@ -63,11 +61,36 @@
     }
   }
 
+  @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.isIdenticalTo(androidHardwareCamera2CameraDeviceType)
+    return type == 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
deleted file mode 100644
index 4d6fc73..0000000
--- a/src/main/java/com/android/tools/r8/graph/analysis/NewlyFailedMethodResolutionEnqueuerAnalysis.java
+++ /dev/null
@@ -1,13 +0,0 @@
-// 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
deleted file mode 100644
index c3eb799..0000000
--- a/src/main/java/com/android/tools/r8/graph/analysis/NewlyInstantiatedClassEnqueuerAnalysis.java
+++ /dev/null
@@ -1,15 +0,0 @@
-// 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
deleted file mode 100644
index e681269..0000000
--- a/src/main/java/com/android/tools/r8/graph/analysis/NewlyLiveClassEnqueuerAnalysis.java
+++ /dev/null
@@ -1,13 +0,0 @@
-// 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
deleted file mode 100644
index 50f934f..0000000
--- a/src/main/java/com/android/tools/r8/graph/analysis/NewlyLiveCodeEnqueuerAnalysis.java
+++ /dev/null
@@ -1,15 +0,0 @@
-// 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
deleted file mode 100644
index 22c0d70..0000000
--- a/src/main/java/com/android/tools/r8/graph/analysis/NewlyLiveFieldEnqueuerAnalysis.java
+++ /dev/null
@@ -1,15 +0,0 @@
-// 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
deleted file mode 100644
index 6c6df87..0000000
--- a/src/main/java/com/android/tools/r8/graph/analysis/NewlyLiveMethodEnqueuerAnalysis.java
+++ /dev/null
@@ -1,19 +0,0 @@
-// 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
deleted file mode 100644
index dbf980a..0000000
--- a/src/main/java/com/android/tools/r8/graph/analysis/NewlyLiveNonProgramClassEnqueuerAnalysis.java
+++ /dev/null
@@ -1,12 +0,0 @@
-// 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
deleted file mode 100644
index bfa942a..0000000
--- a/src/main/java/com/android/tools/r8/graph/analysis/NewlyReachableFieldEnqueuerAnalysis.java
+++ /dev/null
@@ -1,12 +0,0 @@
-// 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
deleted file mode 100644
index 96c15fc..0000000
--- a/src/main/java/com/android/tools/r8/graph/analysis/NewlyReferencedFieldEnqueuerAnalysis.java
+++ /dev/null
@@ -1,11 +0,0 @@
-// 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
deleted file mode 100644
index 5bd7789..0000000
--- a/src/main/java/com/android/tools/r8/graph/analysis/NewlyTargetedMethodEnqueuerAnalysis.java
+++ /dev/null
@@ -1,12 +0,0 @@
-// 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 14aa18a..3d1cb69 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 TraceFieldAccessEnqueuerAnalysis {
+public class ResourceAccessAnalysis implements EnqueuerFieldAccessAnalysis {
 
   private final R8ResourceShrinkerState resourceShrinkerState;
   private final Map<DexType, RClassFieldToValueStore> fieldToValueMapping = new IdentityHashMap<>();
@@ -46,15 +46,13 @@
   }
 
   public static void register(
-      AppView<? extends AppInfoWithClassHierarchy> appView,
-      Enqueuer enqueuer,
-      EnqueuerAnalysisCollection.Builder builder) {
+      AppView<? extends AppInfoWithClassHierarchy> appView, Enqueuer enqueuer) {
     if (fieldAccessAnalysisEnabled(appView, enqueuer)) {
-      builder.addTraceFieldAccessAnalysis(new ResourceAccessAnalysis(appView, enqueuer));
+      enqueuer.registerFieldAccessAnalysis(new ResourceAccessAnalysis(appView, enqueuer));
     }
     if (liveFieldAnalysisEnabled(appView, enqueuer)) {
-      builder.addNewlyLiveFieldAnalysis(
-          new NewlyLiveFieldEnqueuerAnalysis() {
+      enqueuer.registerAnalysis(
+          new EnqueuerAnalysis() {
             @Override
             public void processNewlyLiveField(
                 ProgramField field, ProgramDefinition context, EnqueuerWorklist worklist) {
@@ -71,6 +69,11 @@
     }
   }
 
+  @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/ir/analysis/proto/GeneratedMessageLiteBuilderShrinker.java b/src/main/java/com/android/tools/r8/ir/analysis/proto/GeneratedMessageLiteBuilderShrinker.java
index f925f86..e66ee34 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,8 +20,7 @@
 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.EnqueuerAnalysisCollection;
-import com.android.tools.r8.graph.analysis.FixpointEnqueuerAnalysis;
+import com.android.tools.r8.graph.analysis.EnqueuerAnalysis;
 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;
@@ -83,13 +82,6 @@
     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
@@ -139,11 +131,11 @@
     return true;
   }
 
-  public FixpointEnqueuerAnalysis createEnqueuerAnalysis() {
+  public EnqueuerAnalysis createEnqueuerAnalysis() {
     Set<DexProgramClass> seen = Sets.newIdentityHashSet();
-    return new FixpointEnqueuerAnalysis() {
-
+    return new EnqueuerAnalysis() {
       @Override
+      @SuppressWarnings("ReferenceEquality")
       public void notifyFixpoint(
           Enqueuer enqueuer,
           EnqueuerWorklist worklist,
@@ -179,9 +171,7 @@
                 }
 
                 superClass.accessFlags.demoteFromAbstract();
-                if (superClass
-                    .getType()
-                    .isIdenticalTo(references.generatedMessageLiteBuilderType)) {
+                if (superClass.type == references.generatedMessageLiteBuilderType) {
                   // Manually trace `new GeneratedMessageLite.Builder(DEFAULT_INSTANCE)` since we
                   // haven't rewritten the code yet.
                   worklist.enqueueTraceNewInstanceAction(
@@ -191,9 +181,7 @@
                       dynamicMethod,
                       null);
                 } else {
-                  assert superClass
-                      .getType()
-                      .isIdenticalTo(references.generatedMessageLiteExtendableBuilderType);
+                  assert superClass.type == references.generatedMessageLiteExtendableBuilderType;
                   // Manually trace `new GeneratedMessageLite.ExtendableBuilder(DEFAULT_INSTANCE)`
                   // since we haven't rewritten the code yet.
                   worklist.enqueueTraceNewInstanceAction(
@@ -212,6 +200,7 @@
   }
 
   /** 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 b2b08f0..0ce0317 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,10 +19,7 @@
 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.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.graph.analysis.EnqueuerAnalysis;
 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;
@@ -67,10 +64,7 @@
 // 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
-    implements NewlyLiveClassEnqueuerAnalysis,
-        NewlyLiveMethodEnqueuerAnalysis,
-        FixpointEnqueuerAnalysis {
+public class ProtoEnqueuerExtension extends EnqueuerAnalysis {
 
   private final AppView<? extends AppInfoWithClassHierarchy> appView;
   private final RawMessageInfoDecoder decoder;
@@ -109,18 +103,6 @@
     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 26a1258..e583aea 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataEnqueuerExtension.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataEnqueuerExtension.java
@@ -8,7 +8,6 @@
 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;
@@ -21,19 +20,17 @@
 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.EnqueuerAnalysisCollection;
-import com.android.tools.r8.graph.analysis.FinishedEnqueuerAnalysis;
+import com.android.tools.r8.graph.analysis.EnqueuerAnalysis;
 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 implements FinishedEnqueuerAnalysis {
+public class KotlinMetadataEnqueuerExtension extends EnqueuerAnalysis {
 
   private static final OptimizationFeedback feedback = OptimizationFeedbackSimple.getInstance();
 
@@ -51,23 +48,6 @@
     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 cf0c2b9..be053e4 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -88,13 +88,19 @@
 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.ClassInitializerAssertionEnablingAnalysis;
-import com.android.tools.r8.graph.analysis.EnqueuerAnalysisCollection;
+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.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;
@@ -141,6 +147,7 @@
 import com.android.tools.r8.shaking.KeepMethodInfo.Joiner;
 import com.android.tools.r8.shaking.KeepReason.ReflectiveUseFromXml;
 import com.android.tools.r8.shaking.RootSetUtils.ConsequentRootSet;
+import com.android.tools.r8.shaking.RootSetUtils.ConsequentRootSetBuilder;
 import com.android.tools.r8.shaking.RootSetUtils.RootSet;
 import com.android.tools.r8.shaking.RootSetUtils.RootSetBase;
 import com.android.tools.r8.shaking.RootSetUtils.RootSetBuilder;
@@ -165,6 +172,7 @@
 import com.android.tools.r8.utils.collections.ProgramFieldSet;
 import com.android.tools.r8.utils.collections.ProgramMethodMap;
 import com.android.tools.r8.utils.collections.ProgramMethodSet;
+import com.google.common.base.Equivalence.Wrapper;
 import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Maps;
 import com.google.common.collect.Sets;
@@ -178,6 +186,7 @@
 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;
@@ -249,7 +258,14 @@
   private final boolean forceProguardCompatibility;
   private final Mode mode;
 
-  private final EnqueuerAnalysisCollection analyses;
+  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 Map<DexProgramClass, Boolean> rClassLookupCache = new IdentityHashMap<>();
 
@@ -263,6 +279,8 @@
   private RootSet rootSet;
   private final EnqueuerUseRegistryFactory useRegistryFactory;
   private AnnotationRemover.Builder annotationRemoverBuilder;
+  private final EnqueuerDefinitionSupplier enqueuerDefinitionSupplier =
+      new EnqueuerDefinitionSupplier(this);
 
   private final FieldAccessInfoCollectionImpl fieldAccessInfoCollection =
       new FieldAccessInfoCollectionImpl();
@@ -342,7 +360,7 @@
   private Set<DexType> initialDeadProtoTypes = Sets.newIdentityHashSet();
 
   /** Set of types that was pruned during the first round of tree shaking. */
-  private final Set<DexType> initialPrunedTypes;
+  private Set<DexType> initialPrunedTypes;
 
   private final Set<DexType> noClassMerging = Sets.newIdentityHashSet();
 
@@ -442,6 +460,9 @@
   private final Map<DexType, Map<DexAnnotation, List<ProgramDefinition>>>
       deferredParameterAnnotations = new IdentityHashMap<>();
 
+  /** Map of active if rules to speed up aapt2 generated keep rules. */
+  private Map<Wrapper<ProguardIfRule>, Set<ProguardIfRule>> activeIfRules;
+
   /**
    * A cache of ScopedDexMethodSet for each live type used for determining that virtual methods that
    * cannot be removed because they are widening access for another virtual method defined earlier
@@ -479,26 +500,6 @@
       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();
@@ -518,33 +519,24 @@
         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()) {
-      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);
+      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);
     }
-    analyses = analysesBuilder.build();
 
     targetedMethods = new LiveMethodsSet(graphReporter::registerMethod);
     failedClassResolutionTargets = SetUtils.newIdentityHashSet(0);
@@ -595,6 +587,54 @@
     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();
@@ -611,6 +651,11 @@
     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) {
@@ -788,10 +833,6 @@
     return keepInfo.getClassInfo(clazz);
   }
 
-  public SubtypingInfo getSubtypingInfo() {
-    return subtypingInfo;
-  }
-
   public boolean hasMinimumKeepInfoThatMatches(
       DexProgramClass clazz, Predicate<KeepClassInfo.Joiner> predicate) {
     MinimumKeepInfoCollection minimumKeepInfoCollection =
@@ -853,7 +894,7 @@
         // rules.
         handleLibraryTypeInheritingFromProgramType(clazz.asLibraryClass());
       }
-      analyses.processNewlyLiveNonProgramType(clazz);
+      analyses.forEach(analysis -> analysis.processNewLiveNonProgramType(clazz));
       clazz.forEachClassField(
           field ->
               addNonProgramClassToWorklist(
@@ -1136,7 +1177,7 @@
     }
   }
 
-  private FieldAccessInfoImpl getOrCreateFieldAccessInfo(DexClassAndField field) {
+  private FieldAccessInfoImpl getOrCreateFieldAccessInfo(DexEncodedField field) {
     // Check if we have previously created a FieldAccessInfo object for the field definition.
     FieldAccessInfoImpl info = fieldAccessInfoCollection.get(field.getReference());
 
@@ -1144,11 +1185,6 @@
     if (info == null) {
       info = new FieldAccessInfoImpl(field.getReference());
       fieldAccessInfoCollection.extend(field.getReference(), info);
-
-      // Notify analyses.
-      if (field.isProgramField()) {
-        analyses.processNewlyReferencedField(field.asProgramField());
-      }
     }
 
     return info;
@@ -1176,12 +1212,12 @@
         return true;
       }
 
-      DexClassAndField resolvedField = seenResult.get();
-      info = getOrCreateFieldAccessInfo(resolvedField);
+      DexEncodedField encodedField = seenResult.get().getDefinition();
+      info = getOrCreateFieldAccessInfo(encodedField);
 
       // If `field` is an indirect reference, then create a mapping for it, such that we don't have
       // to resolve the field the next time we see the reference.
-      if (field != resolvedField.getReference()) {
+      if (field != encodedField.getReference()) {
         fieldAccessInfoCollection.extend(field, info);
       }
     } else if (info == MISSING_FIELD_ACCESS_INFO) {
@@ -1281,12 +1317,12 @@
 
   void traceCheckCast(DexType type, ProgramMethod currentMethod, boolean ignoreCompatRules) {
     DexClass clazz = internalTraceConstClassOrCheckCast(type, currentMethod, ignoreCompatRules);
-    analyses.traceCheckCast(type, clazz, currentMethod);
+    checkCastAnalyses.forEach(analysis -> analysis.traceCheckCast(type, clazz, currentMethod));
   }
 
   void traceSafeCheckCast(DexType type, ProgramMethod currentMethod) {
     DexClass clazz = internalTraceConstClassOrCheckCast(type, currentMethod, true);
-    analyses.traceSafeCheckCast(type, clazz, currentMethod);
+    checkCastAnalyses.forEach(analysis -> analysis.traceSafeCheckCast(type, clazz, currentMethod));
   }
 
   void traceConstClass(
@@ -1296,7 +1332,7 @@
       boolean ignoreCompatRules) {
     handleLockCandidate(type, currentMethod, iterator);
     DexClass clazz = internalTraceConstClassOrCheckCast(type, currentMethod, ignoreCompatRules);
-    analyses.traceConstClass(type, clazz, currentMethod);
+    constClassAnalyses.forEach(analysis -> analysis.traceConstClass(type, clazz, currentMethod));
   }
 
   private void handleLockCandidate(
@@ -1459,13 +1495,14 @@
   void traceInstanceOf(DexType type, ProgramMethod currentMethod) {
     DexClass clazz = resolveBaseType(type, currentMethod);
     traceTypeReference(type, currentMethod);
-    analyses.traceInstanceOf(type, clazz, currentMethod);
+    instanceOfAnalyses.forEach(analysis -> analysis.traceInstanceOf(type, clazz, currentMethod));
   }
 
   void traceExceptionGuard(DexType type, ProgramMethod currentMethod) {
     DexClass clazz = resolveBaseType(type, currentMethod);
     traceTypeReference(type, currentMethod);
-    analyses.traceExceptionGuard(type, clazz, currentMethod);
+    exceptionGuardAnalyses.forEach(
+        analysis -> analysis.traceExceptionGuard(type, clazz, currentMethod));
   }
 
   void traceInvokeDirect(
@@ -1514,7 +1551,8 @@
     markTypeAsLive(invokedMethod.getHolderType(), context);
     MethodResolutionResult resolutionResult =
         handleInvokeOfDirectTarget(invokedMethod, context, reason);
-    analyses.traceInvokeDirect(invokedMethod, resolutionResult, context);
+    invokeAnalyses.forEach(
+        analysis -> analysis.traceInvokeDirect(invokedMethod, resolutionResult, context));
   }
 
   void traceInvokeInterface(
@@ -1539,7 +1577,8 @@
     markTypeAsLive(invokedMethod.getHolderType(), context);
     MethodResolutionResult result =
         markVirtualMethodAsReachable(invokedMethod, true, context, keepReason);
-    analyses.traceInvokeInterface(invokedMethod, result, context);
+    invokeAnalyses.forEach(
+        analysis -> analysis.traceInvokeInterface(invokedMethod, result, context));
   }
 
   void traceInvokeStatic(
@@ -1584,7 +1623,8 @@
     markTypeAsLive(invokedMethod.getHolderType(), context);
     MethodResolutionResult resolutionResult =
         handleInvokeOfStaticTarget(invokedMethod, context, reason);
-    analyses.traceInvokeStatic(invokedMethod, resolutionResult, context);
+    invokeAnalyses.forEach(
+        analysis -> analysis.traceInvokeStatic(invokedMethod, resolutionResult, context));
   }
 
   void traceInvokeSuper(
@@ -1630,7 +1670,8 @@
     markTypeAsLive(invokedMethod.getHolderType(), context);
     MethodResolutionResult resolutionResult =
         markVirtualMethodAsReachable(invokedMethod, false, context, reason);
-    analyses.traceInvokeVirtual(invokedMethod, resolutionResult, context);
+    invokeAnalyses.forEach(
+        analysis -> analysis.traceInvokeVirtual(invokedMethod, resolutionResult, context));
   }
 
   void traceMethodPosition(com.android.tools.r8.ir.code.Position position, ProgramMethod context) {
@@ -1679,7 +1720,7 @@
             context,
             InstantiationReason.NEW_INSTANCE_INSTRUCTION,
             KeepReason.instantiatedIn(context));
-    analyses.traceNewInstance(type, clazz, context);
+    newInstanceAnalyses.forEach(analysis -> analysis.traceNewInstance(type, clazz, context));
   }
 
   void traceNewInstanceFromLambda(DexType type, ProgramMethod context) {
@@ -1830,8 +1871,10 @@
 
     resolutionResult.visitFieldResolutionResults(
         singleResolutionResult -> {
-          analyses.traceInstanceFieldRead(
-              fieldReference, singleResolutionResult, currentMethod, worklist);
+          fieldAccessAnalyses.forEach(
+              analysis ->
+                  analysis.traceInstanceFieldRead(
+                      fieldReference, singleResolutionResult, currentMethod, worklist));
 
           ProgramField field = singleResolutionResult.getProgramField();
           if (field == null) {
@@ -1894,8 +1937,10 @@
 
     resolutionResult.visitFieldResolutionResults(
         singleResolutionResult -> {
-          analyses.traceInstanceFieldWrite(
-              fieldReference, singleResolutionResult, currentMethod, worklist);
+          fieldAccessAnalyses.forEach(
+              analysis ->
+                  analysis.traceInstanceFieldWrite(
+                      fieldReference, singleResolutionResult, currentMethod, worklist));
 
           ProgramField field = singleResolutionResult.getProgramField();
           if (field == null) {
@@ -1971,8 +2016,10 @@
 
     resolutionResult.visitFieldResolutionResults(
         singleResolutionResult -> {
-          analyses.traceStaticFieldRead(
-              fieldReference, singleResolutionResult, currentMethod, worklist);
+          fieldAccessAnalyses.forEach(
+              analysis ->
+                  analysis.traceStaticFieldRead(
+                      fieldReference, singleResolutionResult, currentMethod, worklist));
 
           ProgramField field = singleResolutionResult.getProgramField();
           if (field == null) {
@@ -2059,8 +2106,10 @@
 
     resolutionResult.visitFieldResolutionResults(
         singleResolutionResult -> {
-          analyses.traceStaticFieldWrite(
-              fieldReference, singleResolutionResult, currentMethod, worklist);
+          fieldAccessAnalyses.forEach(
+              analysis ->
+                  analysis.traceStaticFieldWrite(
+                      fieldReference, singleResolutionResult, currentMethod, worklist));
 
           ProgramField field = singleResolutionResult.getProgramField();
           if (field == null) {
@@ -2304,7 +2353,7 @@
     compatEnqueueHolderIfDependentNonStaticMember(
         clazz, rootSet.getDependentKeepClassCompatRule(clazz.getType()));
 
-    analyses.processNewlyLiveClass(clazz, worklist);
+    analyses.forEach(analysis -> analysis.processNewlyLiveClass(clazz, worklist));
   }
 
   private void processDeferredAnnotations(
@@ -2892,7 +2941,9 @@
     // 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.processNewlyInstantiatedClass(clazz.asProgramClass(), context, worklist);
+    analyses.forEach(
+        analysis ->
+            analysis.processNewlyInstantiatedClass(clazz.asProgramClass(), context, worklist));
 
     if (!markInstantiatedClass(clazz, context, instantiationReason, keepReason)) {
       return;
@@ -3303,7 +3354,7 @@
     }
 
     // Notify analyses.
-    analyses.processNewlyLiveField(field, context, worklist);
+    analyses.forEach(analysis -> analysis.processNewlyLiveField(field, context, worklist));
   }
 
   // Package protected due to entry point from worklist.
@@ -3331,7 +3382,7 @@
     addEffectivelyLiveOriginalField(field);
     traceFieldDefinition(field);
 
-    analyses.processNewlyReachableField(field, worklist);
+    analyses.forEach(analysis -> analysis.notifyMarkFieldAsReachable(field, worklist));
   }
 
   private void handleFieldAccessWithInaccessibleFieldType(
@@ -3482,6 +3533,29 @@
     return liveTypes.contains(clazz);
   }
 
+  public boolean isEffectivelyLive(DexProgramClass clazz) {
+    if (isTypeLive(clazz)) {
+      return true;
+    }
+    if (mode.isInitialTreeShaking()) {
+      return false;
+    }
+    // TODO(b/325014359): Replace this by value tracking in instructions (akin to resource values).
+    for (DexEncodedField field : clazz.fields()) {
+      if (field.getOptimizationInfo().valueHasBeenPropagated()) {
+        return true;
+      }
+    }
+    // TODO(b/325014359): Replace this by value or position tracking.
+    //  We need to be careful not to throw away such values/positions.
+    for (DexEncodedMethod method : clazz.methods()) {
+      if (method.getOptimizationInfo().returnValueHasBeenPropagated()) {
+        return true;
+      }
+    }
+    return false;
+  }
+
   public boolean isOriginalReferenceEffectivelyLive(DexReference reference) {
     // The effectively-live original set contains types, fields and methods witnessed by
     // instructions, such as method inlining positions.
@@ -3612,6 +3686,7 @@
     target.accept(
         method -> markVirtualDispatchMethodTargetAsLive(method, reason),
         lambda -> markVirtualDispatchLambdaTargetAsLive(lambda, reason));
+    analyses.forEach(analysis -> analysis.notifyMarkVirtualDispatchTargetAsLive(target, worklist));
   }
 
   private void markVirtualDispatchMethodTargetAsLive(
@@ -3743,8 +3818,10 @@
               resolution.lookupInvokeSuperTarget(context.getHolder(), appView);
           if (target == null) {
             failedMethodResolutionTargets.add(resolution.getResolvedMethod().getReference());
-            analyses.processNewlyFailedMethodResolutionTarget(
-                resolution.getResolvedMethod(), worklist);
+            analyses.forEach(
+                analyses ->
+                    analyses.notifyFailedMethodResolutionTarget(
+                        resolution.getResolvedMethod(), worklist));
             return;
           }
 
@@ -3766,7 +3843,8 @@
             }
           }
         });
-    analyses.traceInvokeSuper(reference, resolutionResults, context);
+    invokeAnalyses.forEach(
+        analysis -> analysis.traceInvokeSuper(reference, resolutionResults, context));
   }
 
   public boolean isRClass(DexProgramClass dexProgramClass) {
@@ -3803,6 +3881,29 @@
       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");
@@ -3848,7 +3949,8 @@
     finalizeLibraryMethodOverrideInformation();
     timing.end();
     timing.begin("Finish analysis");
-    analyses.done(this);
+    analyses.forEach(analyses -> analyses.done(this));
+    fieldAccessAnalyses.forEach(fieldAccessAnalyses -> fieldAccessAnalyses.done(this));
     if (appView.options().isOptimizedResourceShrinking()) {
       appView.getResourceShrinkerState().enqueuerDone(this.mode.isFinalTreeShaking());
     }
@@ -4653,6 +4755,28 @@
         long numberOfLiveItemsAfterProcessing = getNumberOfLiveItems();
         if (numberOfLiveItemsAfterProcessing > numberOfLiveItems) {
           timing.time("Conditional rules", () -> applicableRules.evaluateConditionalRules(this));
+
+          // Build the mapping of active if rules. We use a single collection of if-rules to allow
+          // removing if rules that have a constant sequent keep rule when they materialize.
+          if (activeIfRules == null) {
+            activeIfRules = new HashMap<>();
+            IfRuleClassPartEquivalence equivalence = new IfRuleClassPartEquivalence();
+            for (ProguardIfRule ifRule : rootSet.ifRules) {
+              Wrapper<ProguardIfRule> wrap = equivalence.wrap(ifRule);
+              activeIfRules.computeIfAbsent(wrap, ignore -> new LinkedHashSet<>()).add(ifRule);
+            }
+          }
+          ConsequentRootSetBuilder consequentSetBuilder =
+              ConsequentRootSet.builder(appView, this, subtypingInfo);
+          IfRuleEvaluator ifRuleEvaluator =
+              new IfRuleEvaluator(
+                  appView,
+                  subtypingInfo,
+                  this,
+                  executorService,
+                  activeIfRules,
+                  consequentSetBuilder);
+          addConsequentRootSet(ifRuleEvaluator.run());
           assert getNumberOfLiveItems() == numberOfLiveItemsAfterProcessing;
           if (!worklist.isEmpty()) {
             continue;
@@ -4682,7 +4806,9 @@
 
         // Notify each analysis that a fixpoint has been reached, and give each analysis an
         // opportunity to add items to the worklist.
-        analyses.notifyFixpoint(this, worklist, executorService, timing);
+        for (EnqueuerAnalysis analysis : analyses) {
+          analysis.notifyFixpoint(this, worklist, executorService, timing);
+        }
         if (!worklist.isEmpty()) {
           continue;
         }
@@ -4770,7 +4896,7 @@
     }
   }
 
-  public long getNumberOfLiveItems() {
+  private long getNumberOfLiveItems() {
     long result = liveTypes.getItems().size();
     result += liveMethods.items.size();
     result += liveFields.fields.size();
@@ -4778,7 +4904,7 @@
     return result;
   }
 
-  void addConsequentRootSet(ConsequentRootSet consequentRootSet) {
+  private void addConsequentRootSet(ConsequentRootSet consequentRootSet) {
     // TODO(b/132600955): This modifies the root set, but the consequent should not be persistent.
     //  Instead, the consequent root set should be added to collections that are owned by the
     //  enqueuer, similar to Enqueuer#dependentMinimumKeepClassInfo.
@@ -4881,7 +5007,7 @@
 
   // Package protected due to entry point from worklist.
   void markFieldAsKept(ProgramField field, KeepReason reason) {
-    FieldAccessInfoImpl fieldAccessInfo = getOrCreateFieldAccessInfo(field);
+    FieldAccessInfoImpl fieldAccessInfo = getOrCreateFieldAccessInfo(field.getDefinition());
     fieldAccessInfo.setHasReflectiveRead();
     fieldAccessInfo.setHasReflectiveWrite();
 
@@ -4979,7 +5105,8 @@
       }
     }
 
-    analyses.processNewlyLiveMethod(method, context, this, worklist);
+    // Notify analyses.
+    analyses.forEach(analysis -> analysis.processNewlyLiveMethod(method, context, this, worklist));
   }
 
   private void markMethodAsTargeted(ProgramMethod method, KeepReason reason) {
@@ -4999,7 +5126,7 @@
         markMethodAsLiveWithCompatRule(method);
       }
     }
-    analyses.processNewlyTargetedMethod(method, worklist);
+    analyses.forEach(analysis -> analysis.notifyMarkMethodAsTargeted(method, worklist));
   }
 
   void traceMethodDefinitionExcludingCode(ProgramMethod method) {
@@ -5033,7 +5160,8 @@
     DefaultEnqueuerUseRegistry registry =
         useRegistryFactory.create(appView, method, this, appView.apiLevelCompute());
     method.registerCodeReferences(registry);
-    analyses.processNewlyLiveCode(method, registry, worklist);
+    // Notify analyses.
+    analyses.forEach(analysis -> analysis.processTracedCode(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 09ea894..e5f90c2 100644
--- a/src/main/java/com/android/tools/r8/shaking/EnqueuerFactory.java
+++ b/src/main/java/com/android/tools/r8/shaking/EnqueuerFactory.java
@@ -35,8 +35,7 @@
       ExecutorService executorService,
       SubtypingInfo subtypingInfo,
       GraphConsumer keptGraphConsumer,
-      Set<DexType> initialPrunedTypes,
-      RuntimeTypeCheckInfo.Builder runtimeTypeCheckInfoBuilder) {
+      Set<DexType> initialPrunedTypes) {
     ProfileCollectionAdditions profileCollectionAdditions =
         ProfileCollectionAdditions.create(appView);
     Enqueuer enqueuer =
@@ -46,11 +45,10 @@
             executorService,
             subtypingInfo,
             keptGraphConsumer,
-            Mode.FINAL_TREE_SHAKING,
-            initialPrunedTypes,
-            runtimeTypeCheckInfoBuilder);
+            Mode.FINAL_TREE_SHAKING);
     appView.withProtoShrinker(
         shrinker -> enqueuer.setInitialDeadProtoTypes(shrinker.getDeadProtoTypes()));
+    enqueuer.setInitialPrunedTypes(initialPrunedTypes);
     return enqueuer;
   }
 
diff --git a/src/main/java/com/android/tools/r8/shaking/IfRuleClassPartEquivalence.java b/src/main/java/com/android/tools/r8/shaking/IfRuleClassPartEquivalence.java
index 9501279..527565f 100644
--- a/src/main/java/com/android/tools/r8/shaking/IfRuleClassPartEquivalence.java
+++ b/src/main/java/com/android/tools/r8/shaking/IfRuleClassPartEquivalence.java
@@ -5,6 +5,7 @@
 package com.android.tools.r8.shaking;
 
 import com.google.common.base.Equivalence;
+import com.google.common.collect.Iterables;
 import java.util.Objects;
 
 public class IfRuleClassPartEquivalence extends Equivalence<ProguardIfRule> {
@@ -52,6 +53,7 @@
                 ? rule.getInheritanceClassName().hashCode()
                 : 0);
     result = 3 * result + (rule.getInheritanceIsImplements() ? 1 : 0);
+    result = 3 * result + Iterables.size(rule.subsequentRule.getWildcards());
     return result;
   }
 }
diff --git a/src/main/java/com/android/tools/r8/shaking/IfRuleEvaluator.java b/src/main/java/com/android/tools/r8/shaking/IfRuleEvaluator.java
index da2ca1d..44ba57d 100644
--- a/src/main/java/com/android/tools/r8/shaking/IfRuleEvaluator.java
+++ b/src/main/java/com/android/tools/r8/shaking/IfRuleEvaluator.java
@@ -14,22 +14,22 @@
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.SubtypingInfo;
 import com.android.tools.r8.shaking.InlineRule.InlineRuleType;
+import com.android.tools.r8.shaking.RootSetUtils.ConsequentRootSet;
 import com.android.tools.r8.shaking.RootSetUtils.ConsequentRootSetBuilder;
 import com.android.tools.r8.shaking.RootSetUtils.RootSetBuilder;
 import com.android.tools.r8.threading.TaskCollection;
-import com.android.tools.r8.utils.MapUtils;
-import com.android.tools.r8.utils.UncheckedExecutionException;
+import com.android.tools.r8.utils.InternalOptions.TestingOptions.ProguardIfRuleEvaluationData;
 import com.google.common.base.Equivalence.Wrapper;
 import com.google.common.collect.Iterables;
 import com.google.common.collect.Sets;
 import java.util.ArrayList;
 import java.util.Collection;
+import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
 import java.util.concurrent.ExecutionException;
-import java.util.function.Consumer;
-import java.util.function.Predicate;
+import java.util.concurrent.ExecutorService;
 import java.util.stream.Collectors;
 
 public class IfRuleEvaluator {
@@ -37,6 +37,7 @@
   private final AppView<? extends AppInfoWithClassHierarchy> appView;
   private final SubtypingInfo subtypingInfo;
   private final Enqueuer enqueuer;
+  private final Map<Wrapper<ProguardIfRule>, Set<ProguardIfRule>> ifRules;
   private final ConsequentRootSetBuilder rootSetBuilder;
   private final TaskCollection<?> tasks;
 
@@ -44,25 +45,29 @@
       AppView<? extends AppInfoWithClassHierarchy> appView,
       SubtypingInfo subtypingInfo,
       Enqueuer enqueuer,
-      ConsequentRootSetBuilder rootSetBuilder,
-      TaskCollection<?> tasks) {
-    assert tasks.isEmpty();
+      ExecutorService executorService,
+      Map<Wrapper<ProguardIfRule>, Set<ProguardIfRule>> ifRules,
+      ConsequentRootSetBuilder rootSetBuilder) {
     this.appView = appView;
     this.subtypingInfo = subtypingInfo;
     this.enqueuer = enqueuer;
+    this.ifRules = ifRules;
     this.rootSetBuilder = rootSetBuilder;
-    this.tasks = tasks;
+    this.tasks = new TaskCollection<>(appView.options(), executorService);
   }
 
-  public void processActiveIfRulesWithMembers(
-      Map<Wrapper<ProguardIfRule>, Set<ProguardIfRule>> ifRules,
-      Iterable<DexProgramClass> classesWithNewlyLiveMembers,
-      Predicate<DexProgramClass> isEffectivelyLive)
-      throws ExecutionException {
-    MapUtils.removeIf(
-        ifRules,
-        (ifRuleWrapper, ifRulesInEquivalence) -> {
-          ProguardIfRule ifRuleKey = ifRuleWrapper.get();
+  public ConsequentRootSet run() throws ExecutionException {
+    appView.appInfo().app().timing.begin("Find consequent items for -if rules...");
+    try {
+      if (ifRules != null && !ifRules.isEmpty()) {
+        Iterator<Map.Entry<Wrapper<ProguardIfRule>, Set<ProguardIfRule>>> it =
+            ifRules.entrySet().iterator();
+        while (it.hasNext()) {
+          Map.Entry<Wrapper<ProguardIfRule>, Set<ProguardIfRule>> ifRuleEntry = it.next();
+          ProguardIfRule ifRuleKey = ifRuleEntry.getKey().get();
+          Set<ProguardIfRule> ifRulesInEquivalence = ifRuleEntry.getValue();
+          ProguardIfRuleEvaluationData ifRuleEvaluationData =
+              appView.options().testing.proguardIfRuleEvaluationData;
           List<ProguardIfRule> toRemove = new ArrayList<>();
 
           // Depending on which types that trigger the -if rule, the application of the subsequent
@@ -70,82 +75,43 @@
           // rule and live types.
           for (DexProgramClass clazz :
               ifRuleKey.relevantCandidatesForRule(
-                  appView, subtypingInfo, classesWithNewlyLiveMembers, isEffectivelyLive)) {
-            assert isEffectivelyLive.test(clazz);
-            evaluateRuleOnEffectivelyLiveClass(
-                ifRuleKey,
-                ifRulesInEquivalence,
-                clazz,
-                matchedIfRule -> {
-                  if (canRemoveSubsequentKeepRule(matchedIfRule)) {
-                    toRemove.add(matchedIfRule);
-                  }
-                });
-          }
-          if (ifRulesInEquivalence.size() == toRemove.size()) {
-            return true;
-          }
-          toRemove.forEach(ifRulesInEquivalence::remove);
-          return false;
-        });
-    tasks.await();
-  }
+                  appView, subtypingInfo, appView.appInfo().classes())) {
+            if (!isEffectivelyLive(clazz)) {
+              continue;
+            }
 
-  public void processActiveIfRulesWithoutMembers(
-      Map<Wrapper<ProguardIfRule>, Set<ProguardIfRule>> ifRules,
-      Iterable<DexProgramClass> newlyLiveClasses)
-      throws ExecutionException {
-    MapUtils.removeIf(
-        ifRules,
-        (ifRuleWrapper, ifRulesInEquivalence) -> {
-          // Depending on which types that trigger the -if rule, the application of the subsequent
-          // -keep rule may vary (due to back references). So, we need to try all pairs of -if
-          // rule and live types.
-          ProguardIfRule ifRuleKey = ifRuleWrapper.get();
-          for (DexProgramClass clazz : newlyLiveClasses) {
+            // Check if the class matches the if-rule.
+            if (appView.options().testing.measureProguardIfRuleEvaluations) {
+              ifRuleEvaluationData.numberOfProguardIfRuleClassEvaluations++;
+            }
             if (evaluateClassForIfRule(ifRuleKey, clazz)) {
-              ifRulesInEquivalence.removeIf(
-                  ifRule -> {
-                    registerClassCapture(ifRule, clazz, clazz);
-                    uncheckedMaterializeIfRule(ifRule, clazz);
-                    return canRemoveSubsequentKeepRule(ifRule);
-                  });
+              // When matching an if rule against a type, the if-rule are filled with the current
+              // capture of wildcards. Propagate this down to member rules with same class part
+              // equivalence.
+              for (ProguardIfRule ifRule : ifRulesInEquivalence) {
+                registerClassCapture(ifRule, clazz, clazz);
+                if (appView.options().testing.measureProguardIfRuleEvaluations) {
+                  ifRuleEvaluationData.numberOfProguardIfRuleMemberEvaluations++;
+                }
+                boolean matched = evaluateIfRuleMembersAndMaterialize(ifRule, clazz);
+                if (matched && canRemoveSubsequentKeepRule(ifRule)) {
+                  toRemove.add(ifRule);
+                }
+              }
             }
           }
-          return ifRulesInEquivalence.isEmpty();
-        });
-    tasks.await();
-  }
-
-  private void evaluateRuleOnEffectivelyLiveClass(
-      ProguardIfRule ifRuleKey,
-      Set<ProguardIfRule> ifRulesInEquivalence,
-      DexProgramClass clazz,
-      Consumer<ProguardIfRule> matchedConsumer)
-      throws ExecutionException {
-    // Check if the class matches the if-rule.
-    if (!evaluateClassForIfRule(ifRuleKey, clazz)) {
-      return;
+          if (ifRulesInEquivalence.size() == toRemove.size()) {
+            it.remove();
+          } else if (!toRemove.isEmpty()) {
+            ifRulesInEquivalence.removeAll(toRemove);
+          }
+        }
+        tasks.await();
+      }
+    } finally {
+      appView.appInfo().app().timing.end();
     }
-    // When matching an if rule against a type, the if-rule are filled with the current
-    // capture of wildcards. Propagate this down to member rules with same class part
-    // equivalence.
-    for (ProguardIfRule ifRule : ifRulesInEquivalence) {
-      registerClassCapture(ifRule, clazz, clazz);
-      evaluateIfRuleMembersAndMaterialize(ifRule, clazz, matchedConsumer);
-    }
-  }
-
-  private void incrementNumberOfProguardIfRuleClassEvaluations() {
-    if (appView.testing().measureProguardIfRuleEvaluations) {
-      appView.testing().proguardIfRuleEvaluationData.numberOfProguardIfRuleClassEvaluations++;
-    }
-  }
-
-  private void incrementNumberOfProguardIfRuleMemberEvaluations() {
-    if (appView.testing().measureProguardIfRuleEvaluations) {
-      appView.testing().proguardIfRuleEvaluationData.numberOfProguardIfRuleMemberEvaluations++;
-    }
+    return rootSetBuilder.buildConsequentRootSet();
   }
 
   private boolean canRemoveSubsequentKeepRule(ProguardIfRule rule) {
@@ -169,9 +135,12 @@
     }
   }
 
+  private boolean isEffectivelyLive(DexProgramClass clazz) {
+    return enqueuer.isEffectivelyLive(clazz);
+  }
+
   /** Determines if {@param clazz} satisfies the given if-rule class specification. */
   private boolean evaluateClassForIfRule(ProguardIfRule rule, DexProgramClass clazz) {
-    incrementNumberOfProguardIfRuleClassEvaluations();
     if (!RootSetBuilder.satisfyClassType(rule, clazz)) {
       return false;
     }
@@ -193,12 +162,13 @@
     return true;
   }
 
-  private void evaluateIfRuleMembersAndMaterialize(
-      ProguardIfRule rule, DexProgramClass clazz, Consumer<ProguardIfRule> matchedConsumer)
+  private boolean evaluateIfRuleMembersAndMaterialize(ProguardIfRule rule, DexProgramClass clazz)
       throws ExecutionException {
-    incrementNumberOfProguardIfRuleMemberEvaluations();
     Collection<ProguardMemberRule> memberKeepRules = rule.getMemberRules();
-    assert !memberKeepRules.isEmpty();
+    if (memberKeepRules.isEmpty()) {
+      materializeIfRule(rule, clazz);
+      return true;
+    }
 
     List<DexClassAndField> fieldsInlinedByJavaC = new ArrayList<>();
     Set<DexDefinition> filteredMembers = Sets.newIdentityHashSet();
@@ -253,7 +223,7 @@
 
     // If the number of member rules to hold is more than live members, we can't make it.
     if (filteredMembers.size() < memberKeepRules.size()) {
-      return;
+      return false;
     }
 
     // Depending on which members trigger the -if rule, the application of the subsequent
@@ -283,11 +253,11 @@
       if (satisfied) {
         materializeIfRule(rule, clazz);
         if (canRemoveSubsequentKeepRule(rule)) {
-          matchedConsumer.accept(rule);
-          return;
+          return true;
         }
       }
     }
+    return false;
   }
 
   private boolean isFieldInlinedByJavaC(DexEncodedField field) {
@@ -299,14 +269,6 @@
     return field.getOrComputeIsInlinableByJavaC(appView.dexItemFactory());
   }
 
-  private void uncheckedMaterializeIfRule(ProguardIfRule rule, DexProgramClass precondition) {
-    try {
-      materializeIfRule(rule, precondition);
-    } catch (ExecutionException e) {
-      throw new UncheckedExecutionException(e);
-    }
-  }
-
   private void materializeIfRule(ProguardIfRule rule, DexProgramClass precondition)
       throws ExecutionException {
     DexItemFactory dexItemFactory = appView.dexItemFactory();
diff --git a/src/main/java/com/android/tools/r8/shaking/IfRuleEvaluatorFactory.java b/src/main/java/com/android/tools/r8/shaking/IfRuleEvaluatorFactory.java
deleted file mode 100644
index 497f691..0000000
--- a/src/main/java/com/android/tools/r8/shaking/IfRuleEvaluatorFactory.java
+++ /dev/null
@@ -1,272 +0,0 @@
-// 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.shaking;
-
-import static com.android.tools.r8.utils.MapUtils.ignoreKey;
-
-import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
-import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.DexEncodedField;
-import com.android.tools.r8.graph.DexEncodedMethod;
-import com.android.tools.r8.graph.DexProgramClass;
-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.SubtypingInfo;
-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;
-import com.android.tools.r8.utils.Timing;
-import com.google.common.base.Equivalence.Wrapper;
-import com.google.common.collect.Iterables;
-import com.google.common.collect.Sets;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.LinkedHashSet;
-import java.util.Map;
-import java.util.Set;
-import java.util.concurrent.ExecutionException;
-import java.util.concurrent.ExecutorService;
-
-public class IfRuleEvaluatorFactory
-    implements NewlyLiveClassEnqueuerAnalysis,
-        NewlyLiveFieldEnqueuerAnalysis,
-        NewlyLiveMethodEnqueuerAnalysis,
-        NewlyReferencedFieldEnqueuerAnalysis,
-        NewlyTargetedMethodEnqueuerAnalysis,
-        FixpointEnqueuerAnalysis {
-
-  private final AppView<? extends AppInfoWithClassHierarchy> appView;
-
-  /** Map of active if rules. This is important for speeding up aapt2 generated keep rules. */
-  private final Map<Wrapper<ProguardIfRule>, Set<ProguardIfRule>> activeIfRulesWithMembers;
-
-  private final Map<Wrapper<ProguardIfRule>, Set<ProguardIfRule>> activeIfRulesWithoutMembers;
-
-  private final Set<DexProgramClass> effectivelyFakeLiveClasses;
-  private final Set<DexProgramClass> newlyLiveClasses = Sets.newIdentityHashSet();
-  private final Set<DexProgramClass> classesWithNewlyLiveMembers = Sets.newIdentityHashSet();
-
-  private boolean seenFixpoint;
-
-  private final TaskCollection<?> tasks;
-
-  public IfRuleEvaluatorFactory(
-      AppView<? extends AppInfoWithClassHierarchy> appView,
-      Enqueuer enqueuer,
-      ExecutorService executorService) {
-    this.appView = appView;
-    this.activeIfRulesWithMembers = createActiveIfRules(appView.rootSet().ifRules, true);
-    this.activeIfRulesWithoutMembers = createActiveIfRules(appView.rootSet().ifRules, false);
-    this.effectivelyFakeLiveClasses = createEffectivelyFakeLiveClasses(appView, enqueuer);
-    this.tasks = new TaskCollection<>(appView.options(), executorService);
-  }
-
-  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()) {
-      IfRuleEvaluatorFactory factory =
-          new IfRuleEvaluatorFactory(appView, enqueuer, executorService);
-      builder
-          .addNewlyLiveClassAnalysis(factory)
-          .addNewlyLiveFieldAnalysis(factory)
-          .addNewlyLiveMethodAnalysis(factory)
-          .addNewlyReferencedFieldAnalysis(factory)
-          .addNewlyTargetedMethodAnalysis(factory)
-          .addFixpointAnalysis(factory);
-    }
-  }
-
-  private static Map<Wrapper<ProguardIfRule>, Set<ProguardIfRule>> createActiveIfRules(
-      Set<ProguardIfRule> ifRules, boolean withMembers) {
-    // Build the mapping of active if rules. We use a single collection of if-rules to allow
-    // removing if rules that have a constant sequent keep rule when they materialize.
-    Map<Wrapper<ProguardIfRule>, Set<ProguardIfRule>> activeIfRules = new HashMap<>(ifRules.size());
-    IfRuleClassPartEquivalence equivalence = new IfRuleClassPartEquivalence();
-    for (ProguardIfRule ifRule : ifRules) {
-      boolean hasMembers = !ifRule.getMemberRules().isEmpty();
-      if (hasMembers == withMembers) {
-        Wrapper<ProguardIfRule> wrap = equivalence.wrap(ifRule);
-        activeIfRules.computeIfAbsent(wrap, ignoreKey(LinkedHashSet::new)).add(ifRule);
-      }
-    }
-    return activeIfRules;
-  }
-
-  @SuppressWarnings("MixedMutabilityReturnType")
-  private static Set<DexProgramClass> createEffectivelyFakeLiveClasses(
-      AppView<? extends AppInfoWithClassHierarchy> appView, Enqueuer enqueuer) {
-    if (enqueuer.getMode().isInitialTreeShaking()) {
-      return Collections.emptySet();
-    }
-    Set<DexProgramClass> effectivelyFakeLiveClasses = Sets.newIdentityHashSet();
-    for (DexProgramClass clazz : appView.appInfo().classes()) {
-      if (isFakeEffectiveLive(clazz)) {
-        effectivelyFakeLiveClasses.add(clazz);
-      }
-    }
-    return effectivelyFakeLiveClasses;
-  }
-
-  private static boolean isFakeEffectiveLive(DexProgramClass clazz) {
-    // TODO(b/325014359): Replace this by value tracking in instructions (akin to resource values).
-    for (DexEncodedField field : clazz.fields()) {
-      if (field.getOptimizationInfo().valueHasBeenPropagated()) {
-        return true;
-      }
-    }
-    // TODO(b/325014359): Replace this by value or position tracking.
-    //  We need to be careful not to throw away such values/positions.
-    for (DexEncodedMethod method : clazz.methods()) {
-      if (method.getOptimizationInfo().returnValueHasBeenPropagated()) {
-        return true;
-      }
-    }
-    return false;
-  }
-
-  @Override
-  public void notifyFixpoint(
-      Enqueuer enqueuer, EnqueuerWorklist worklist, ExecutorService executorService, Timing timing)
-      throws ExecutionException {
-    boolean isFirstFixpoint = setSeenFixpoint();
-    if (!shouldProcessActiveIfRulesWithMembers(isFirstFixpoint)
-        && !shouldProcessActiveIfRulesWithoutMembers(isFirstFixpoint)) {
-      return;
-    }
-    long numberOfLiveItemsAtStart = enqueuer.getNumberOfLiveItems();
-    ConsequentRootSet consequentRootSet =
-        timing.time(
-            "Find consequent items for -if rules...",
-            () -> processActiveIfRules(enqueuer, isFirstFixpoint));
-    enqueuer.addConsequentRootSet(consequentRootSet);
-    assert enqueuer.getNumberOfLiveItems() == numberOfLiveItemsAtStart;
-  }
-
-  private boolean shouldProcessActiveIfRulesWithMembers(boolean isFirstFixpoint) {
-    if (activeIfRulesWithMembers.isEmpty()) {
-      return false;
-    }
-    if (isFirstFixpoint && !effectivelyFakeLiveClasses.isEmpty()) {
-      return true;
-    }
-    return !classesWithNewlyLiveMembers.isEmpty();
-  }
-
-  private ConsequentRootSet processActiveIfRules(Enqueuer enqueuer, boolean isFirstFixpoint)
-      throws ExecutionException {
-    SubtypingInfo subtypingInfo = enqueuer.getSubtypingInfo();
-    ConsequentRootSetBuilder consequentRootSetBuilder =
-        ConsequentRootSet.builder(appView, enqueuer, subtypingInfo);
-    IfRuleEvaluator evaluator =
-        new IfRuleEvaluator(appView, subtypingInfo, enqueuer, consequentRootSetBuilder, tasks);
-    if (shouldProcessActiveIfRulesWithMembers(isFirstFixpoint)) {
-      processActiveIfRulesWithMembers(evaluator, isFirstFixpoint);
-    }
-    if (shouldProcessActiveIfRulesWithoutMembers(isFirstFixpoint)) {
-      processActiveIfRulesWithoutMembers(evaluator, isFirstFixpoint);
-    }
-    return consequentRootSetBuilder.buildConsequentRootSet();
-  }
-
-  private void processActiveIfRulesWithMembers(IfRuleEvaluator evaluator, boolean isFirstFixpoint)
-      throws ExecutionException {
-    if (isFirstFixpoint && !effectivelyFakeLiveClasses.isEmpty()) {
-      evaluator.processActiveIfRulesWithMembers(
-          activeIfRulesWithMembers,
-          Iterables.concat(effectivelyFakeLiveClasses, classesWithNewlyLiveMembers),
-          clazz ->
-              effectivelyFakeLiveClasses.contains(clazz)
-                  || classesWithNewlyLiveMembers.contains(clazz));
-    } else {
-      evaluator.processActiveIfRulesWithMembers(
-          activeIfRulesWithMembers,
-          classesWithNewlyLiveMembers,
-          classesWithNewlyLiveMembers::contains);
-    }
-    classesWithNewlyLiveMembers.clear();
-  }
-
-  private boolean shouldProcessActiveIfRulesWithoutMembers(boolean isFirstFixpoint) {
-    if (activeIfRulesWithoutMembers.isEmpty()) {
-      return false;
-    }
-    if (isFirstFixpoint && !effectivelyFakeLiveClasses.isEmpty()) {
-      return true;
-    }
-    return !newlyLiveClasses.isEmpty();
-  }
-
-  private void processActiveIfRulesWithoutMembers(
-      IfRuleEvaluator evaluator, boolean isFirstFixpoint) throws ExecutionException {
-    if (isFirstFixpoint && !effectivelyFakeLiveClasses.isEmpty()) {
-      evaluator.processActiveIfRulesWithoutMembers(
-          activeIfRulesWithoutMembers,
-          Iterables.concat(effectivelyFakeLiveClasses, newlyLiveClasses));
-    } else {
-      evaluator.processActiveIfRulesWithoutMembers(activeIfRulesWithoutMembers, newlyLiveClasses);
-    }
-    newlyLiveClasses.clear();
-  }
-
-  private boolean setSeenFixpoint() {
-    if (!seenFixpoint) {
-      seenFixpoint = true;
-      return true;
-    }
-    return false;
-  }
-
-  @Override
-  public void processNewlyLiveClass(DexProgramClass clazz, EnqueuerWorklist worklist) {
-    if (effectivelyFakeLiveClasses.contains(clazz)) {
-      return;
-    }
-    newlyLiveClasses.add(clazz);
-  }
-
-  @Override
-  public void processNewlyLiveField(
-      ProgramField field, ProgramDefinition context, EnqueuerWorklist worklist) {
-    addClassWithNewlyLiveMembers(field.getHolder());
-  }
-
-  @Override
-  public void processNewlyReferencedField(ProgramField field) {
-    addClassWithNewlyLiveMembers(field.getHolder());
-  }
-
-  @Override
-  public void processNewlyLiveMethod(
-      ProgramMethod method,
-      ProgramDefinition context,
-      Enqueuer enqueuer,
-      EnqueuerWorklist worklist) {
-    addClassWithNewlyLiveMembers(method.getHolder());
-  }
-
-  @Override
-  public void processNewlyTargetedMethod(ProgramMethod method, EnqueuerWorklist worklist) {
-    addClassWithNewlyLiveMembers(method.getHolder());
-  }
-
-  private void addClassWithNewlyLiveMembers(DexProgramClass clazz) {
-    // In the first fixpoint we report all effectively fake live classes as changed.
-    if (seenFixpoint || !effectivelyFakeLiveClasses.contains(clazz)) {
-      classesWithNewlyLiveMembers.add(clazz);
-    }
-  }
-}
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardClassNameList.java b/src/main/java/com/android/tools/r8/shaking/ProguardClassNameList.java
index 25c9cbf..bf87066 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardClassNameList.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardClassNameList.java
@@ -6,7 +6,6 @@
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.utils.IterableUtils;
-import com.android.tools.r8.utils.ListUtils;
 import com.android.tools.r8.utils.TraversalContinuation;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.Iterables;
@@ -83,9 +82,7 @@
   @Override
   public abstract int hashCode();
 
-  public abstract boolean hasSpecificTypes();
-
-  public abstract List<DexType> getSpecificTypes();
+  public abstract List<DexType> asSpecificDexTypes();
 
   public abstract boolean matches(DexType type);
 
@@ -157,12 +154,7 @@
     }
 
     @Override
-    public boolean hasSpecificTypes() {
-      return false;
-    }
-
-    @Override
-    public List<DexType> getSpecificTypes() {
+    public List<DexType> asSpecificDexTypes() {
       return null;
     }
 
@@ -219,15 +211,9 @@
     }
 
     @Override
-    public boolean hasSpecificTypes() {
-      return className.hasSpecificType();
-    }
-
-    @Override
-    public List<DexType> getSpecificTypes() {
-      return className.hasSpecificType()
-          ? Collections.singletonList(className.getSpecificType())
-          : null;
+    public List<DexType> asSpecificDexTypes() {
+      DexType specific = className.getSpecificType();
+      return specific == null ? null : Collections.singletonList(specific);
     }
 
     @Override
@@ -261,8 +247,6 @@
 
     private final ImmutableList<ProguardTypeMatcher> classNames;
 
-    private List<DexType> specificTypes;
-
     private PositiveClassNameList(Collection<ProguardTypeMatcher> classNames) {
       this.classNames = ImmutableList.copyOf(classNames);
     }
@@ -303,22 +287,15 @@
     }
 
     @Override
-    public boolean hasSpecificTypes() {
-      return getSpecificTypes() != null;
-    }
-
-    @Override
-    public List<DexType> getSpecificTypes() {
-      if (specificTypes == null) {
-        specificTypes =
-            classNames.stream().allMatch(ProguardTypeMatcher::hasSpecificType)
-                ? ListUtils.map(classNames, ProguardTypeMatcher::getSpecificType)
-                : Collections.emptyList();
+    public List<DexType> asSpecificDexTypes() {
+      if (classNames.stream().allMatch(k -> k.getSpecificType() != null)) {
+        return classNames.stream()
+            .map(ProguardTypeMatcher::getSpecificType)
+            .collect(Collectors.toList());
       }
-      return specificTypes.isEmpty() ? null : specificTypes;
+      return null;
     }
 
-
     @Override
     public boolean matches(DexType type) {
       return Iterables.any(classNames, name -> name.matches(type));
@@ -401,12 +378,7 @@
     }
 
     @Override
-    public boolean hasSpecificTypes() {
-      return false;
-    }
-
-    @Override
-    public List<DexType> getSpecificTypes() {
+    public List<DexType> asSpecificDexTypes() {
       return null;
     }
 
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationRule.java b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationRule.java
index 4e80401..01c9118 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationRule.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationRule.java
@@ -3,12 +3,11 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.shaking;
 
-import static com.android.tools.r8.graph.DexProgramClass.asProgramClassOrNull;
-
 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;
 import com.android.tools.r8.graph.DexDefinitionSupplier;
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexProgramClass;
@@ -19,11 +18,11 @@
 import com.android.tools.r8.utils.BooleanBox;
 import com.android.tools.r8.utils.IterableUtils;
 import com.android.tools.r8.utils.StringUtils;
+import com.google.common.collect.ImmutableList;
 import com.google.common.collect.Iterables;
 import java.util.Collections;
 import java.util.List;
 import java.util.Objects;
-import java.util.function.Predicate;
 
 public abstract class ProguardConfigurationRule extends ProguardClassSpecification {
 
@@ -150,53 +149,47 @@
   Iterable<DexProgramClass> relevantCandidatesForRule(
       AppView<? extends AppInfoWithClassHierarchy> appView,
       SubtypingInfo subtypingInfo,
-      Iterable<DexProgramClass> defaultValue,
-      Predicate<DexProgramClass> isRelevant) {
-    Iterable<DexType> specificTypes;
-    if (getClassNames().hasSpecificTypes()) {
-      specificTypes = getClassNames().getSpecificTypes();
-    } else if (hasInheritanceClassName() && getInheritanceClassName().hasSpecificType()) {
+      Iterable<DexProgramClass> defaultValue) {
+    List<DexType> specificTypes = getClassNames().asSpecificDexTypes();
+    if (specificTypes != null) {
+      return DexProgramClass.asProgramClasses(
+          specificTypes,
+          new DexDefinitionSupplier() {
+            @Override
+            public ClassResolutionResult contextIndependentDefinitionForWithResolutionResult(
+                DexType type) {
+              throw new Unreachable("Add support for multiple definitions with rule evaluation");
+            }
+
+            @Override
+            public DexClass definitionFor(DexType type) {
+              if (canReferenceDeadTypes) {
+                return appView.appInfo().definitionForWithoutExistenceAssert(type);
+              }
+              return appView.definitionFor(type);
+            }
+
+            @Override
+            public DexItemFactory dexItemFactory() {
+              return appView.dexItemFactory();
+            }
+          });
+    }
+    if (hasInheritanceClassName() && getInheritanceClassName().hasSpecificType()) {
       DexType type = getInheritanceClassName().getSpecificType();
       if (appView.getVerticallyMergedClasses() != null
           && appView.getVerticallyMergedClasses().hasBeenMergedIntoSubtype(type)) {
         DexType target = appView.getVerticallyMergedClasses().getTargetFor(type);
-        DexProgramClass clazz = asProgramClassOrNull(appView.definitionFor(target));
-        assert clazz != null;
-        specificTypes = IterableUtils.append(subtypingInfo.subtypes(type), clazz.getType());
+        DexClass clazz = appView.definitionFor(target);
+        assert clazz != null && clazz.isProgramClass();
+        return Iterables.concat(
+            ImmutableList.of(clazz.asProgramClass()),
+            DexProgramClass.asProgramClasses(subtypingInfo.subtypes(type), appView));
       } else {
-        specificTypes = subtypingInfo.subtypes(type);
+        return DexProgramClass.asProgramClasses(subtypingInfo.subtypes(type), appView);
       }
-    } else {
-      return defaultValue;
     }
-    assert specificTypes != null;
-    return DexProgramClass.asProgramClasses(
-        specificTypes,
-        new DexDefinitionSupplier() {
-          @Override
-          public ClassResolutionResult contextIndependentDefinitionForWithResolutionResult(
-              DexType type) {
-            throw new Unreachable("Add support for multiple definitions with rule evaluation");
-          }
-
-          @Override
-          public DexProgramClass definitionFor(DexType type) {
-            DexProgramClass result =
-                asProgramClassOrNull(
-                    canReferenceDeadTypes
-                        ? appView.appInfo().definitionForWithoutExistenceAssert(type)
-                        : appView.definitionFor(type));
-            if (result != null && isRelevant.test(result)) {
-              return result;
-            }
-            return null;
-          }
-
-          @Override
-          public DexItemFactory dexItemFactory() {
-            return appView.dexItemFactory();
-          }
-        });
+    return defaultValue;
   }
 
   abstract String typeString();
diff --git a/src/main/java/com/android/tools/r8/shaking/RootSetUtils.java b/src/main/java/com/android/tools/r8/shaking/RootSetUtils.java
index d2e7a82..46ef4f1 100644
--- a/src/main/java/com/android/tools/r8/shaking/RootSetUtils.java
+++ b/src/main/java/com/android/tools/r8/shaking/RootSetUtils.java
@@ -346,10 +346,11 @@
 
     void runPerRule(TaskCollection<?> tasks, ProguardConfigurationRule rule, ProguardIfRule ifRule)
         throws ExecutionException {
-      if (rule.getClassNames().hasSpecificTypes()) {
+      List<DexType> specifics = rule.getClassNames().asSpecificDexTypes();
+      if (specifics != null) {
         // This keep rule only lists specific type matches.
         // This means there is no need to iterate over all classes.
-        for (DexType type : rule.getClassNames().getSpecificTypes()) {
+        for (DexType type : specifics) {
           DexClass clazz = application.definitionFor(type);
           // Ignore keep rule iff it does not reference a class in the app.
           if (clazz != null) {
@@ -362,8 +363,7 @@
       tasks.submit(
           () -> {
             for (DexProgramClass clazz :
-                rule.relevantCandidatesForRule(
-                    appView, subtypingInfo, application.classes(), alwaysTrue())) {
+                rule.relevantCandidatesForRule(appView, subtypingInfo, application.classes())) {
               process(clazz, rule, ifRule);
             }
             if (rule.applyToNonProgramClasses()) {
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 705edd8..045227d 100644
--- a/src/main/java/com/android/tools/r8/shaking/RuntimeTypeCheckInfo.java
+++ b/src/main/java/com/android/tools/r8/shaking/RuntimeTypeCheckInfo.java
@@ -10,10 +10,9 @@
 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.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.analysis.EnqueuerCheckCastAnalysis;
+import com.android.tools.r8.graph.analysis.EnqueuerExceptionGuardAnalysis;
+import com.android.tools.r8.graph.analysis.EnqueuerInstanceOfAnalysis;
 import com.android.tools.r8.graph.lens.GraphLens;
 import com.android.tools.r8.graph.lens.NonIdentityGraphLens;
 import com.android.tools.r8.utils.SetUtils;
@@ -34,15 +33,6 @@
     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);
   }
@@ -69,9 +59,9 @@
   }
 
   public static class Builder
-      implements TraceInstanceOfEnqueuerAnalysis,
-          TraceCheckCastEnqueuerAnalysis,
-          TraceExceptionGuardEnqueuerAnalysis {
+      implements EnqueuerInstanceOfAnalysis,
+          EnqueuerCheckCastAnalysis,
+          EnqueuerExceptionGuardAnalysis {
 
     private final GraphLens appliedGraphLens;
     private final DexItemFactory factory;
@@ -119,5 +109,12 @@
         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 39fd650..02881e6 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -941,7 +941,8 @@
    * 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() {
+  public boolean isClassMergingExtensionRequired(Enqueuer.Mode mode) {
+    assert mode.isFinalTreeShaking();
     WholeProgramOptimizations wholeProgramOptimizations = WholeProgramOptimizations.ON;
     return horizontalClassMergerOptions.isEnabled(wholeProgramOptimizations)
         && !horizontalClassMergerOptions.isRestrictedToSynthetics();
diff --git a/src/main/java/com/android/tools/r8/utils/MapUtils.java b/src/main/java/com/android/tools/r8/utils/MapUtils.java
index ac5d7f6..c991a7d 100644
--- a/src/main/java/com/android/tools/r8/utils/MapUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/MapUtils.java
@@ -10,11 +10,11 @@
 import it.unimi.dsi.fastutil.ints.Int2ReferenceMaps;
 import java.util.Collections;
 import java.util.IdentityHashMap;
-import java.util.Iterator;
 import java.util.Map;
 import java.util.Map.Entry;
 import java.util.function.BiConsumer;
 import java.util.function.BiFunction;
+import java.util.function.BiPredicate;
 import java.util.function.Consumer;
 import java.util.function.Function;
 import java.util.function.IntFunction;
@@ -80,15 +80,8 @@
     map.entrySet().removeIf(entry -> entry.getKey() == entry.getValue());
   }
 
-  public static <K, V, E extends Throwable> void removeIf(
-      Map<K, V> map, ThrowingBiPredicate<K, V, E> predicate) throws E {
-    Iterator<Entry<K, V>> iterator = map.entrySet().iterator();
-    while (iterator.hasNext()) {
-      Entry<K, V> entry = iterator.next();
-      if (predicate.test(entry.getKey(), entry.getValue())) {
-        iterator.remove();
-      }
-    }
+  public static <K, V> void removeIf(Map<K, V> map, BiPredicate<K, V> predicate) {
+    map.entrySet().removeIf(entry -> predicate.test(entry.getKey(), entry.getValue()));
   }
 
   public static <K, V> V removeOrDefault(Map<K, V> map, K key, V defaultValue) {
diff --git a/src/main/java/com/android/tools/r8/utils/ThrowingBiPredicate.java b/src/main/java/com/android/tools/r8/utils/ThrowingBiPredicate.java
deleted file mode 100644
index 7e6fa66..0000000
--- a/src/main/java/com/android/tools/r8/utils/ThrowingBiPredicate.java
+++ /dev/null
@@ -1,9 +0,0 @@
-// 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.utils;
-
-public interface ThrowingBiPredicate<S, T, E extends Throwable> {
-
-  boolean test(S s, T t) throws E;
-}
diff --git a/src/test/java/com/android/tools/r8/shaking/ProguardConfigurationParserTest.java b/src/test/java/com/android/tools/r8/shaking/ProguardConfigurationParserTest.java
index 9b6bc09..564fd19 100644
--- a/src/test/java/com/android/tools/r8/shaking/ProguardConfigurationParserTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/ProguardConfigurationParserTest.java
@@ -258,7 +258,7 @@
     assertEquals(1, rules.size());
     assertEquals(ProguardClassType.CLASS, rules.get(0).getClassType());
     assertEquals(1, rules.get(0).getClassNames().size());
-    List<DexType> classTypes = rules.get(0).getClassNames().getSpecificTypes();
+    List<DexType> classTypes = rules.get(0).getClassNames().asSpecificDexTypes();
     assertEquals(1, classTypes.size());
     assertSame(dexItemFactory.createType("L-package-/-ClassNameWithDash-;"), classTypes.get(0));
     ProguardConfigurationRule rule = rules.get(0);
diff --git a/src/test/java/com/android/tools/r8/shaking/ifrule/IfSimilarClassSpecificationBundlingTest.java b/src/test/java/com/android/tools/r8/shaking/ifrule/IfSimilarClassSpecificationBundlingTest.java
index 4b9155a..106fffa 100644
--- a/src/test/java/com/android/tools/r8/shaking/ifrule/IfSimilarClassSpecificationBundlingTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/ifrule/IfSimilarClassSpecificationBundlingTest.java
@@ -92,8 +92,8 @@
   public void testBundlingOfIfRulesWithNonConstantSequent()
       throws IOException, CompilationFailedException, ExecutionException {
     runTest(
-        14,
-        18,
+        22,
+        36,
         "-if class **$R* { int keepA; }",
         "-keep class"
             + " com.android.tools.r8.shaking.ifrule.IfSimilarClassSpecificationBundlingTest$<2> {"