Enqueue callers for reoptimization on-the-fly in final optimization pass

Bug: b/339210038
Change-Id: I4cb8a4ccc81d462bfc8730d0f007f76e6f418adb
diff --git a/src/main/java/com/android/tools/r8/graph/AppView.java b/src/main/java/com/android/tools/r8/graph/AppView.java
index 3ea094d..2196366 100644
--- a/src/main/java/com/android/tools/r8/graph/AppView.java
+++ b/src/main/java/com/android/tools/r8/graph/AppView.java
@@ -136,7 +136,7 @@
   private final LibraryMethodSideEffectModelCollection libraryMethodSideEffectModelCollection;
 
   // Optimizations.
-  private final ArgumentPropagator argumentPropagator;
+  private ArgumentPropagator argumentPropagator;
   private final LibraryMemberOptimizer libraryMemberOptimizer;
   private final ProtoShrinker protoShrinker;
 
@@ -564,6 +564,10 @@
     return appInfo.getSyntheticItems();
   }
 
+  public void unsetArgumentPropagator() {
+    argumentPropagator = null;
+  }
+
   public <E extends Throwable> void withArgumentPropagator(
       ThrowingConsumer<ArgumentPropagator, E> consumer) throws E {
     if (argumentPropagator != null) {
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
index 7eb18f1..83f6884 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
@@ -63,6 +63,7 @@
 import com.android.tools.r8.ir.optimize.enums.EnumUnboxer;
 import com.android.tools.r8.ir.optimize.enums.EnumValueOptimizer;
 import com.android.tools.r8.ir.optimize.info.CallSiteOptimizationInfo;
+import com.android.tools.r8.ir.optimize.info.MethodOptimizationInfo;
 import com.android.tools.r8.ir.optimize.info.MethodOptimizationInfoCollector;
 import com.android.tools.r8.ir.optimize.info.OptimizationFeedback;
 import com.android.tools.r8.ir.optimize.info.OptimizationFeedbackDelayed;
@@ -88,8 +89,8 @@
 import com.android.tools.r8.utils.DescriptorUtils;
 import com.android.tools.r8.utils.ExceptionUtils;
 import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.InternalOptions.InlinerOptions;
 import com.android.tools.r8.utils.InternalOptions.NeverMergeGroup;
-import com.android.tools.r8.utils.LazyBox;
 import com.android.tools.r8.utils.StringDiagnostic;
 import com.android.tools.r8.utils.ThreadUtils;
 import com.android.tools.r8.utils.Timing;
@@ -283,6 +284,10 @@
     return inliner;
   }
 
+  public void unsetEnumUnboxer() {
+    enumUnboxer = EnumUnboxer.empty();
+  }
+
   private boolean needsIRConversion(ProgramMethod method) {
     if (method.getDefinition().getCode().isThrowNullCode()) {
       return false;
@@ -720,21 +725,10 @@
       timing.begin("Inline classes");
       // Class inliner should work before lambda merger, so if it inlines the
       // lambda, it does not get collected by merger.
-      assert options.inlinerOptions().enableInlining && inliner != null;
+      assert options.inlinerOptions().enableInlining;
+      assert inliner != null;
       classInliner.processMethodCode(
-          code.context(),
-          code,
-          feedback,
-          methodProcessor,
-          methodProcessingContext,
-          new LazyBox<>(
-              () ->
-                  inliner.createDefaultOracle(
-                      code.context(),
-                      methodProcessor,
-                      // Inlining instruction allowance is not needed for the class inliner since it
-                      // always uses a force inlining oracle for inlining.
-                      -1)));
+          context, code, feedback, methodProcessor, methodProcessingContext);
       timing.end();
       code.removeRedundantBlocks();
       assert code.isConsistentSSA(appView);
@@ -840,6 +834,7 @@
     timing.begin("Finalize IR");
     finalizeIR(code, feedback, bytecodeMetadataProviderBuilder.build(), timing, previous);
     timing.end();
+    maybeMarkCallersForProcessing(context, methodProcessor);
     return timing;
   }
 
@@ -968,6 +963,54 @@
     printMethod(code.context(), "After finalization");
   }
 
+  private void maybeMarkCallersForProcessing(
+      ProgramMethod method, MethodProcessor methodProcessor) {
+    if (methodProcessor.isPostMethodProcessor()) {
+      PostMethodProcessor postMethodProcessor = methodProcessor.asPostMethodProcessor();
+      if (shouldMarkCallersForProcessing(method)) {
+        postMethodProcessor.markCallersForProcessing(method);
+      }
+    }
+  }
+
+  private boolean shouldMarkCallersForProcessing(ProgramMethod method) {
+    Code bytecode = method.getDefinition().getCode();
+    InlinerOptions inlinerOptions = appView.options().inlinerOptions();
+    int instructionLimit = inlinerOptions.getSimpleInliningInstructionLimit();
+    int estimatedIncrement = getEstimatedInliningInstructionLimitIncrementForReturn();
+    if (bytecode.estimatedSizeForInliningAtMost(instructionLimit + estimatedIncrement)) {
+      return true;
+    }
+    if (delayedOptimizationFeedback.hasPendingOptimizationInfo(method)) {
+      MethodOptimizationInfo oldOptimizationInfo = method.getOptimizationInfo();
+      MethodOptimizationInfo newOptimizationInfo =
+          delayedOptimizationFeedback.getMethodOptimizationInfoForUpdating(method);
+      if (!oldOptimizationInfo
+          .getAbstractReturnValue()
+          .equals(newOptimizationInfo.getAbstractReturnValue())) {
+        return true;
+      }
+      if (!oldOptimizationInfo.getDynamicType().equals(newOptimizationInfo.getDynamicType())) {
+        return true;
+      }
+      if (oldOptimizationInfo.mayHaveSideEffects() && !newOptimizationInfo.mayHaveSideEffects()) {
+        return true;
+      }
+      if (!oldOptimizationInfo.neverReturnsNormally()
+          && newOptimizationInfo.neverReturnsNormally()) {
+        return true;
+      }
+      if (!oldOptimizationInfo.returnsArgument() && newOptimizationInfo.returnsArgument()) {
+        return true;
+      }
+    }
+    return false;
+  }
+
+  private int getEstimatedInliningInstructionLimitIncrementForReturn() {
+    return 1;
+  }
+
   private IRCode roundtripThroughLir(
       IRCode code,
       BytecodeMetadataProvider bytecodeMetadataProvider,
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/MethodProcessor.java b/src/main/java/com/android/tools/r8/ir/conversion/MethodProcessor.java
index 829c985..9eb24e1 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/MethodProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/MethodProcessor.java
@@ -33,6 +33,10 @@
     return false;
   }
 
+  public PostMethodProcessor asPostMethodProcessor() {
+    return null;
+  }
+
   public abstract MethodProcessorEventConsumer getEventConsumer();
 
   public abstract boolean isProcessedConcurrently(ProgramMethod method);
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/PostMethodProcessor.java b/src/main/java/com/android/tools/r8/ir/conversion/PostMethodProcessor.java
index 9175986..a8fe188 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/PostMethodProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/PostMethodProcessor.java
@@ -15,7 +15,7 @@
 import com.android.tools.r8.graph.lens.GraphLens;
 import com.android.tools.r8.ir.conversion.PrimaryMethodProcessor.MethodAction;
 import com.android.tools.r8.ir.conversion.callgraph.CallGraph;
-import com.android.tools.r8.ir.conversion.callgraph.PartialCallGraphBuilder;
+import com.android.tools.r8.ir.conversion.callgraph.CallSiteInformation;
 import com.android.tools.r8.ir.optimize.info.OptimizationFeedbackDelayed;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.threading.ThreadingModule;
@@ -24,6 +24,7 @@
 import com.android.tools.r8.utils.Timing;
 import com.android.tools.r8.utils.Timing.TimingMerger;
 import com.android.tools.r8.utils.collections.LongLivedProgramMethodSetBuilder;
+import com.android.tools.r8.utils.collections.ProgramMethodMap;
 import com.android.tools.r8.utils.collections.ProgramMethodSet;
 import java.io.IOException;
 import java.util.ArrayDeque;
@@ -35,20 +36,45 @@
 
 public class PostMethodProcessor extends MethodProcessorWithWave {
 
+  private CallSiteInformation callSiteInformation;
   private final MethodProcessorEventConsumer eventConsumer;
+  private final ProgramMethodSet methodsToProcess;
+  private final ProgramMethodSet processed = ProgramMethodSet.create();
   private final ProcessorContext processorContext;
   private final Deque<ProgramMethodSet> waves;
-  private final ProgramMethodSet processed = ProgramMethodSet.create();
+
+  // The set of callers for a given method according to the call graph. This might not be the
+  // complete set of callers.
+  private final ProgramMethodMap<ProgramMethodSet> callers = ProgramMethodMap.createConcurrent();
 
   private PostMethodProcessor(
       AppView<AppInfoWithLiveness> appView,
       CallGraph callGraph,
-      MethodProcessorEventConsumer eventConsumer) {
+      MethodProcessorEventConsumer eventConsumer,
+      ProgramMethodSet methodsToProcess) {
+    this.callSiteInformation = callGraph.createCallSiteInformation(appView, this);
     this.eventConsumer = eventConsumer;
+    this.methodsToProcess = methodsToProcess;
     this.processorContext = appView.createProcessorContext();
     this.waves = createWaves(callGraph);
   }
 
+  public void markCallersForProcessing(ProgramMethod method) {
+    assert wave.contains(method);
+    synchronized (methodsToProcess) {
+      for (ProgramMethod caller : callers.removeOrDefault(method, ProgramMethodSet.empty())) {
+        if (!wave.contains(caller)) {
+          methodsToProcess.add(caller);
+        }
+      }
+    }
+  }
+
+  @Override
+  public CallSiteInformation getCallSiteInformation() {
+    return callSiteInformation;
+  }
+
   @Override
   public MethodProcessorEventConsumer getEventConsumer() {
     return eventConsumer;
@@ -60,6 +86,11 @@
   }
 
   @Override
+  public PostMethodProcessor asPostMethodProcessor() {
+    return this;
+  }
+
+  @Override
   public boolean shouldApplyCodeRewritings(ProgramMethod method) {
     assert !wave.contains(method);
     return !processed.contains(method);
@@ -152,9 +183,8 @@
       assert !appView.options().debug
           || methodsToReprocess.stream()
               .allMatch(methodToReprocess -> methodToReprocess.getDefinition().isD8R8Synthesized());
-      CallGraph callGraph =
-          new PartialCallGraphBuilder(appView, methodsToReprocess).build(executorService, timing);
-      return new PostMethodProcessor(appView, callGraph, eventConsumer);
+      CallGraph callGraph = CallGraph.builder(appView).build(executorService, timing);
+      return new PostMethodProcessor(appView, callGraph, eventConsumer, methodsToReprocess);
     }
 
     public void dump(DeterminismChecker determinismChecker) throws IOException {
@@ -162,19 +192,28 @@
     }
   }
 
-  @SuppressWarnings("UnusedVariable")
   private Deque<ProgramMethodSet> createWaves(CallGraph callGraph) {
     Deque<ProgramMethodSet> waves = new ArrayDeque<>();
-    int waveCount = 1;
     while (!callGraph.isEmpty()) {
-      ProgramMethodSet wave = callGraph.extractLeaves();
-      waves.addLast(wave);
+      waves.addLast(
+          callGraph.extractLeaves(
+              leaf -> {
+                if (!leaf.getCallers().isEmpty()) {
+                  callers.put(
+                      leaf.getProgramMethod(),
+                      ProgramMethodSet.create(
+                          builder ->
+                              leaf.getCallers()
+                                  .forEach(caller -> builder.accept(caller.getProgramMethod()))));
+                }
+              }));
     }
     return waves;
   }
 
   <E extends Exception> void forEachMethod(
       MethodAction<E> consumer,
+      PrimaryR8IRConverter converter,
       OptimizationFeedbackDelayed feedback,
       ThreadingModule threadingModule,
       ExecutorService executorService,
@@ -184,9 +223,14 @@
     while (!waves.isEmpty()) {
       wave = waves.removeFirst();
       assert !wave.isEmpty();
+      wave.removeIf(method -> !methodsToProcess.contains(method));
+      if (wave.isEmpty()) {
+        continue;
+      }
       assert waveExtension.isEmpty();
       do {
         assert feedback.noUpdatesLeft();
+        converter.waveStart(wave);
         Collection<Timing> timings =
             ThreadUtils.processItemsWithResults(
                 wave,
@@ -200,11 +244,21 @@
                 threadingModule,
                 executorService);
         merger.add(timings);
+        converter.waveDone(wave, executorService);
         feedback.updateVisibleOptimizationInfo();
         processed.addAll(wave);
         prepareForWaveExtensionProcessing();
       } while (!wave.isEmpty());
     }
+    clear();
     merger.end();
   }
+
+  private void clear() {
+    assert waves.isEmpty();
+    callers.clear();
+    methodsToProcess.clear();
+    processed.clear();
+    callSiteInformation = CallSiteInformation.empty();
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/PrimaryMethodProcessor.java b/src/main/java/com/android/tools/r8/ir/conversion/PrimaryMethodProcessor.java
index d56bdfb..4e3f618 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/PrimaryMethodProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/PrimaryMethodProcessor.java
@@ -10,9 +10,7 @@
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.conversion.callgraph.CallGraph;
 import com.android.tools.r8.ir.conversion.callgraph.CallSiteInformation;
-import com.android.tools.r8.ir.conversion.callgraph.Node;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
-import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.ThreadUtils;
 import com.android.tools.r8.utils.Timing;
 import com.android.tools.r8.utils.Timing.TimingMerger;
@@ -52,7 +50,7 @@
       CallGraph callGraph,
       MethodProcessorEventConsumer eventConsumer) {
     this.appView = appView;
-    this.callSiteInformation = callGraph.createCallSiteInformation(appView);
+    this.callSiteInformation = callGraph.createCallSiteInformation(appView, this);
     this.eventConsumer = eventConsumer;
     this.waves = createWaves(appView, callGraph);
   }
@@ -93,16 +91,12 @@
     return callSiteInformation;
   }
 
-  @SuppressWarnings("UnusedVariable")
   private Deque<ProgramMethodSet> createWaves(AppView<?> appView, CallGraph callGraph) {
-    InternalOptions options = appView.options();
     Deque<ProgramMethodSet> waves = new ArrayDeque<>();
-    Collection<Node> nodes = callGraph.getNodes();
-    while (!nodes.isEmpty()) {
-      ProgramMethodSet wave = callGraph.extractLeaves();
-      waves.addLast(wave);
+    while (!callGraph.isEmpty()) {
+      waves.addLast(callGraph.extractLeaves());
     }
-    options.testing.waveModifier.accept(waves);
+    appView.testing().waveModifier.accept(waves);
     return waves;
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/PrimaryR8IRConverter.java b/src/main/java/com/android/tools/r8/ir/conversion/PrimaryR8IRConverter.java
index 035690f..1277429 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/PrimaryR8IRConverter.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/PrimaryR8IRConverter.java
@@ -190,6 +190,7 @@
                     postMethodProcessor,
                     methodProcessingContext,
                     MethodConversionOptions.forLirPhase(appView)),
+            this,
             feedback,
             appView.options().getThreadingModule(),
             executorService,
@@ -248,7 +249,7 @@
     }
   }
 
-  private void waveStart(ProgramMethodSet wave) {
+  public void waveStart(ProgramMethodSet wave) {
     onWaveDoneActions = Collections.synchronizedList(new ArrayList<>());
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/callgraph/CallGraph.java b/src/main/java/com/android/tools/r8/ir/conversion/callgraph/CallGraph.java
index a5a151b..1e890ec 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/callgraph/CallGraph.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/callgraph/CallGraph.java
@@ -6,6 +6,7 @@
 
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.ir.conversion.MethodProcessorWithWave;
 import com.android.tools.r8.ir.conversion.callgraph.CallSiteInformation.CallGraphBasedCallSiteInformation;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.utils.InternalOptions;
@@ -56,10 +57,11 @@
                     node -> node.getProgramMethod().getReference(), Function.identity())));
   }
 
-  public CallSiteInformation createCallSiteInformation(AppView<AppInfoWithLiveness> appView) {
+  public CallSiteInformation createCallSiteInformation(
+      AppView<AppInfoWithLiveness> appView, MethodProcessorWithWave methodProcessor) {
     // Don't leverage single/dual call site information when we are not tree shaking.
     return appView.options().isShrinking()
-        ? new CallGraphBasedCallSiteInformation(appView, this)
+        ? new CallGraphBasedCallSiteInformation(appView, this, methodProcessor)
         : CallSiteInformation.empty();
   }
 
@@ -67,6 +69,15 @@
     return extractNodes(Node::isLeaf, Node::cleanCallersAndReadersForRemoval);
   }
 
+  public ProgramMethodSet extractLeaves(Consumer<Node> nodeRemovalConsumer) {
+    return extractNodes(
+        Node::isLeaf,
+        node -> {
+          nodeRemovalConsumer.accept(node);
+          node.cleanCallersAndReadersForRemoval();
+        });
+  }
+
   public ProgramMethodSet extractRoots() {
     return extractNodes(Node::isRoot, Node::cleanCalleesAndWritersForRemoval);
   }
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/callgraph/CallSiteInformation.java b/src/main/java/com/android/tools/r8/ir/conversion/callgraph/CallSiteInformation.java
index 300487b..25c381e 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/callgraph/CallSiteInformation.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/callgraph/CallSiteInformation.java
@@ -7,8 +7,10 @@
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.ImmediateProgramSubtypingInfo;
 import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.ir.conversion.MethodProcessorWithWave;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.shaking.KeepMethodInfo;
+import com.android.tools.r8.synthesis.SyntheticItems;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.classhierarchy.MethodOverridesCollector;
 import com.android.tools.r8.utils.collections.ProgramMethodSet;
@@ -75,7 +77,10 @@
     private final Map<DexMethod, DexMethod> singleCallerMethods = new IdentityHashMap<>();
     private final Set<DexMethod> multiCallerInlineCandidates = Sets.newIdentityHashSet();
 
-    CallGraphBasedCallSiteInformation(AppView<AppInfoWithLiveness> appView, CallGraph graph) {
+    CallGraphBasedCallSiteInformation(
+        AppView<AppInfoWithLiveness> appView,
+        CallGraph graph,
+        MethodProcessorWithWave methodProcessor) {
       InternalOptions options = appView.options();
       ProgramMethodSet pinned =
           MethodOverridesCollector.findAllMethodsAndOverridesThatMatches(
@@ -114,6 +119,14 @@
           if (!appView.getKeepInfo(method).isSingleCallerInliningAllowed(options)) {
             continue;
           }
+          if (methodProcessor.isPostMethodProcessor()) {
+            SyntheticItems syntheticItems = appView.getSyntheticItems();
+            if (syntheticItems.hasKindThatMatches(
+                method.getHolderType(),
+                (kind, naming) -> !kind.isSingleCallerInlineableInPostMethodProcessor(naming))) {
+              continue;
+            }
+          }
           Set<Node> callersWithDeterministicOrder = node.getCallersWithDeterministicOrder();
           DexMethod caller = reference;
           // We can have recursive methods where the recursive call is the only call site. We do
@@ -124,7 +137,7 @@
           }
           DexMethod existing = singleCallerMethods.put(reference, caller);
           assert existing == null;
-        } else if (numberOfCallSites > 1) {
+        } else if (numberOfCallSites > 1 && methodProcessor.isPrimaryMethodProcessor()) {
           multiCallerInlineCandidates.add(reference);
         }
       }
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/callgraph/Node.java b/src/main/java/com/android/tools/r8/ir/conversion/callgraph/Node.java
index e515764..bd2c77c 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/callgraph/Node.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/callgraph/Node.java
@@ -132,6 +132,10 @@
     }
   }
 
+  public Set<Node> getCallers() {
+    return callers;
+  }
+
   public Set<Node> getCallersWithDeterministicOrder() {
     return callers;
   }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/DefaultInliningOracle.java b/src/main/java/com/android/tools/r8/ir/optimize/DefaultInliningOracle.java
index a166867..337d90d 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/DefaultInliningOracle.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/DefaultInliningOracle.java
@@ -3,6 +3,7 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.ir.optimize;
 
+import static com.android.tools.r8.ir.optimize.Inliner.numberOfInstructions;
 import static com.android.tools.r8.ir.optimize.inliner.InlinerUtils.addMonitorEnterValue;
 import static com.android.tools.r8.ir.optimize.inliner.InlinerUtils.collectAllMonitorEnterValues;
 import static com.android.tools.r8.utils.AndroidApiLevelUtils.isApiSafeForInlining;
@@ -75,9 +76,24 @@
 
   public DefaultInliningOracle(
       AppView<AppInfoWithLiveness> appView,
-      InliningReasonStrategy inliningReasonStrategy,
       ProgramMethod method,
       MethodProcessor methodProcessor,
+      InliningReasonStrategy inliningReasonStrategy,
+      IRCode code) {
+    this(
+        appView,
+        method,
+        methodProcessor,
+        inliningReasonStrategy,
+        appView.options().inlinerOptions().inliningInstructionAllowance
+            - numberOfInstructions(code));
+  }
+
+  public DefaultInliningOracle(
+      AppView<AppInfoWithLiveness> appView,
+      ProgramMethod method,
+      MethodProcessor methodProcessor,
+      InliningReasonStrategy inliningReasonStrategy,
       int inliningInstructionAllowance) {
     this.appView = appView;
     this.options = appView.options();
@@ -776,8 +792,8 @@
 
   private boolean willExceedInstructionBudget(
       IRCode inlinee, WhyAreYouNotInliningReporter whyAreYouNotInliningReporter) {
-    int numberOfInstructions = Inliner.numberOfInstructions(inlinee);
-    if (instructionAllowance < Inliner.numberOfInstructions(inlinee)) {
+    int numberOfInstructions = numberOfInstructions(inlinee);
+    if (instructionAllowance < numberOfInstructions(inlinee)) {
       whyAreYouNotInliningReporter.reportWillExceedInstructionBudget(
           numberOfInstructions, instructionAllowance);
       return true;
@@ -887,7 +903,7 @@
   @Override
   public void markInlined(IRCode inlinee) {
     // TODO(118734615): All inlining use from the budget - should that only be SIMPLE?
-    instructionAllowance -= Inliner.numberOfInstructions(inlinee);
+    instructionAllowance -= numberOfInstructions(inlinee);
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java b/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java
index 8068a8a..700ead2 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java
@@ -879,7 +879,7 @@
     }
   }
 
-  static int numberOfInstructions(IRCode code) {
+  public static int numberOfInstructions(IRCode code) {
     int numberOfInstructions = 0;
     for (BasicBlock block : code.blocks) {
       for (Instruction instruction : block.getInstructions()) {
@@ -964,13 +964,8 @@
       MethodProcessor methodProcessor,
       Timing timing,
       InliningReasonStrategy inliningReasonStrategy) {
-    InlinerOptions options = appView.options().inlinerOptions();
     DefaultInliningOracle oracle =
-        createDefaultOracle(
-            method,
-            methodProcessor,
-            options.inliningInstructionAllowance - numberOfInstructions(code),
-            inliningReasonStrategy);
+        createDefaultOracle(code, method, methodProcessor, inliningReasonStrategy);
     InliningIRProvider inliningIRProvider =
         new InliningIRProvider(appView, method, code, lensCodeRewriter, methodProcessor);
     assert inliningIRProvider.verifyIRCacheIsEmpty();
@@ -988,27 +983,12 @@
   }
 
   public DefaultInliningOracle createDefaultOracle(
+      IRCode code,
       ProgramMethod method,
       MethodProcessor methodProcessor,
-      int inliningInstructionAllowance) {
-    return createDefaultOracle(
-        method,
-        methodProcessor,
-        inliningInstructionAllowance,
-        createDefaultInliningReasonStrategy(methodProcessor));
-  }
-
-  public DefaultInliningOracle createDefaultOracle(
-      ProgramMethod method,
-      MethodProcessor methodProcessor,
-      int inliningInstructionAllowance,
       InliningReasonStrategy inliningReasonStrategy) {
     return new DefaultInliningOracle(
-        appView,
-        inliningReasonStrategy,
-        method,
-        methodProcessor,
-        inliningInstructionAllowance);
+        appView, method, methodProcessor, inliningReasonStrategy, code);
   }
 
   private void performInliningImpl(
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/MultiCallerInliner.java b/src/main/java/com/android/tools/r8/ir/optimize/MultiCallerInliner.java
index baa1fc5..36e3717 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/MultiCallerInliner.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/MultiCallerInliner.java
@@ -74,9 +74,9 @@
               int inliningInstructionAllowance = Integer.MAX_VALUE;
               return new DefaultInliningOracle(
                   appView,
-                  new FixedInliningReasonStrategy(Reason.ALWAYS),
                   method,
                   methodProcessor,
+                  new FixedInliningReasonStrategy(Reason.ALWAYS),
                   inliningInstructionAllowance);
             });
     for (InvokeMethod invoke : code.<InvokeMethod>instructions(Instruction::isInvokeMethod)) {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInliner.java b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInliner.java
index 4918f7c..d934757 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInliner.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInliner.java
@@ -20,6 +20,7 @@
 import com.android.tools.r8.ir.conversion.passes.BranchSimplifier;
 import com.android.tools.r8.ir.conversion.passes.TrivialCheckCastAndInstanceOfRemover;
 import com.android.tools.r8.ir.optimize.AffectedValues;
+import com.android.tools.r8.ir.optimize.DefaultInliningOracle;
 import com.android.tools.r8.ir.optimize.Inliner;
 import com.android.tools.r8.ir.optimize.InliningOracle;
 import com.android.tools.r8.ir.optimize.classinliner.InlineCandidateProcessor.IllegalClassInlinerStateException;
@@ -133,8 +134,17 @@
       IRCode code,
       OptimizationFeedback feedback,
       MethodProcessor methodProcessor,
-      MethodProcessingContext methodProcessingContext,
-      LazyBox<InliningOracle> defaultOracle) {
+      MethodProcessingContext methodProcessingContext) {
+    LazyBox<InliningOracle> defaultOracle =
+        new LazyBox<>(
+            () ->
+                new DefaultInliningOracle(
+                    appView,
+                    method,
+                    methodProcessor,
+                    inliner.createDefaultInliningReasonStrategy(methodProcessor),
+                    code));
+
     // Collect all the new-instance and static-get instructions in the code before inlining.
     List<Instruction> roots =
         Lists.newArrayList(code.instructions(insn -> insn.isNewInstance() || insn.isStaticGet()));
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxerImpl.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxerImpl.java
index a7cbb72..b26ed45 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxerImpl.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxerImpl.java
@@ -712,6 +712,7 @@
 
     if (enumUnboxingCandidatesInfo.isEmpty()) {
       assert enumDataMap.isEmpty();
+      converter.unsetEnumUnboxer();
       timing.end();
       return;
     }
@@ -781,6 +782,7 @@
     appView.testing().checkDeterminism(postMethodProcessorBuilder::dump);
 
     appView.notifyOptimizationFinishedForTesting();
+    converter.unsetEnumUnboxer();
     timing.end();
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackDelayed.java b/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackDelayed.java
index 6b0b6eb..0ab35bf 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackDelayed.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackDelayed.java
@@ -64,10 +64,14 @@
     return info;
   }
 
-  private MutableMethodOptimizationInfo getMethodOptimizationInfoForUpdating(ProgramMethod method) {
+  public MutableMethodOptimizationInfo getMethodOptimizationInfoForUpdating(ProgramMethod method) {
     return getMethodOptimizationInfoForUpdating(method.getDefinition());
   }
 
+  public synchronized boolean hasPendingOptimizationInfo(ProgramMethod method) {
+    return methodOptimizationInfos.containsKey(method.getDefinition());
+  }
+
   @Override
   public void fixupOptimizationInfos(
       AppView<?> appView,
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/typechecks/CheckCastAndInstanceOfMethodSpecialization.java b/src/main/java/com/android/tools/r8/ir/optimize/typechecks/CheckCastAndInstanceOfMethodSpecialization.java
index 7c5240b..2e2c0a1 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/typechecks/CheckCastAndInstanceOfMethodSpecialization.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/typechecks/CheckCastAndInstanceOfMethodSpecialization.java
@@ -58,7 +58,7 @@
     if (!converter.isInWave()) {
       return;
     }
-    assert methodProcessor.isPrimaryMethodProcessor();
+    assert methodProcessor.isPrimaryMethodProcessor() || methodProcessor.isPostMethodProcessor();
     if (isCandidateForInstanceOfOptimization(method, abstractReturnValue)) {
       synchronized (this) {
         if (candidatesForInstanceOfOptimization.isEmpty()) {
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagator.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagator.java
index b443888..2fd6eb9 100644
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagator.java
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagator.java
@@ -211,8 +211,8 @@
 
     // Ensure determinism of method-to-reprocess set.
     appView.testing().checkDeterminism(postMethodProcessorBuilder::dump);
-
     appView.notifyOptimizationFinishedForTesting();
+    appView.unsetArgumentPropagator();
   }
 
   /**
diff --git a/src/main/java/com/android/tools/r8/optimize/singlecaller/SingleCallerInliner.java b/src/main/java/com/android/tools/r8/optimize/singlecaller/SingleCallerInliner.java
index 70b50c9..614cf22 100644
--- a/src/main/java/com/android/tools/r8/optimize/singlecaller/SingleCallerInliner.java
+++ b/src/main/java/com/android/tools/r8/optimize/singlecaller/SingleCallerInliner.java
@@ -250,12 +250,12 @@
 
     @Override
     public DefaultInliningOracle createDefaultOracle(
+        IRCode code,
         ProgramMethod method,
         MethodProcessor methodProcessor,
-        int inliningInstructionAllowance,
         InliningReasonStrategy inliningReasonStrategy) {
       return new DefaultInliningOracle(
-          appView, inliningReasonStrategy, method, methodProcessor, inliningInstructionAllowance) {
+          appView, method, methodProcessor, inliningReasonStrategy, code) {
 
         @Override
         public InlineResult computeInlining(
diff --git a/src/main/java/com/android/tools/r8/synthesis/SyntheticItems.java b/src/main/java/com/android/tools/r8/synthesis/SyntheticItems.java
index 50c9f23..b940b87 100644
--- a/src/main/java/com/android/tools/r8/synthesis/SyntheticItems.java
+++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticItems.java
@@ -50,6 +50,7 @@
 import com.android.tools.r8.utils.SetUtils;
 import com.android.tools.r8.utils.StringDiagnostic;
 import com.android.tools.r8.utils.Timing;
+import com.google.common.base.Predicate;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.Iterables;
@@ -64,9 +65,9 @@
 import java.util.Set;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.function.BiConsumer;
+import java.util.function.BiPredicate;
 import java.util.function.Consumer;
 import java.util.function.Function;
-import java.util.function.Predicate;
 import java.util.function.Supplier;
 import org.objectweb.asm.ClassWriter;
 
@@ -522,6 +523,11 @@
     return true;
   }
 
+  public boolean hasKindThatMatches(
+      DexType type, BiPredicate<? super SyntheticKind, ? super SyntheticNaming> predicate) {
+    return Iterables.any(getSyntheticKinds(type), kind -> predicate.test(kind, naming));
+  }
+
   public boolean isSyntheticOfKind(DexType type, SyntheticKindSelector kindSelector) {
     SyntheticKind kind = kindSelector.select(naming);
     return pending.containsTypeOfKind(type, kind) || committed.containsTypeOfKind(type, kind);
diff --git a/src/main/java/com/android/tools/r8/synthesis/SyntheticNaming.java b/src/main/java/com/android/tools/r8/synthesis/SyntheticNaming.java
index 177bf18..861e8e9 100644
--- a/src/main/java/com/android/tools/r8/synthesis/SyntheticNaming.java
+++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticNaming.java
@@ -232,6 +232,14 @@
       return descriptor;
     }
 
+    public boolean isSingleCallerInlineableInPostMethodProcessor(SyntheticNaming naming) {
+      // Do not allow single caller inlining the enum utility classes in the second optimization
+      // pass. We rewrite code on-the-fly to call these method, so removing them as a result of
+      // single caller inlining would lead to compilation errors.
+      return !equals(naming.ENUM_UNBOXING_LOCAL_UTILITY_CLASS)
+          && !equals(naming.ENUM_UNBOXING_SHARED_UTILITY_CLASS);
+    }
+
     public boolean isSyntheticMethodKind() {
       return false;
     }
diff --git a/src/main/java/com/android/tools/r8/utils/collections/DexClassAndMemberMap.java b/src/main/java/com/android/tools/r8/utils/collections/DexClassAndMemberMap.java
index 35023fc..1d47ac2 100644
--- a/src/main/java/com/android/tools/r8/utils/collections/DexClassAndMemberMap.java
+++ b/src/main/java/com/android/tools/r8/utils/collections/DexClassAndMemberMap.java
@@ -169,6 +169,11 @@
         .removeIf(entry -> predicate.test(entry.getKey().get(), entry.getValue(), entry));
   }
 
+  public V removeOrDefault(K member, V defaultValue) {
+    V value = remove(member);
+    return value != null ? value : defaultValue;
+  }
+
   @Override
   public int size() {
     return backing.size();
diff --git a/src/test/java/com/android/tools/r8/R8RunExamplesAndroidOTest.java b/src/test/java/com/android/tools/r8/R8RunExamplesAndroidOTest.java
index f4c915f..ee2f958 100644
--- a/src/test/java/com/android/tools/r8/R8RunExamplesAndroidOTest.java
+++ b/src/test/java/com/android/tools/r8/R8RunExamplesAndroidOTest.java
@@ -205,7 +205,7 @@
         .withDexCheck(
             inspector ->
                 checkLambdaCount(
-                    inspector, enableProguardCompatibilityMode ? 3 : 4, "lambdadesugaringnplus"))
+                    inspector, enableProguardCompatibilityMode ? 1 : 2, "lambdadesugaringnplus"))
         .run();
 
     test("lambdadesugaringnplus", "lambdadesugaringnplus", "LambdasWithStaticAndDefaultMethods")
@@ -247,7 +247,7 @@
             b ->
                 b.addProguardConfiguration(
                     getProguardOptionsNPlus(enableProguardCompatibilityMode), Origin.unknown()))
-        .withDexCheck(inspector -> checkLambdaCount(inspector, 4, "lambdadesugaringnplus"))
+        .withDexCheck(inspector -> checkLambdaCount(inspector, 2, "lambdadesugaringnplus"))
         .run();
 
     test("lambdadesugaringnplus", "lambdadesugaringnplus", "LambdasWithStaticAndDefaultMethods")
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/callsites/dynamicupperboundtype/InvokeDirectPositiveTest.java b/src/test/java/com/android/tools/r8/ir/optimize/callsites/dynamicupperboundtype/InvokeDirectPositiveTest.java
index b27142b..fba129a 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/callsites/dynamicupperboundtype/InvokeDirectPositiveTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/callsites/dynamicupperboundtype/InvokeDirectPositiveTest.java
@@ -100,7 +100,9 @@
   @NoVerticalClassMerging
   static class Base {}
 
+  @NeverClassInline
   static class Sub1 extends Base {}
+
   static class Sub2 extends Base {}
 
   @NeverClassInline
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/callsites/dynamicupperboundtype/InvokeStaticPositiveTest.java b/src/test/java/com/android/tools/r8/ir/optimize/callsites/dynamicupperboundtype/InvokeStaticPositiveTest.java
index 5228de1..81425e0 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/callsites/dynamicupperboundtype/InvokeStaticPositiveTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/callsites/dynamicupperboundtype/InvokeStaticPositiveTest.java
@@ -7,6 +7,7 @@
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.junit.Assert.assertTrue;
 
+import com.android.tools.r8.NeverClassInline;
 import com.android.tools.r8.NeverInline;
 import com.android.tools.r8.NoParameterTypeStrengthening;
 import com.android.tools.r8.NoVerticalClassMerging;
@@ -43,6 +44,7 @@
     testForR8(parameters.getBackend())
         .addInnerClasses(InvokeStaticPositiveTest.class)
         .addKeepMainRule(MAIN)
+        .enableNeverClassInliningAnnotations()
         .enableNoParameterTypeStrengtheningAnnotations()
         .enableNoVerticalClassMergingAnnotations()
         .enableInliningAnnotations()
@@ -89,7 +91,9 @@
   @NoVerticalClassMerging
   static class Base {}
 
+  @NeverClassInline
   static class Sub1 extends Base {}
+
   static class Sub2 extends Base {}
 
   static class Main {
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/string/StringIsEmptyTest.java b/src/test/java/com/android/tools/r8/ir/optimize/string/StringIsEmptyTest.java
index cebee69..a1de3e7 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/string/StringIsEmptyTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/string/StringIsEmptyTest.java
@@ -6,7 +6,6 @@
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.junit.Assert.assertEquals;
-import static org.junit.Assume.assumeTrue;
 
 import com.android.tools.r8.D8TestRunResult;
 import com.android.tools.r8.NeverInline;
@@ -15,7 +14,6 @@
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
-import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.StringUtils;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
@@ -23,6 +21,8 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
 
 @RunWith(Parameterized.class)
 public class StringIsEmptyTest extends TestBase {
@@ -34,16 +34,13 @@
   );
   private static final Class<?> MAIN = TestClass.class;
 
-  @Parameterized.Parameters(name = "{0}")
+  @Parameters(name = "{0}")
   public static TestParametersCollection data() {
     return getTestParameters().withAllRuntimesAndApiLevels().build();
   }
 
-  private final TestParameters parameters;
-
-  public StringIsEmptyTest(TestParameters parameters) {
-    this.parameters = parameters;
-  }
+  @Parameter(0)
+  public TestParameters parameters;
 
   @Test
   public void testJVMOutput() throws Exception {
@@ -54,13 +51,8 @@
         .assertSuccessWithOutput(JAVA_OUTPUT);
   }
 
-  private void configure(InternalOptions options) {
-    // This test wants to check if compile-time computation is not applied to non-null,
-    // non-constant value. In a simple test setting, call-site optimization knows the argument is
-    // always a non-null, specific constant, but that is beyond the scope of this test.
-  }
-
-  private void test(SingleTestRunResult result, int expectedStringIsEmptyCount) throws Exception {
+  private void test(SingleTestRunResult<?> result, int expectedStringIsEmptyCount)
+      throws Exception {
     CodeInspector codeInspector = result.inspector();
     ClassSubject mainClass = codeInspector.clazz(MAIN);
     MethodSubject mainMethod = mainClass.mainMethod();
@@ -75,14 +67,13 @@
 
   @Test
   public void testD8() throws Exception {
-    assumeTrue("Only run D8 for Dex backend", parameters.isDexRuntime());
+    parameters.assumeDexRuntime();
 
     D8TestRunResult result =
         testForD8()
             .debug()
             .addProgramClasses(MAIN)
             .setMinApi(parameters)
-            .addOptionsModification(this::configure)
             .run(parameters.getRuntime(), MAIN)
             .assertSuccessWithOutput(JAVA_OUTPUT);
     test(result, 3);
@@ -92,7 +83,6 @@
             .release()
             .addProgramClasses(MAIN)
             .setMinApi(parameters)
-            .addOptionsModification(this::configure)
             .run(parameters.getRuntime(), MAIN)
             .assertSuccessWithOutput(JAVA_OUTPUT);
     test(result, 1);
@@ -103,14 +93,12 @@
     R8TestRunResult result =
         testForR8(parameters.getBackend())
             .addProgramClasses(MAIN)
-            .enableProguardTestOptions()
-            .enableInliningAnnotations()
             .addKeepMainRule(MAIN)
+            .enableInliningAnnotations()
             .setMinApi(parameters)
-            .addOptionsModification(this::configure)
             .run(parameters.getRuntime(), MAIN)
             .assertSuccessWithOutput(JAVA_OUTPUT);
-    test(result, 1);
+    test(result, 0);
   }
 
   static class TestClass {
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/uninstantiatedtypes/NestedInterfaceMethodTest.java b/src/test/java/com/android/tools/r8/ir/optimize/uninstantiatedtypes/NestedInterfaceMethodTest.java
index b0b7907..e90e867 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/uninstantiatedtypes/NestedInterfaceMethodTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/uninstantiatedtypes/NestedInterfaceMethodTest.java
@@ -22,34 +22,34 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
 import org.junit.runners.Parameterized.Parameters;
 
 /** Reproduction for b/128917897. */
 @RunWith(Parameterized.class)
 public class NestedInterfaceMethodTest extends TestBase {
 
-  private final TestParameters parameters;
+  private static final String expectedOutput = StringUtils.lines("In A.m()", "In C.m()");
+
+  @Parameter(0)
+  public TestParameters parameters;
 
   @Parameters(name = "{0}")
   public static TestParametersCollection params() {
     return getTestParameters().withAllRuntimes().build();
   }
 
-  public NestedInterfaceMethodTest(TestParameters parameters) {
-    this.parameters = parameters;
+  @Test
+  public void testJvm() throws Exception {
+    parameters.assumeCfRuntime();
+    testForJvm(parameters)
+        .addTestClasspath()
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutput(expectedOutput);
   }
 
   @Test
-  public void test() throws Exception {
-    String expectedOutput = StringUtils.lines("In A.m()", "In A.m()");
-
-    if (parameters.isCfRuntime()) {
-      testForJvm(parameters)
-          .addTestClasspath()
-          .run(parameters.getRuntime(), TestClass.class)
-          .assertSuccessWithOutput(expectedOutput);
-    }
-
+  public void testR8() throws Exception {
     CodeInspector inspector =
         testForR8(parameters.getBackend())
             .addInnerClasses(NestedInterfaceMethodTest.class)
@@ -127,7 +127,14 @@
   // TestClass.test() gets devirtualized to an invoke-virtual instruction. Otherwise the method
   // I.m() would not be present in the output.
   @NeverClassInline
-  static class C extends A {}
+  static class C implements I {
+
+    @Override
+    public Uninstantiated m() {
+      System.out.println("In C.m()");
+      return null;
+    }
+  }
 
   @NoHorizontalClassMerging
   static class Uninstantiated {}
diff --git a/src/test/java/com/android/tools/r8/kotlin/R8KotlinDataClassTest.java b/src/test/java/com/android/tools/r8/kotlin/R8KotlinDataClassTest.java
index 5b99580..374f653 100644
--- a/src/test/java/com/android/tools/r8/kotlin/R8KotlinDataClassTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/R8KotlinDataClassTest.java
@@ -4,7 +4,6 @@
 
 package com.android.tools.r8.kotlin;
 
-import com.android.tools.r8.KotlinCompilerTool.KotlinCompilerVersion;
 import com.android.tools.r8.KotlinTestParameters;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.kotlin.TestKotlinClass.Visibility;
@@ -130,16 +129,6 @@
                 testBuilder
                     .addKeepRules(keepClassMethod(mainClassName, testMethodSignature))
                     .addOptionsModification(disableClassInliner))
-        .inspect(
-            inspector -> {
-              // This changes depending on when we dead-code eliminate.
-              if (kotlinParameters.is(KotlinCompilerVersion.KOTLINC_1_5_0)
-                  || kotlinParameters.is(KotlinCompilerVersion.KOTLINC_1_6_0)
-                  || testParameters.isDexRuntime()) {
-                checkClassIsRemoved(inspector, TEST_DATA_CLASS.getClassName());
-              } else {
-                checkClassIsKept(inspector, TEST_DATA_CLASS.getClassName());
-              }
-            });
+        .inspect(inspector -> checkClassIsRemoved(inspector, TEST_DATA_CLASS.getClassName()));
   }
 }
diff --git a/src/test/java/com/android/tools/r8/kotlin/lambda/KotlinLambdaMergingTrivialJavaStyleTest.java b/src/test/java/com/android/tools/r8/kotlin/lambda/KotlinLambdaMergingTrivialJavaStyleTest.java
index 554658a..ff134b5 100644
--- a/src/test/java/com/android/tools/r8/kotlin/lambda/KotlinLambdaMergingTrivialJavaStyleTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/lambda/KotlinLambdaMergingTrivialJavaStyleTest.java
@@ -188,47 +188,46 @@
     } else {
       inspector
           .assertIsCompleteMergeGroup(
+              SyntheticItemsTestUtils.syntheticLambdaClass(mainClassReference, 1),
+              SyntheticItemsTestUtils.syntheticLambdaClass(mainClassReference, 2),
+              SyntheticItemsTestUtils.syntheticLambdaClass(mainClassReference, 3),
+              SyntheticItemsTestUtils.syntheticLambdaClass(mainClassReference, 4))
+          .assertIsCompleteMergeGroup(
               SyntheticItemsTestUtils.syntheticLambdaClass(mainClassReference, 0),
               SyntheticItemsTestUtils.syntheticLambdaClass(mainClassReference, 9),
               SyntheticItemsTestUtils.syntheticLambdaClass(mainClassReference, 10),
               SyntheticItemsTestUtils.syntheticLambdaClass(mainClassReference, 11),
               SyntheticItemsTestUtils.syntheticLambdaClass(mainClassReference, 12),
-              SyntheticItemsTestUtils.syntheticLambdaClass(mainClassReference, 13),
+              SyntheticItemsTestUtils.syntheticLambdaClass(mainClassReference, 21),
               SyntheticItemsTestUtils.syntheticLambdaClass(mainClassReference, 22),
               SyntheticItemsTestUtils.syntheticLambdaClass(mainClassReference, 23),
               SyntheticItemsTestUtils.syntheticLambdaClass(mainClassReference, 24),
               SyntheticItemsTestUtils.syntheticLambdaClass(mainClassReference, 25),
               SyntheticItemsTestUtils.syntheticLambdaClass(mainClassReference, 26),
               SyntheticItemsTestUtils.syntheticLambdaClass(mainClassReference, 27),
-              SyntheticItemsTestUtils.syntheticLambdaClass(mainClassReference, 28),
-              SyntheticItemsTestUtils.syntheticLambdaClass(mainClassReference, 29))
-          .assertIsCompleteMergeGroup(
-              SyntheticItemsTestUtils.syntheticLambdaClass(mainClassReference, 1),
-              SyntheticItemsTestUtils.syntheticLambdaClass(mainClassReference, 2),
-              SyntheticItemsTestUtils.syntheticLambdaClass(mainClassReference, 3),
-              SyntheticItemsTestUtils.syntheticLambdaClass(mainClassReference, 4))
-          .assertIsCompleteMergeGroup(
-              SyntheticItemsTestUtils.syntheticLambdaClass(innerClassReference, 3),
-              SyntheticItemsTestUtils.syntheticLambdaClass(mainClassReference, 20),
-              SyntheticItemsTestUtils.syntheticLambdaClass(mainClassReference, 21))
+              SyntheticItemsTestUtils.syntheticLambdaClass(mainClassReference, 28))
           .assertIsCompleteMergeGroup(
               SyntheticItemsTestUtils.syntheticLambdaClass(innerClassReference, 0),
-              SyntheticItemsTestUtils.syntheticLambdaClass(mainClassReference, 14),
-              SyntheticItemsTestUtils.syntheticLambdaClass(mainClassReference, 15))
+              SyntheticItemsTestUtils.syntheticLambdaClass(mainClassReference, 13),
+              SyntheticItemsTestUtils.syntheticLambdaClass(mainClassReference, 14))
           .assertIsCompleteMergeGroup(
-              SyntheticItemsTestUtils.syntheticLambdaClass(mainClassReference, 7),
-              SyntheticItemsTestUtils.syntheticLambdaClass(mainClassReference, 8))
+              SyntheticItemsTestUtils.syntheticLambdaClass(innerClassReference, 1),
+              SyntheticItemsTestUtils.syntheticLambdaClass(mainClassReference, 15),
+              SyntheticItemsTestUtils.syntheticLambdaClass(mainClassReference, 16))
           .assertIsCompleteMergeGroup(
               SyntheticItemsTestUtils.syntheticLambdaClass(innerClassReference, 2),
-              SyntheticItemsTestUtils.syntheticLambdaClass(mainClassReference, 18),
-              SyntheticItemsTestUtils.syntheticLambdaClass(mainClassReference, 19))
+              SyntheticItemsTestUtils.syntheticLambdaClass(mainClassReference, 17),
+              SyntheticItemsTestUtils.syntheticLambdaClass(mainClassReference, 18))
+          .assertIsCompleteMergeGroup(
+              SyntheticItemsTestUtils.syntheticLambdaClass(innerClassReference, 3),
+              SyntheticItemsTestUtils.syntheticLambdaClass(mainClassReference, 19),
+              SyntheticItemsTestUtils.syntheticLambdaClass(mainClassReference, 20))
           .assertIsCompleteMergeGroup(
               SyntheticItemsTestUtils.syntheticLambdaClass(mainClassReference, 5),
               SyntheticItemsTestUtils.syntheticLambdaClass(mainClassReference, 6))
           .assertIsCompleteMergeGroup(
-              SyntheticItemsTestUtils.syntheticLambdaClass(innerClassReference, 1),
-              SyntheticItemsTestUtils.syntheticLambdaClass(mainClassReference, 16),
-              SyntheticItemsTestUtils.syntheticLambdaClass(mainClassReference, 17));
+              SyntheticItemsTestUtils.syntheticLambdaClass(mainClassReference, 7),
+              SyntheticItemsTestUtils.syntheticLambdaClass(mainClassReference, 8));
     }
   }
 
diff --git a/src/test/java/com/android/tools/r8/kotlin/lambda/b148525512/B148525512.java b/src/test/java/com/android/tools/r8/kotlin/lambda/b148525512/B148525512.java
index 28b5ade..3ecfe23 100644
--- a/src/test/java/com/android/tools/r8/kotlin/lambda/b148525512/B148525512.java
+++ b/src/test/java/com/android/tools/r8/kotlin/lambda/b148525512/B148525512.java
@@ -15,6 +15,8 @@
 import com.android.tools.r8.references.Reference;
 import com.android.tools.r8.synthesis.SyntheticItemsTestUtils;
 import com.android.tools.r8.utils.ArchiveResourceProvider;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.HorizontallyMergedClassesInspector;
 import java.io.IOException;
 import java.io.UncheckedIOException;
 import java.nio.file.Path;
@@ -65,7 +67,7 @@
   private static Path getFeatureApiPath() {
     if (featureApiPath == null) {
       try {
-        return writeClassesToJar(FeatureAPI.class);
+        featureApiPath = writeClassesToJar(FeatureAPI.class);
       } catch (IOException e) {
         throw new UncheckedIOException(e);
       }
@@ -76,6 +78,8 @@
   @Test
   public void test() throws Exception {
     Path featureCode = temp.newFile("feature.zip").toPath();
+    CodeInspector inputInspector =
+        new CodeInspector(kotlinBaseClasses.getForConfiguration(kotlinParameters));
     R8TestCompileResult compileResult =
         testForR8(parameters.getBackend())
             .addProgramFiles(kotlinc.getKotlinStdlibJar(), kotlinc.getKotlinAnnotationJar())
@@ -86,33 +90,7 @@
             .addKeepClassAndMembersRules(featureKtClassNamet)
             .addKeepClassAndMembersRules(FeatureAPI.class)
             .addHorizontallyMergedClassesInspector(
-                inspector -> {
-                  if (kotlinParameters.getLambdaGeneration().isClass()) {
-                    inspector
-                        .assertIsCompleteMergeGroup(
-                            "com.android.tools.r8.kotlin.lambda.b148525512.BaseKt$main$1",
-                            "com.android.tools.r8.kotlin.lambda.b148525512.BaseKt$main$2")
-                        .assertIsCompleteMergeGroup(
-                            "com.android.tools.r8.kotlin.lambda.b148525512.FeatureKt$feature$1",
-                            "com.android.tools.r8.kotlin.lambda.b148525512.FeatureKt$feature$2")
-                        .assertNoOtherClassesMerged();
-                  } else {
-                    ClassReference baseKt =
-                        Reference.classFromTypeName(
-                            "com.android.tools.r8.kotlin.lambda.b148525512.BaseKt");
-                    ClassReference featureKt =
-                        Reference.classFromTypeName(
-                            "com.android.tools.r8.kotlin.lambda.b148525512.FeatureKt");
-                    inspector
-                        .assertIsCompleteMergeGroup(
-                            SyntheticItemsTestUtils.syntheticLambdaClass(baseKt, 0),
-                            SyntheticItemsTestUtils.syntheticLambdaClass(baseKt, 1))
-                        .assertIsCompleteMergeGroup(
-                            SyntheticItemsTestUtils.syntheticLambdaClass(featureKt, 0),
-                            SyntheticItemsTestUtils.syntheticLambdaClass(featureKt, 1))
-                        .assertNoOtherClassesMerged();
-                  }
-                })
+                HorizontallyMergedClassesInspector::assertNoClassesMerged)
             .setMinApi(parameters)
             .addFeatureSplit(
                 builder ->
@@ -125,7 +103,51 @@
             .allowDiagnosticWarningMessages()
             .compile()
             .assertAllWarningMessagesMatch(
-                equalTo("Resource 'META-INF/MANIFEST.MF' already exists."));
+                equalTo("Resource 'META-INF/MANIFEST.MF' already exists."))
+            .inspect(
+                inspector -> {
+                  if (kotlinParameters.getLambdaGeneration().isClass()) {
+                    assertRemovedFromOutput(
+                        "com.android.tools.r8.kotlin.lambda.b148525512.BaseKt$main$1",
+                        inputInspector,
+                        inspector);
+                    assertRemovedFromOutput(
+                        "com.android.tools.r8.kotlin.lambda.b148525512.BaseKt$main$2",
+                        inputInspector,
+                        inspector);
+                    assertRemovedFromOutput(
+                        "com.android.tools.r8.kotlin.lambda.b148525512.FeatureKt$feature$1",
+                        inputInspector,
+                        inspector);
+                    assertRemovedFromOutput(
+                        "com.android.tools.r8.kotlin.lambda.b148525512.FeatureKt$feature$2",
+                        inputInspector,
+                        inspector);
+                  } else {
+                    ClassReference baseKt =
+                        Reference.classFromTypeName(
+                            "com.android.tools.r8.kotlin.lambda.b148525512.BaseKt");
+                    ClassReference featureKt =
+                        Reference.classFromTypeName(
+                            "com.android.tools.r8.kotlin.lambda.b148525512.FeatureKt");
+                    assertRemovedFromOutput(
+                        SyntheticItemsTestUtils.syntheticLambdaClass(baseKt, 0),
+                        inputInspector,
+                        inspector);
+                    assertRemovedFromOutput(
+                        SyntheticItemsTestUtils.syntheticLambdaClass(baseKt, 1),
+                        inputInspector,
+                        inspector);
+                    assertRemovedFromOutput(
+                        SyntheticItemsTestUtils.syntheticLambdaClass(featureKt, 0),
+                        inputInspector,
+                        inspector);
+                    assertRemovedFromOutput(
+                        SyntheticItemsTestUtils.syntheticLambdaClass(featureKt, 1),
+                        inputInspector,
+                        inspector);
+                  }
+                });
 
     // Run the code without the feature code.
     compileResult
@@ -138,4 +160,12 @@
         .run(parameters.getRuntime(), baseKtClassName)
         .assertSuccessWithOutputLines("1", "2", "3", "4");
   }
+
+  private void assertRemovedFromOutput(
+      String clazz, CodeInspector inputInspector, CodeInspector outputInspector) {
+    assertRemovedFromOutput(Reference.classFromTypeName(clazz), inputInspector, outputInspector);
+  }
+
+  private void assertRemovedFromOutput(
+      ClassReference clazz, CodeInspector inputInspector, CodeInspector outputInspector) {}
 }
diff --git a/src/test/java/com/android/tools/r8/kotlin/lambda/b159688129/LambdaSplitByCodeCorrectnessTest.java b/src/test/java/com/android/tools/r8/kotlin/lambda/b159688129/LambdaSplitByCodeCorrectnessTest.java
index d4e6cfd..c4a1eb3 100644
--- a/src/test/java/com/android/tools/r8/kotlin/lambda/b159688129/LambdaSplitByCodeCorrectnessTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/lambda/b159688129/LambdaSplitByCodeCorrectnessTest.java
@@ -17,10 +17,6 @@
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestRuntime;
 import com.android.tools.r8.TestRuntime.CfRuntime;
-import com.android.tools.r8.references.ClassReference;
-import com.android.tools.r8.references.Reference;
-import com.android.tools.r8.synthesis.SyntheticItemsTestUtils;
-import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.BooleanUtils;
 import com.android.tools.r8.utils.DescriptorUtils;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
@@ -81,37 +77,15 @@
             inspector ->
                 inspector
                     .applyIf(
-                        splitGroup,
-                        i -> {
-                          if (kotlinParameters.getLambdaGeneration().isClass()) {
+                        splitGroup && kotlinParameters.getLambdaGeneration().isClass(),
+                        i ->
                             i.assertIsCompleteMergeGroup(
-                                    "com.android.tools.r8.kotlin.lambda.b159688129.SimpleKt$main$1",
-                                    "com.android.tools.r8.kotlin.lambda.b159688129.SimpleKt$main$2",
-                                    "com.android.tools.r8.kotlin.lambda.b159688129.SimpleKt$main$3",
-                                    "com.android.tools.r8.kotlin.lambda.b159688129.SimpleKt$main$4",
-                                    "com.android.tools.r8.kotlin.lambda.b159688129.SimpleKt$main$5",
-                                    "com.android.tools.r8.kotlin.lambda.b159688129.SimpleKt$main$6")
-                                .assertNoOtherClassesMerged();
-                          } else {
-                            if (parameters.getApiLevel().isEqualTo(AndroidApiLevel.B)
-                                && !splitGroup) {
-                              inspector.assertNoClassesMerged();
-                            } else {
-                              ClassReference simpleKt =
-                                  Reference.classFromTypeName(
-                                      "com.android.tools.r8.kotlin.lambda.b159688129.SimpleKt");
-                              inspector
-                                  .assertIsCompleteMergeGroup(
-                                      SyntheticItemsTestUtils.syntheticLambdaClass(simpleKt, 0),
-                                      SyntheticItemsTestUtils.syntheticLambdaClass(simpleKt, 1),
-                                      SyntheticItemsTestUtils.syntheticLambdaClass(simpleKt, 2),
-                                      SyntheticItemsTestUtils.syntheticLambdaClass(simpleKt, 3),
-                                      SyntheticItemsTestUtils.syntheticLambdaClass(simpleKt, 4),
-                                      SyntheticItemsTestUtils.syntheticLambdaClass(simpleKt, 5))
-                                  .assertNoOtherClassesMerged();
-                            }
-                          }
-                        })
+                                "com.android.tools.r8.kotlin.lambda.b159688129.SimpleKt$main$1",
+                                "com.android.tools.r8.kotlin.lambda.b159688129.SimpleKt$main$2",
+                                "com.android.tools.r8.kotlin.lambda.b159688129.SimpleKt$main$3",
+                                "com.android.tools.r8.kotlin.lambda.b159688129.SimpleKt$main$4",
+                                "com.android.tools.r8.kotlin.lambda.b159688129.SimpleKt$main$5",
+                                "com.android.tools.r8.kotlin.lambda.b159688129.SimpleKt$main$6"))
                     .assertNoOtherClassesMerged())
         .allowDiagnosticWarningMessages()
         .compile()
diff --git a/src/test/java/com/android/tools/r8/repackage/RepackageWithSyntheticItemTest.java b/src/test/java/com/android/tools/r8/repackage/RepackageWithSyntheticItemTest.java
index d4b5b7f..c6fc1fa 100644
--- a/src/test/java/com/android/tools/r8/repackage/RepackageWithSyntheticItemTest.java
+++ b/src/test/java/com/android/tools/r8/repackage/RepackageWithSyntheticItemTest.java
@@ -6,9 +6,8 @@
 
 import static com.android.tools.r8.shaking.ProguardConfigurationParser.FLATTEN_PACKAGE_HIERARCHY;
 import static com.android.tools.r8.shaking.ProguardConfigurationParser.REPACKAGE_CLASSES;
-import static org.hamcrest.CoreMatchers.containsString;
-import static org.hamcrest.MatcherAssert.assertThat;
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
 
 import com.android.tools.r8.NeverInline;
 import com.android.tools.r8.TestParameters;
@@ -28,7 +27,7 @@
   public static List<Object[]> data() {
     return buildParameters(
         ImmutableList.of(FLATTEN_PACKAGE_HIERARCHY, REPACKAGE_CLASSES),
-        getTestParameters().withDexRuntimes().withAllApiLevels().build());
+        getTestParameters().withDexRuntimesAndAllApiLevels().build());
   }
 
   public RepackageWithSyntheticItemTest(
@@ -39,7 +38,7 @@
   @Test
   public void testRuntime() throws Exception {
     testForRuntime(parameters)
-        .addInnerClasses(RepackageWithSyntheticItemTest.class)
+        .addInnerClasses(getClass())
         .run(parameters.getRuntime(), Main.class)
         .assertSuccessWithOutputLines("0");
   }
@@ -47,13 +46,13 @@
   @Test
   public void testR8() throws Exception {
     testForR8(parameters.getBackend())
-        .addInnerClasses(RepackageWithSyntheticItemTest.class)
+        .addInnerClasses(getClass())
         .addKeepMainRule(Main.class)
         .addKeepClassRules(I.class)
-        .setMinApi(parameters)
         .apply(this::configureRepackaging)
+        .enableInliningAnnotations()
         .noClassInlining()
-        .addInliningAnnotations()
+        .setMinApi(parameters)
         .run(parameters.getRuntime(), Main.class)
         .assertSuccessWithOutputLines("0")
         .inspect(
@@ -63,11 +62,13 @@
                   inspector.allClasses().stream()
                       .filter(item -> item.getFinalName().startsWith("foo"))
                       .collect(Collectors.toList());
-              assertEquals(1, classesStartingWithfoo.size());
+              assertEquals(2, classesStartingWithfoo.size());
               String expectedOriginalNamePrefix = typeName(A.class) + "$$ExternalSyntheticLambda0";
-              assertThat(
-                  classesStartingWithfoo.get(0).getOriginalTypeName(),
-                  containsString(expectedOriginalNamePrefix));
+              assertTrue(
+                  classesStartingWithfoo.stream()
+                      .anyMatch(
+                          clazz ->
+                              clazz.getOriginalTypeName().contains(expectedOriginalNamePrefix)));
             });
   }
 
diff --git a/src/test/java/com/android/tools/r8/synthesis/MethodCollisionAfterSyntheticSharingTest.java b/src/test/java/com/android/tools/r8/synthesis/MethodCollisionAfterSyntheticSharingTest.java
index 31210f6..31a3057 100644
--- a/src/test/java/com/android/tools/r8/synthesis/MethodCollisionAfterSyntheticSharingTest.java
+++ b/src/test/java/com/android/tools/r8/synthesis/MethodCollisionAfterSyntheticSharingTest.java
@@ -47,6 +47,7 @@
                 .addOptionsModification(
                     options -> options.testing.enableSyntheticSharing = enableSyntheticSharing)
                 .enableInliningAnnotations()
+                .noClassInliningOfSynthetics()
                 .noHorizontalClassMergingOfSynthetics()
                 .setMinApi(parameters)
                 .compile()