Small method inliner

This adds a simple inliner pass that aggressively inlines small methods
such as getters and setters early in the pipeline. All small methods
that are inlined into all call sites are then deleted from the app.

Experiments show that this can lead to the removal of a large fraction
of all methods, which lead to less memory usage, and that the extra
inlining pass does not negatively impact build speed.

Change-Id: Ieddc6b51bb2c240646bf21a894e1e7e7c204a579
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java
index bddf650..163e4a1 100644
--- a/src/main/java/com/android/tools/r8/R8.java
+++ b/src/main/java/com/android/tools/r8/R8.java
@@ -79,6 +79,7 @@
 import com.android.tools.r8.optimize.proto.ProtoNormalizer;
 import com.android.tools.r8.optimize.redundantbridgeremoval.RedundantBridgeRemover;
 import com.android.tools.r8.optimize.singlecaller.SingleCallerInliner;
+import com.android.tools.r8.optimize.smallmethodinliner.SmallMethodInliner;
 import com.android.tools.r8.origin.CommandLineOrigin;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.profile.art.ArtProfileCompletenessChecker;
@@ -536,6 +537,8 @@
 
       LirConverter.rewriteLirWithLens(appView, timing, executorService);
 
+      SmallMethodInliner.run(appViewWithLiveness, executorService, timing);
+
       VerticalClassMerger.createForInitialClassMerging(appViewWithLiveness, timing)
           .runIfNecessary(executorService, timing);
 
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 6cff9de..54e84f0 100644
--- a/src/main/java/com/android/tools/r8/graph/AppView.java
+++ b/src/main/java/com/android/tools/r8/graph/AppView.java
@@ -46,6 +46,7 @@
 import com.android.tools.r8.optimize.argumentpropagation.ArgumentPropagator;
 import com.android.tools.r8.optimize.compose.ComposeReferences;
 import com.android.tools.r8.optimize.interfaces.collection.OpenClosedInterfacesCollection;
+import com.android.tools.r8.optimize.smallmethodinliner.SmallMethodInlinerResult;
 import com.android.tools.r8.profile.art.ArtProfileCollection;
 import com.android.tools.r8.profile.startup.profile.StartupProfile;
 import com.android.tools.r8.resourceshrinker.r8integration.R8ResourceShrinkerState;
@@ -66,9 +67,9 @@
 import com.android.tools.r8.synthesis.SyntheticItems.GlobalSyntheticsStrategy;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.InternalOptions.TestingOptions;
-import com.android.tools.r8.utils.internal.OptionalBool;
 import com.android.tools.r8.utils.Reporter;
 import com.android.tools.r8.utils.ResourceShrinkerUtils;
+import com.android.tools.r8.utils.internal.OptionalBool;
 import com.android.tools.r8.utils.internal.ThrowingConsumer;
 import com.android.tools.r8.utils.threads.ThreadTask;
 import com.android.tools.r8.utils.threads.ThreadTaskUtils;
@@ -152,6 +153,7 @@
   private HorizontallyMergedClasses horizontallyMergedClasses = HorizontallyMergedClasses.empty();
   private VerticallyMergedClasses verticallyMergedClasses;
   private EnumDataMap unboxedEnums = null;
+  private SmallMethodInlinerResult smallMethodInlinerResult = null;
   private OpenClosedInterfacesCollection openClosedInterfacesCollection =
       OpenClosedInterfacesCollection.getDefault();
   // TODO(b/169115389): Remove
@@ -961,6 +963,14 @@
     this.openClosedInterfacesCollection = openClosedInterfacesCollection;
   }
 
+  public SmallMethodInlinerResult getSmallMethodInlinerResult() {
+    return smallMethodInlinerResult;
+  }
+
+  public void setSmallMethodInlinerResult(SmallMethodInlinerResult smallMethodInlinerResult) {
+    this.smallMethodInlinerResult = smallMethodInlinerResult;
+  }
+
   public boolean hasUnboxedEnums() {
     return unboxedEnums != null;
   }
@@ -1349,6 +1359,18 @@
                 public boolean shouldRun() {
                   return appView.getSyntheticUnsafeClass() != null;
                 }
+              },
+              new ThreadTask() {
+                @Override
+                public void run(Timing timing) throws Exception {
+                  appView.setSmallMethodInlinerResult(
+                      appView.getSmallMethodInlinerResult().rewrittenWithLens(lens, appliedLens));
+                }
+
+                @Override
+                public boolean shouldRun() {
+                  return appView.getSmallMethodInlinerResult() != null;
+                }
               });
         });
 
diff --git a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
index c0c6410..a388e9e 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
@@ -42,12 +42,12 @@
 import com.android.tools.r8.lightir.LirCode;
 import com.android.tools.r8.naming.NamingLens;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.utils.RetracerForCodePrinting;
 import com.android.tools.r8.utils.internal.BooleanBox;
 import com.android.tools.r8.utils.internal.BooleanUtils;
 import com.android.tools.r8.utils.internal.ConsumerUtils;
 import com.android.tools.r8.utils.internal.ObjectUtils;
 import com.android.tools.r8.utils.internal.OptionalBool;
-import com.android.tools.r8.utils.RetracerForCodePrinting;
 import com.android.tools.r8.utils.internal.exceptions.Unreachable;
 import com.android.tools.r8.utils.structural.CompareToVisitor;
 import com.android.tools.r8.utils.structural.HashingVisitor;
@@ -835,6 +835,11 @@
     return code;
   }
 
+  public LirCode<?> getLirCode() {
+    checkIfObsolete();
+    return hasLirCode() ? code.asLirCode() : null;
+  }
+
   public CfVersion getClassFileVersion() {
     checkIfObsolete();
     assert classFileVersion != null;
diff --git a/src/main/java/com/android/tools/r8/graph/FieldAccessInfoImpl.java b/src/main/java/com/android/tools/r8/graph/FieldAccessInfoImpl.java
index 2836942..6e4f08f 100644
--- a/src/main/java/com/android/tools/r8/graph/FieldAccessInfoImpl.java
+++ b/src/main/java/com/android/tools/r8/graph/FieldAccessInfoImpl.java
@@ -8,9 +8,9 @@
 import com.android.tools.r8.graph.lens.GraphLens;
 import com.android.tools.r8.shaking.Enqueuer;
 import com.android.tools.r8.shaking.Enqueuer.FieldAccessKind;
+import com.android.tools.r8.utils.collections.ProgramMethodSet;
 import com.android.tools.r8.utils.internal.BitUtils;
 import com.android.tools.r8.utils.internal.ObjectUtils;
-import com.android.tools.r8.utils.collections.ProgramMethodSet;
 import com.android.tools.r8.utils.internal.exceptions.Unreachable;
 import com.google.common.collect.Sets;
 import java.util.Map;
@@ -340,6 +340,7 @@
     return writesWithContexts.isAccessedOutside(method) || isWrittenIndirectly();
   }
 
+  @Override
   public boolean recordRead(DexField access, ProgramMethod context) {
     setReadDirectly();
     if (readsWithContexts.isBottom()) {
@@ -351,6 +352,7 @@
     return false;
   }
 
+  @Override
   public boolean recordWrite(DexField access, ProgramMethod context) {
     setWrittenDirectly();
     updateEffectivelyFinalFlags(context);
diff --git a/src/main/java/com/android/tools/r8/graph/MutableFieldAccessInfo.java b/src/main/java/com/android/tools/r8/graph/MutableFieldAccessInfo.java
index ee1d85b..de238d4 100644
--- a/src/main/java/com/android/tools/r8/graph/MutableFieldAccessInfo.java
+++ b/src/main/java/com/android/tools/r8/graph/MutableFieldAccessInfo.java
@@ -8,4 +8,8 @@
   void clearReads();
 
   void clearWrites();
+
+  boolean recordRead(DexField access, ProgramMethod context);
+
+  boolean recordWrite(DexField access, ProgramMethod context);
 }
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/proto/GeneratedExtensionRegistryShrinker.java b/src/main/java/com/android/tools/r8/ir/analysis/proto/GeneratedExtensionRegistryShrinker.java
index 512deb5..eb07c95 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/proto/GeneratedExtensionRegistryShrinker.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/proto/GeneratedExtensionRegistryShrinker.java
@@ -7,6 +7,7 @@
 import static com.android.tools.r8.graph.DexProgramClass.asProgramClassOrNull;
 
 import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexEncodedField;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexField;
@@ -221,10 +222,15 @@
   }
 
   private void forEachFindLiteExtensionByNumberMethod(Consumer<ProgramMethod> consumer) {
+    DexClass extensionRegistryLiteClass =
+        appView.appInfo().definitionForWithoutExistenceAssert(references.extensionRegistryLiteType);
+    if (extensionRegistryLiteClass == null) {
+      return;
+    }
     appView
         .appInfo()
         .forEachInstantiatedSubType(
-            references.extensionRegistryLiteType,
+            extensionRegistryLiteClass.getType(),
             clazz ->
                 clazz.forEachProgramMethodMatching(
                     definition ->
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 b471350..a66aa55 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
@@ -19,6 +19,7 @@
 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.MethodResolutionResult;
 import com.android.tools.r8.graph.MethodResolutionResult.SingleResolutionResult;
 import com.android.tools.r8.graph.NestMemberClassAttribute;
 import com.android.tools.r8.graph.ProgramField;
@@ -101,6 +102,7 @@
   private final IRConverter converter;
   private final LensCodeRewriter lensCodeRewriter;
   final MainDexInfo mainDexInfo;
+  protected final InternalOptions options;
 
   // The set of callers of single caller methods where the single caller method could not be inlined
   // due to not being processed at the time of inlining.
@@ -140,11 +142,12 @@
     this.lensCodeRewriter = lensCodeRewriter;
     this.mainDexInfo = appView.appInfo().getMainDexInfo();
     this.multiCallerInliner = new MultiCallerInliner(appView);
+    this.options = appView.options();
     this.singleInlineCallers =
         LongLivedProgramMethodSetBuilder.createConcurrentForIdentitySet(appView.graphLens());
     availableApiExceptions =
-        appView.options().canHaveDalvikCatchHandlerVerificationBug()
-            ? new AvailableApiExceptions(appView.options())
+        options.canHaveDalvikCatchHandlerVerificationBug()
+            ? new AvailableApiExceptions(options)
             : null;
   }
 
@@ -169,7 +172,6 @@
 
   @SuppressWarnings("ReferenceEquality")
   public ConstraintWithTarget computeInliningConstraint(IRCode code) {
-    InternalOptions options = appView.options();
     if (!options.inlinerOptions().isEnabled()) {
       return ConstraintWithTarget.NEVER;
     }
@@ -188,7 +190,7 @@
     }
 
     ProgramMethod context = code.context();
-    if (appView.options().canHaveDalvikIntUsedAsNonIntPrimitiveTypeBug()
+    if (options.canHaveDalvikIntUsedAsNonIntPrimitiveTypeBug()
         && returnsIntAsBoolean(code, context)) {
       return ConstraintWithTarget.NEVER;
     }
@@ -1020,7 +1022,7 @@
       blockToInvokes.computeIfAbsent(invoke.getBlock(), ignoreKey(ArrayList::new)).add(invoke);
     }
     InvokeSupplier invokeSupplier =
-        (block, consumer) -> {
+        (theMethod, block, consumer) -> {
           List<InvokeMethod> invokes = blockToInvokes.getOrDefault(block, Collections.emptyList());
           if (invokes.isEmpty()) {
             return;
@@ -1077,13 +1079,8 @@
       MethodProcessor methodProcessor,
       Timing timing,
       InliningReasonStrategy inliningReasonStrategy) {
-    DefaultInliningOracle oracle =
-        createDefaultOracle(code, method, methodProcessor, inliningReasonStrategy);
-    InliningIRProvider inliningIRProvider =
-        new InliningIRProvider(appView, method, code, lensCodeRewriter, methodProcessor);
-    assert inliningIRProvider.verifyIRCacheIsEmpty();
     InvokeSupplier invokeSupplier =
-        (block, consumer) -> {
+        (theMethod, block, consumer) -> {
           InstructionListIterator iterator = block.listIterator();
           while (iterator.hasNext()) {
             Instruction current = iterator.next();
@@ -1092,6 +1089,23 @@
             }
           }
         };
+    performInlining(
+        method, code, feedback, methodProcessor, timing, inliningReasonStrategy, invokeSupplier);
+  }
+
+  public void performInlining(
+      ProgramMethod method,
+      IRCode code,
+      OptimizationFeedback feedback,
+      MethodProcessor methodProcessor,
+      Timing timing,
+      InliningReasonStrategy inliningReasonStrategy,
+      InvokeSupplier invokeSupplier) {
+    DefaultInliningOracle oracle =
+        createDefaultOracle(code, method, methodProcessor, inliningReasonStrategy);
+    InliningIRProvider inliningIRProvider =
+        new InliningIRProvider(appView, method, code, lensCodeRewriter, methodProcessor);
+    assert inliningIRProvider.verifyIRCacheIsEmpty();
     performInliningImpl(
         oracle,
         method,
@@ -1147,9 +1161,10 @@
         continue;
       }
       invokeSupplier.forEachInvoke(
+          context,
           block,
           (invoke, iterator) ->
-              inlineInvoke(
+              inlineInvokes(
                   oracle,
                   context,
                   code,
@@ -1173,7 +1188,7 @@
     new R8MemberValuePropagation(appView).run(code);
   }
 
-  private void inlineInvoke(
+  private void inlineInvokes(
       InliningOracle oracle,
       ProgramMethod context,
       IRCode code,
@@ -1190,13 +1205,17 @@
     // TODO(b/142116551): This should be equivalent to invoke.lookupSingleTarget()!
     BasicBlock block = invoke.getBlock();
     DexMethod invokedMethod = invoke.getInvokedMethod();
-    SingleResolutionResult<?> resolutionResult =
-        appView
-            .appInfo()
-            .resolveMethodLegacy(invokedMethod, invoke.getInterfaceBit())
-            .asSingleResolution();
-    if (resolutionResult == null
-        || resolutionResult.isAccessibleFrom(context, appView).isPossiblyFalse()) {
+
+    MethodResolutionResult maybeFailedResolutionResult =
+        appView.appInfo().resolveMethodLegacy(invokedMethod, invoke.getInterfaceBit());
+    if (!maybeFailedResolutionResult.isSingleResolution()) {
+      notifyInvokeNotInlined(invoke, maybeFailedResolutionResult);
+      return;
+    }
+
+    SingleResolutionResult<?> resolutionResult = maybeFailedResolutionResult.asSingleResolution();
+    if (resolutionResult.isAccessibleFrom(context, appView).isPossiblyFalse()) {
+      notifyInvokeNotInlined(invoke, resolutionResult);
       return;
     }
 
@@ -1208,6 +1227,7 @@
     // TODO(b/156853206): Should not duplicate resolution.
     ProgramMethod singleTarget = oracle.lookupSingleTarget(invoke, context);
     if (singleTarget == null) {
+      notifyInvokeNotInlined(invoke, resolutionResult);
       WhyAreYouNotInliningReporter.handleInvokeWithUnknownTarget(this, invoke, appView, context);
       return;
     }
@@ -1232,6 +1252,7 @@
             whyAreYouNotInliningReporter);
     timing.end();
     if (inlineResult == null) {
+      notifyInvokeNotInlined(invoke, resolutionResult);
       assert whyAreYouNotInliningReporter.unsetReasonHasBeenReportedFlag();
       return;
     }
@@ -1243,11 +1264,13 @@
 
     InlineAction action = inlineResult.asInlineAction();
     if (action.reason == Reason.MULTI_CALLER_CANDIDATE) {
+      notifyInvokeNotInlined(invoke, resolutionResult);
       assert methodProcessor.isPrimaryMethodProcessor();
       return;
     }
 
     if (!singleTargetOracle.stillHasBudget(action, whyAreYouNotInliningReporter)) {
+      notifyInvokeNotInlined(invoke, resolutionResult);
       assert whyAreYouNotInliningReporter.unsetReasonHasBeenReportedFlag();
       return;
     }
@@ -1257,6 +1280,7 @@
     timing.end();
     if (singleTargetOracle.willExceedBudget(
         action, code, inlinee, invoke, block, whyAreYouNotInliningReporter)) {
+      notifyInvokeNotInlined(invoke, resolutionResult);
       assert whyAreYouNotInliningReporter.unsetReasonHasBeenReportedFlag();
       return;
     }
@@ -1275,8 +1299,7 @@
     oracle.markInlined(inlinee);
 
     timing.begin("Inline invoke");
-    iterator.inlineInvoke(
-        appView, code, inlinee, blockIterator, blocksToRemove, action.getDowncastClass());
+    inlineInvoke(iterator, code, inlinee, blockIterator, blocksToRemove, action.getDowncastClass());
     timing.end();
 
     if (action.shouldEnsureStoreStoreFenceCauses != null) {
@@ -1321,10 +1344,7 @@
 
     if (inlineeMayHaveInvokeMethod) {
       int inliningDepth = inlineeStack.size() + 1;
-      if (appView
-          .options()
-          .inlinerOptions()
-          .shouldApplyInliningToInlinee(appView, singleTarget, inliningDepth)) {
+      if (shouldApplyInliningToInlinee(appView, singleTarget, inliningDepth)) {
         // Record that we will be inside the inlinee until the next block.
         BasicBlock inlineeEnd = IteratorUtils.peekNext(blockIterator);
         inlineeStack.push(inlineeEnd);
@@ -1335,6 +1355,28 @@
     }
   }
 
+  protected void inlineInvoke(
+      InstructionListIterator iterator,
+      IRCode code,
+      IRCode inlinee,
+      ListIterator<BasicBlock> blockIterator,
+      Set<BasicBlock> blocksToRemove,
+      DexProgramClass downcast) {
+    iterator.inlineInvoke(appView, code, inlinee, blockIterator, blocksToRemove, downcast);
+  }
+
+  protected void notifyInvokeNotInlined(
+      InvokeMethod invoke, MethodResolutionResult resolutionResult) {
+    // Intentionally empty.
+  }
+
+  protected boolean shouldApplyInliningToInlinee(
+      AppView<?> appView, ProgramMethod singleTarget, int inliningDepth) {
+    return options
+        .inlinerOptions()
+        .shouldApplyInliningToInlinee(appView, singleTarget, inliningDepth);
+  }
+
   private InliningOracle getSingleTargetOracle(
       InvokeMethod invoke, ProgramMethod singleTarget, InliningOracle oracle) {
     return oracle.isForcedInliningOracle() || !singleTarget.getOptimizationInfo().forceInline()
@@ -1367,7 +1409,7 @@
 
   private boolean containsPotentialCatchHandlerVerificationError(IRCode code) {
     if (availableApiExceptions == null) {
-      assert !appView.options().canHaveDalvikCatchHandlerVerificationBug();
+      assert !options.canHaveDalvikCatchHandlerVerificationBug();
       return false;
     }
     for (BasicBlock block : code.blocks) {
@@ -1567,9 +1609,11 @@
     return true;
   }
 
-  private interface InvokeSupplier {
+  public interface InvokeSupplier {
 
     void forEachInvoke(
-        BasicBlock block, BiConsumer<InvokeMethod, InstructionListIterator> consumer);
+        ProgramMethod method,
+        BasicBlock block,
+        BiConsumer<InvokeMethod, InstructionListIterator> consumer);
   }
 }
diff --git a/src/main/java/com/android/tools/r8/lightir/LirOpcodeUtils.java b/src/main/java/com/android/tools/r8/lightir/LirOpcodeUtils.java
index d603268..1bf60b8ec 100644
--- a/src/main/java/com/android/tools/r8/lightir/LirOpcodeUtils.java
+++ b/src/main/java/com/android/tools/r8/lightir/LirOpcodeUtils.java
@@ -85,4 +85,8 @@
   public static boolean isInvokeVirtual(int opcode) {
     return opcode == INVOKEVIRTUAL;
   }
+
+  public static boolean isReturn(int opcode) {
+    return opcode == LirOpcodes.RETURN || opcode == LirOpcodes.ARETURN;
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorMethodReprocessingEnqueuer.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorMethodReprocessingEnqueuer.java
index f1e180e..4bf5ec4 100644
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorMethodReprocessingEnqueuer.java
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorMethodReprocessingEnqueuer.java
@@ -15,6 +15,7 @@
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexValue;
 import com.android.tools.r8.graph.FieldResolutionResult;
+import com.android.tools.r8.graph.FieldResolutionResult.SingleFieldResolutionResult;
 import com.android.tools.r8.graph.MethodResolutionResult.SingleResolutionResult;
 import com.android.tools.r8.graph.ProgramField;
 import com.android.tools.r8.graph.ProgramMethod;
@@ -224,8 +225,13 @@
           if (fieldInstruction == null) {
             continue;
           }
-          ProgramField resolvedField =
-              fieldInstruction.resolveField(appView, method).getProgramField();
+          SingleFieldResolutionResult<?> resolutionResult =
+              fieldInstruction.resolveField(appView, method).asSingleFieldResolutionResult();
+          if (resolutionResult == null
+              || resolutionResult.isAccessibleFrom(method, appView).isFalse()) {
+            continue;
+          }
+          ProgramField resolvedField = resolutionResult.getProgramField();
           if (resolvedField == null || !isDeadFieldAccess(resolvedField)) {
             continue;
           }
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/propagation/DefaultFieldValueJoiner.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/propagation/DefaultFieldValueJoiner.java
index 3905448..a87b3bc 100644
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/propagation/DefaultFieldValueJoiner.java
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/propagation/DefaultFieldValueJoiner.java
@@ -34,8 +34,8 @@
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.MapUtils;
 import com.android.tools.r8.utils.ThreadUtils;
-import com.android.tools.r8.utils.internal.collections.Pair;
 import com.android.tools.r8.utils.collections.ProgramFieldSet;
+import com.android.tools.r8.utils.internal.collections.Pair;
 import com.android.tools.r8.utils.internal.exceptions.Unreachable;
 import com.android.tools.r8.utils.timing.Timing;
 import com.google.common.collect.Iterables;
@@ -220,8 +220,8 @@
     if (!options.canInitNewInstanceUsingSuperclassConstructor()) {
       return fieldsNotSubjectToInitializerAnalysis;
     }
-    if (classesWithSingleCallerInlinedInstanceInitializers != null
-        && classesWithSingleCallerInlinedInstanceInitializers.isEmpty()) {
+
+    if (!hasFullyInlinedInstanceInitializers()) {
       return fieldsNotSubjectToInitializerAnalysis;
     }
 
@@ -231,8 +231,7 @@
     MapUtils.removeIf(
         fieldsSubjectToInitializerAnalysis,
         (holder, fields) -> {
-          if (classesWithSingleCallerInlinedInstanceInitializers != null
-              && !classesWithSingleCallerInlinedInstanceInitializers.contains(holder)) {
+          if (!hasFullyInlinedInstanceInitializers(holder)) {
             return false;
           }
 
@@ -252,6 +251,18 @@
     return fieldsNotSubjectToInitializerAnalysis;
   }
 
+  private boolean hasFullyInlinedInstanceInitializers() {
+    return !classesWithSingleCallerInlinedInstanceInitializers.isEmpty()
+        || (appView.getSmallMethodInlinerResult() != null
+            && appView.getSmallMethodInlinerResult().hasFullyInlinedInstanceInitializers());
+  }
+
+  private boolean hasFullyInlinedInstanceInitializers(DexProgramClass clazz) {
+    return classesWithSingleCallerInlinedInstanceInitializers.contains(clazz)
+        || (appView.getSmallMethodInlinerResult() != null
+            && appView.getSmallMethodInlinerResult().hasFullyInlinedInstanceInitializers(clazz));
+  }
+
   private void analyzeInitializers(
       Map<DexProgramClass, List<ProgramField>> fieldsOfInterest,
       Consumer<ProgramField> concurrentLiveDefaultValueConsumer,
diff --git a/src/main/java/com/android/tools/r8/optimize/smallmethodinliner/SmallMethodInliner.java b/src/main/java/com/android/tools/r8/optimize/smallmethodinliner/SmallMethodInliner.java
new file mode 100644
index 0000000..2b49438
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/optimize/smallmethodinliner/SmallMethodInliner.java
@@ -0,0 +1,363 @@
+// Copyright (c) 2026, 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.optimize.smallmethodinliner;
+
+import static com.android.tools.r8.graph.DexClassAndMethod.asProgramMethodOrNull;
+import static com.android.tools.r8.utils.MapUtils.ignoreKey;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.Code;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.MethodResolutionResult;
+import com.android.tools.r8.graph.MethodResolutionResult.SingleResolutionResult;
+import com.android.tools.r8.graph.MutableFieldAccessInfo;
+import com.android.tools.r8.graph.ProgramField;
+import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.graph.PrunedItems;
+import com.android.tools.r8.graph.bytecodemetadata.BytecodeMetadataProvider;
+import com.android.tools.r8.ir.code.BasicBlock;
+import com.android.tools.r8.ir.code.FieldInstruction;
+import com.android.tools.r8.ir.code.IRCode;
+import com.android.tools.r8.ir.code.Instruction;
+import com.android.tools.r8.ir.code.InstructionListIterator;
+import com.android.tools.r8.ir.code.InvokeMethod;
+import com.android.tools.r8.ir.conversion.IRFinalizer;
+import com.android.tools.r8.ir.conversion.MethodConversionOptions;
+import com.android.tools.r8.ir.conversion.MethodProcessor;
+import com.android.tools.r8.ir.conversion.MethodProcessorEventConsumer;
+import com.android.tools.r8.ir.conversion.OneTimeMethodProcessor;
+import com.android.tools.r8.ir.optimize.DeadCodeRemover;
+import com.android.tools.r8.ir.optimize.DefaultInliningOracle;
+import com.android.tools.r8.ir.optimize.Inliner;
+import com.android.tools.r8.ir.optimize.Inliner.InvokeSupplier;
+import com.android.tools.r8.ir.optimize.info.OptimizationFeedbackSimple;
+import com.android.tools.r8.ir.optimize.inliner.InliningIRProvider;
+import com.android.tools.r8.ir.optimize.inliner.InliningReasonStrategy;
+import com.android.tools.r8.ir.optimize.inliner.WhyAreYouNotInliningReporter;
+import com.android.tools.r8.lightir.LirCode;
+import com.android.tools.r8.lightir.LirConstant;
+import com.android.tools.r8.lightir.LirInstructionView;
+import com.android.tools.r8.lightir.LirOpcodeUtils;
+import com.android.tools.r8.optimize.singlecaller.SingleCallerInliner;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.shaking.KeepMethodInfo;
+import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.ThreadUtils;
+import com.android.tools.r8.utils.collections.DexMethodSignatureSet;
+import com.android.tools.r8.utils.collections.ProgramMethodSet;
+import com.android.tools.r8.utils.timing.Timing;
+import com.google.common.collect.Sets;
+import java.util.IdentityHashMap;
+import java.util.ListIterator;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
+import java.util.function.BiConsumer;
+
+/**
+ * A simple inliner pass that aggressively inlines small methods such as getters and setters early
+ * in the pipeline. All small methods that are inlined into all call sites are then deleted from the
+ * app.
+ */
+public class SmallMethodInliner extends Inliner implements InliningReasonStrategy, InvokeSupplier {
+
+  private final ProgramMethodSet methodsToInline = ProgramMethodSet.createConcurrent();
+  private final ProgramMethodSet failedToInline = ProgramMethodSet.createConcurrent();
+
+  private final ProgramMethodSet needsFinalization = ProgramMethodSet.createConcurrent();
+
+  private SmallMethodInliner(AppView<AppInfoWithLiveness> appView) {
+    super(appView);
+  }
+
+  public static void run(
+      AppView<AppInfoWithLiveness> appView, ExecutorService executorService, Timing timing)
+      throws ExecutionException {
+    InternalOptions options = appView.options();
+    if (options.isOptimizing() && options.isShrinking()) {
+      timing.begin("SmallMethodInliner");
+      new SmallMethodInliner(appView).runInternal(executorService, timing);
+      appView.getTypeElementFactory().clearTypeElementsCache();
+      timing.end();
+    }
+  }
+
+  private void runInternal(ExecutorService executorService, Timing timing)
+      throws ExecutionException {
+    computeMethodsToInline(executorService, timing);
+    if (methodsToInline.isEmpty()) {
+      return;
+    }
+
+    ProgramMethodSet callers = computeCallers(executorService, timing);
+    processCallers(callers, executorService, timing);
+    pruneInlinedMethods(executorService, timing);
+  }
+
+  private void computeMethodsToInline(ExecutorService executorService, Timing timing)
+      throws ExecutionException {
+    try (Timing t0 = timing.begin("Compute methods to inline")) {
+      ProgramMethodSet monomorphicVirtualMethods =
+          SingleCallerInliner.computeMonomorphicVirtualRootMethods(appView, executorService);
+      ThreadUtils.processItems(
+          appView.appInfo().classes(),
+          clazz ->
+              clazz.forEachProgramMethodMatching(
+                  method -> {
+                    if (!method.hasLirCode() || method.isClassInitializer()) {
+                      return false;
+                    }
+                    if (method.isLibraryMethodOverride().isPossiblyTrue()) {
+                      // We cannot delete this method as the library may dispatch to it.
+                      return false;
+                    }
+                    if (method.isInstanceInitializer()
+                        && !options.canInitNewInstanceUsingSuperclassConstructor()) {
+                      return false;
+                    }
+                    if (method.getAccessFlags().belongsToVirtualPool()
+                        && !monomorphicVirtualMethods.contains(method)) {
+                      return false;
+                    }
+                    return hasAtMostOneInstruction(method.getLirCode());
+                  },
+                  method -> {
+                    KeepMethodInfo keepInfo = appView.getKeepInfo(method);
+                    if (!keepInfo.isClosedWorldReasoningAllowed(options)
+                        || !keepInfo.isOptimizationAllowed(options)
+                        || !keepInfo.isShrinkingAllowed(options)) {
+                      return;
+                    }
+                    IRCode code = method.buildIR(appView, MethodConversionOptions.nonConverting());
+                    ConstraintWithTarget constraint = computeInliningConstraint(code);
+                    if (constraint.isAlways()) {
+                      methodsToInline.add(method);
+                      method.getDefinition().markProcessed(constraint);
+                    }
+                  }),
+          options.getThreadingModule(),
+          executorService);
+    }
+  }
+
+  private boolean hasAtMostOneInstruction(LirCode<?> lirCode) {
+    int count = 0;
+    for (LirInstructionView view : lirCode) {
+      int opcode = view.getOpcode();
+      if (!LirOpcodeUtils.isReturn(opcode)) {
+        count++;
+        if (count > 1) {
+          return false;
+        }
+      }
+    }
+    return true;
+  }
+
+  private ProgramMethodSet computeCallers(ExecutorService executorService, Timing timing)
+      throws ExecutionException {
+    try (Timing t0 = timing.begin("Compute callers")) {
+      DexMethodSignatureSet methodSignaturesOfInterest = DexMethodSignatureSet.create();
+      for (ProgramMethod method : methodsToInline) {
+        methodSignaturesOfInterest.add(method);
+      }
+
+      // Find all methods that call a method that needs to be inlined.
+      ProgramMethodSet callers = ProgramMethodSet.createConcurrent();
+      ThreadUtils.processItems(
+          appView.appInfo().classes(),
+          clazz ->
+              clazz.forEachProgramMethodMatching(
+                  method -> {
+                    if (method.hasLirCode()) {
+                      for (LirConstant constant : method.getCode().asLirCode().getConstantPool()) {
+                        if (constant instanceof DexMethod) {
+                          DexMethod methodConstant = (DexMethod) constant;
+                          if (!methodSignaturesOfInterest.contains(methodConstant)) {
+                            continue;
+                          }
+                          ProgramMethod resolvedMethod =
+                              appView
+                                  .appInfo()
+                                  .unsafeResolveMethodDueToDexFormat(methodConstant)
+                                  .getResolvedProgramMethod();
+                          if (resolvedMethod != null && methodsToInline.contains(resolvedMethod)) {
+                            return true;
+                          }
+                        }
+                      }
+                    }
+                    return false;
+                  },
+                  callers::add),
+          options.getThreadingModule(),
+          executorService);
+      return callers;
+    }
+  }
+
+  private void processCallers(
+      ProgramMethodSet callers, ExecutorService executorService, Timing timing)
+      throws ExecutionException {
+    try (Timing t0 = timing.begin("Process callers")) {
+      MethodProcessor methodProcessor = createMethodProcessor();
+      ThreadUtils.processItems(
+          callers,
+          method -> {
+            IRCode code = method.buildIR(appView);
+            DeadCodeRemover deadCodeRemover = new DeadCodeRemover(appView);
+            Timing threadTiming = Timing.empty();
+            performInlining(
+                method,
+                code,
+                OptimizationFeedbackSimple.getInstance(),
+                methodProcessor,
+                threadTiming,
+                this,
+                this);
+
+            // Only convert IR back to LIR if any methods were inlined.
+            if (needsFinalization.remove(method)) {
+              IRFinalizer<?> finalizer =
+                  code.getConversionOptions().getFinalizer(deadCodeRemover, appView);
+              Code newCode =
+                  finalizer.finalizeCode(code, BytecodeMetadataProvider.empty(), threadTiming);
+              method.setCode(newCode, appView);
+            }
+          },
+          options.getThreadingModule(),
+          executorService);
+      assert needsFinalization.isEmpty();
+    }
+  }
+
+  private void pruneInlinedMethods(ExecutorService executorService, Timing timing)
+      throws ExecutionException {
+    try (Timing t0 = timing.begin("Prune inlined methods")) {
+      PrunedItems.Builder prunedItemsBuilder = PrunedItems.builder().setPrunedApp(appView.app());
+      Map<DexProgramClass, Set<DexEncodedMethod>> methodsToRemoveByClass = new IdentityHashMap<>();
+      SmallMethodInlinerResult result = new SmallMethodInlinerResult();
+      for (ProgramMethod method : methodsToInline) {
+        if (failedToInline.contains(method)) {
+          method.getDefinition().markNotProcessed();
+        } else {
+          prunedItemsBuilder.addRemovedMethod(method.getReference());
+          methodsToRemoveByClass
+              .computeIfAbsent(method.getHolder(), ignoreKey(Sets::newIdentityHashSet))
+              .add(method.getDefinition());
+          if (method.getDefinition().isInstanceInitializer()) {
+            result.classesWithFullyInlinedInstanceInitializers.add(method.getHolderType());
+          }
+        }
+      }
+      // Remove all methods for each class at once to avoid quadratic behavior.
+      methodsToRemoveByClass.forEach(
+          (clazz, methodsToRemove) -> clazz.getMethodCollection().removeMethods(methodsToRemove));
+      appView.pruneItems(prunedItemsBuilder.build(), executorService, timing);
+      appView.setSmallMethodInlinerResult(result);
+    }
+  }
+
+  private MethodProcessor createMethodProcessor() {
+    return OneTimeMethodProcessor.create(
+        ProgramMethodSet.empty(), MethodProcessorEventConsumer.empty(), appView);
+  }
+
+  @Override
+  public Reason computeInliningReason(
+      InvokeMethod invoke,
+      ProgramMethod target,
+      ProgramMethod context,
+      DefaultInliningOracle oracle,
+      InliningIRProvider inliningIRProvider,
+      MethodProcessor methodProcessor,
+      WhyAreYouNotInliningReporter whyAreYouNotInliningReporter) {
+    return methodsToInline.contains(target) ? Reason.ALWAYS : Reason.NEVER;
+  }
+
+  @Override
+  public void forEachInvoke(
+      ProgramMethod method,
+      BasicBlock block,
+      BiConsumer<InvokeMethod, InstructionListIterator> consumer) {
+    InstructionListIterator iterator = block.listIterator();
+    while (iterator.hasNext()) {
+      InvokeMethod invoke = iterator.next().asInvokeMethod();
+      if (invoke == null) {
+        continue;
+      }
+      SingleResolutionResult<?> resolutionResult =
+          invoke.resolveMethod(appView, method).asSingleResolution();
+      if (resolutionResult == null) {
+        continue;
+      }
+      ProgramMethod target =
+          asProgramMethodOrNull(
+              resolutionResult
+                  .lookupDispatchTarget(appView, invoke, method)
+                  .getSingleDispatchTarget());
+      if (target != null && methodsToInline.contains(target)) {
+        consumer.accept(invoke, iterator);
+      } else {
+        ProgramMethod resolvedMethod = resolutionResult.getResolvedProgramMethod();
+        if (resolvedMethod != null && methodsToInline.contains(resolvedMethod)) {
+          failedToInline.add(resolvedMethod);
+        }
+      }
+    }
+  }
+
+  @Override
+  protected void inlineInvoke(
+      InstructionListIterator iterator,
+      IRCode code,
+      IRCode inlinee,
+      ListIterator<BasicBlock> blockIterator,
+      Set<BasicBlock> blocksToRemove,
+      DexProgramClass downcast) {
+    for (FieldInstruction fieldInstruction :
+        inlinee.<FieldInstruction>instructions(Instruction::isFieldInstruction)) {
+      ProgramField resolvedField =
+          fieldInstruction.resolveField(appView, inlinee.context()).getProgramField();
+      if (resolvedField != null) {
+        MutableFieldAccessInfo info =
+            appView
+                .appInfo()
+                .getMutableFieldAccessInfoCollection()
+                .get(resolvedField.getReference());
+        if (info != null) {
+          synchronized (info) {
+            if (fieldInstruction.isFieldGet()) {
+              info.recordRead(fieldInstruction.getField(), code.context());
+            } else {
+              info.recordWrite(fieldInstruction.getField(), code.context());
+            }
+          }
+        }
+      }
+    }
+    super.inlineInvoke(iterator, code, inlinee, blockIterator, blocksToRemove, downcast);
+    needsFinalization.add(code.context());
+  }
+
+  @Override
+  protected void notifyInvokeNotInlined(
+      InvokeMethod invoke, MethodResolutionResult resolutionResult) {
+    if (resolutionResult.isSingleResolution()) {
+      ProgramMethod target = resolutionResult.getResolvedProgramMethod();
+      if (target != null && methodsToInline.contains(target)) {
+        failedToInline.add(target);
+      }
+    }
+  }
+
+  @Override
+  protected boolean shouldApplyInliningToInlinee(
+      AppView<?> appView, ProgramMethod singleTarget, int inliningDepth) {
+    return true;
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/optimize/smallmethodinliner/SmallMethodInlinerResult.java b/src/main/java/com/android/tools/r8/optimize/smallmethodinliner/SmallMethodInlinerResult.java
new file mode 100644
index 0000000..3728aa0
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/optimize/smallmethodinliner/SmallMethodInlinerResult.java
@@ -0,0 +1,39 @@
+// Copyright (c) 2026, 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.optimize.smallmethodinliner;
+
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.lens.GraphLens;
+import com.android.tools.r8.utils.SetUtils;
+import com.google.common.collect.Sets;
+import java.util.Set;
+
+public class SmallMethodInlinerResult {
+
+  public Set<DexType> classesWithFullyInlinedInstanceInitializers;
+
+  public SmallMethodInlinerResult() {
+    this(Sets.newIdentityHashSet());
+  }
+
+  public SmallMethodInlinerResult(Set<DexType> classesWithFullyInlinedInstanceInitializers) {
+    this.classesWithFullyInlinedInstanceInitializers = classesWithFullyInlinedInstanceInitializers;
+  }
+
+  public boolean hasFullyInlinedInstanceInitializers() {
+    return !classesWithFullyInlinedInstanceInitializers.isEmpty();
+  }
+
+  public boolean hasFullyInlinedInstanceInitializers(DexProgramClass clazz) {
+    return classesWithFullyInlinedInstanceInitializers.contains(clazz.getType());
+  }
+
+  public SmallMethodInlinerResult rewrittenWithLens(GraphLens lens, GraphLens appliedLens) {
+    return new SmallMethodInlinerResult(
+        SetUtils.mapIdentityHashSet(
+            classesWithFullyInlinedInstanceInitializers,
+            type -> lens.lookupClassType(type, appliedLens)));
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/apimodel/ApiModelInlineInSameClassTest.java b/src/test/java/com/android/tools/r8/apimodel/ApiModelInlineInSameClassTest.java
index 962f638..f578f7c 100644
--- a/src/test/java/com/android/tools/r8/apimodel/ApiModelInlineInSameClassTest.java
+++ b/src/test/java/com/android/tools/r8/apimodel/ApiModelInlineInSameClassTest.java
@@ -7,16 +7,16 @@
 import static com.android.tools.r8.apimodel.ApiModelingTestHelper.setMockApiLevelForMethod;
 import static com.android.tools.r8.apimodel.ApiModelingTestHelper.verifyThat;
 import static com.android.tools.r8.utils.AndroidApiLevel.L_MR1;
+import static com.android.tools.r8.utils.codeinspector.CodeMatchers.invokesMethod;
+import static com.android.tools.r8.utils.codeinspector.CodeMatchers.invokesMethodWithName;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isAbsent;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
-import static org.hamcrest.CoreMatchers.not;
 import static org.hamcrest.MatcherAssert.assertThat;
 
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.utils.AndroidApiLevel;
-import com.android.tools.r8.utils.codeinspector.ClassSubject;
-import com.android.tools.r8.utils.codeinspector.CodeMatchers;
 import com.android.tools.r8.utils.codeinspector.MethodSubject;
 import java.lang.reflect.Method;
 import org.junit.Test;
@@ -57,15 +57,21 @@
         .compile()
         .inspect(
             inspector -> {
-              // No matter the api level, we should always inline callingApi into notCallingApi.
-              verifyThat(inspector, parameters, notCallingApi).inlinedIntoFromApiLevel(main, L_MR1);
-              assertThat(inspector.method(callingApi), not(isPresent()));
+              MethodSubject callingApiMethod = inspector.method(callingApi);
+              MethodSubject mainMethodSubject = inspector.clazz(Main.class).mainMethod();
               if (parameters.isDexRuntime()
                   && parameters.getApiLevel().isGreaterThanOrEqualTo(L_MR1)) {
-                ClassSubject mainSubject = inspector.clazz(Main.class);
-                MethodSubject mainMethodSubject = mainSubject.uniqueMethodWithOriginalName("main");
-                assertThat(mainMethodSubject, isPresent());
-                assertThat(mainMethodSubject, CodeMatchers.invokesMethodWithName("apiLevel22"));
+                verifyThat(inspector, parameters, notCallingApi)
+                    .inlinedIntoFromApiLevel(main, L_MR1);
+                assertThat(callingApiMethod, isAbsent());
+                assertThat(mainMethodSubject, invokesMethodWithName("apiLevel22"));
+              } else {
+                // Since API outlining happens in the backend, callingApi() is not inlined
+                // notCallingApi(), but notCallingApi() is inlined into main(). As a result, main()
+                // ends up calling callingApi(), which is equally valid.
+                assertThat(callingApiMethod, isPresent());
+                assertThat(callingApiMethod, invokesMethodWithName("apiLevel22"));
+                assertThat(mainMethodSubject, invokesMethod(callingApiMethod));
               }
             })
         .addRunClasspathClasses(Api.class)
diff --git a/src/test/java/com/android/tools/r8/apimodel/ApiModelOutlineInstanceInitializerTest.java b/src/test/java/com/android/tools/r8/apimodel/ApiModelOutlineInstanceInitializerTest.java
index 4846bec..6b1954c 100644
--- a/src/test/java/com/android/tools/r8/apimodel/ApiModelOutlineInstanceInitializerTest.java
+++ b/src/test/java/com/android/tools/r8/apimodel/ApiModelOutlineInstanceInitializerTest.java
@@ -112,13 +112,15 @@
   private void inspect(CodeInspector inspector, boolean isR8) throws Exception {
     Method mainMethod = Main.class.getMethod("main", String[].class);
     verifyThat(inspector, parameters, Argument.class.getDeclaredConstructor(String.class))
-        .isOutlinedFromBetween(mainMethod, AndroidApiLevel.L, classApiLevel);
+        .isOutlinedFromBetween(
+            mainMethod, isR8 ? AndroidApiLevel.M : AndroidApiLevel.L, classApiLevel);
     verifyThat(inspector, parameters, LibraryClass.class.getDeclaredConstructor(Argument.class))
-        .isOutlinedFromBetween(mainMethod, AndroidApiLevel.L, classApiLevel);
+        .isOutlinedFromBetween(
+            mainMethod, isR8 ? AndroidApiLevel.M : AndroidApiLevel.L, classApiLevel);
     // For R8 we inline into the method with an instance initializer when we do not outline it.
     verifyThat(inspector, parameters, LibraryClass.class.getMethod("print"))
         .isOutlinedFromBetween(
-            mainMethod, isR8 ? AndroidApiLevel.L : AndroidApiLevel.B, classApiLevel);
+            mainMethod, isR8 ? AndroidApiLevel.M : AndroidApiLevel.B, classApiLevel);
   }
 
   private void checkOutput(SingleTestRunResult<?> runResult) {
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/fields/FieldInitializedByNonConstantArgumentInSuperConstructorTest.java b/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/fields/FieldInitializedByNonConstantArgumentInSuperConstructorTest.java
index 435cd31..191dc40 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/fields/FieldInitializedByNonConstantArgumentInSuperConstructorTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/fields/FieldInitializedByNonConstantArgumentInSuperConstructorTest.java
@@ -100,6 +100,8 @@
 
     B(int x) {
       super(x);
+      // So that B.<init> is not subject to small method inlining.
+      System.out.print("");
     }
   }
 }
diff --git a/src/test/java/com/android/tools/r8/kotlin/KotlinClassInlinerTest.java b/src/test/java/com/android/tools/r8/kotlin/KotlinClassInlinerTest.java
index 6d0b9c0..ab5db19 100644
--- a/src/test/java/com/android/tools/r8/kotlin/KotlinClassInlinerTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/KotlinClassInlinerTest.java
@@ -274,7 +274,7 @@
                     isAbsent());
                 assertThat(
                     inspector.clazz("class_inliner_lambda_k_style.MainKt$testBigExtraMethod$1"),
-                    isAbsent());
+                    isPresentIf(testParameters.isCfRuntime()));
               }
             });
   }
diff --git a/src/test/java/com/android/tools/r8/shaking/ifrule/ConditionalRuleOnMemberWithKeepInitSimpleInlineTest.java b/src/test/java/com/android/tools/r8/shaking/ifrule/ConditionalRuleOnMemberWithKeepInitSimpleInlineTest.java
index f2300e3..ef76510 100644
--- a/src/test/java/com/android/tools/r8/shaking/ifrule/ConditionalRuleOnMemberWithKeepInitSimpleInlineTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/ifrule/ConditionalRuleOnMemberWithKeepInitSimpleInlineTest.java
@@ -52,13 +52,15 @@
   @Test
   public void testJustStarConditionalKeepClassMembers() throws Exception {
     String keepRule = "-if class * -keepclasseswithmembers class <1> { <init>(); }";
-    testKeepRule(keepRule, ImmutableList.of(), ImmutableList.of(TestClass.class, A.class));
+    // TODO(b/316100042) We should keep A here.
+    testKeepRule(keepRule, ImmutableList.of(A.class), ImmutableList.of(TestClass.class));
   }
 
   @Test
   public void testJustStarConditionalKeepClass() throws Exception {
     String keepRule = "-if class * -keep class <1> { <init>(); }";
-    testKeepRule(keepRule, ImmutableList.of(), ImmutableList.of(TestClass.class, A.class));
+    // TODO(b/316100042) We should keep A here.
+    testKeepRule(keepRule, ImmutableList.of(A.class), ImmutableList.of(TestClass.class));
   }
 
   @Test
diff --git a/src/test/java/com/android/tools/r8/shaking/ifrule/ConditionalRuleOnMemberWithKeepInitTest.java b/src/test/java/com/android/tools/r8/shaking/ifrule/ConditionalRuleOnMemberWithKeepInitTest.java
index 38ff7d5..f239d61 100644
--- a/src/test/java/com/android/tools/r8/shaking/ifrule/ConditionalRuleOnMemberWithKeepInitTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/ifrule/ConditionalRuleOnMemberWithKeepInitTest.java
@@ -52,14 +52,14 @@
   @Test
   public void testJustStarConditionalKeepClassMembers() throws Exception {
     String keepRule = "-if class * -keepclasseswithmembers class <1> { <init>(); }";
-    // TODO(b/316100042) We should keep A here
+    // TODO(b/316100042) We should keep A here.
     testKeepRule(keepRule, ImmutableList.of(A.class), ImmutableList.of(TestClass.class));
   }
 
   @Test
   public void testJustStarConditionalKeepClass() throws Exception {
     String keepRule = "-if class * -keep class <1> { <init>(); }";
-    // TODO(b/316100042) We should keep A here
+    // TODO(b/316100042) We should keep A here.
     testKeepRule(keepRule, ImmutableList.of(A.class), ImmutableList.of(TestClass.class));
   }
 
diff --git a/src/test/java/com/android/tools/r8/startup/SingleCallerBridgeStartupTest.java b/src/test/java/com/android/tools/r8/startup/SingleCallerBridgeStartupTest.java
index 88d5f71..fd33342 100644
--- a/src/test/java/com/android/tools/r8/startup/SingleCallerBridgeStartupTest.java
+++ b/src/test/java/com/android/tools/r8/startup/SingleCallerBridgeStartupTest.java
@@ -7,6 +7,7 @@
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
 import static org.hamcrest.MatcherAssert.assertThat;
 
+import com.android.tools.r8.NeverInline;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
@@ -58,6 +59,8 @@
                 options.inlinerOptions().applyInliningToInlineePredicateForTesting =
                     (appView, inlinee, inliningDepth) ->
                         inlinee.getMethodReference().equals(barMethod))
+        .allowDiagnosticInfoMessages(parameters.canUseNativeMultidex())
+        .enableInliningAnnotations()
         .setMinApi(parameters)
         .compile()
         .inspect(
@@ -85,10 +88,12 @@
       B.foo();
     }
 
+    @NeverInline
     public static void callBarInStartup() {
       bar();
     }
 
+    @NeverInline
     public static void callBarOutsideStartup() {
       bar();
     }
diff --git a/src/test/java11/com/android/tools/r8/jdk11/string/StringConcatTest.java b/src/test/java11/com/android/tools/r8/jdk11/string/StringConcatTest.java
index c3e82b2..3d3dba5 100644
--- a/src/test/java11/com/android/tools/r8/jdk11/string/StringConcatTest.java
+++ b/src/test/java11/com/android/tools/r8/jdk11/string/StringConcatTest.java
@@ -10,6 +10,7 @@
 
 import com.android.tools.r8.Jdk9TestUtils;
 import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.ReprocessMethod;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
@@ -116,6 +117,7 @@
         .addInnerClassesAndStrippedOuter(getClass())
         .addKeepMainRule(Main.class)
         .enableInliningAnnotations()
+        .enableReprocessMethodAnnotations()
         .addDontObfuscate()
         .run(parameters.getRuntime(), Main.class)
         .assertSuccessWithOutput(EXPECTED_OUTPUT)
@@ -403,6 +405,7 @@
     }
 
     @NeverInline
+    @ReprocessMethod
     public static void noOutValues_noSideEffects() {
       try {
         // Values should be removed.