Verify methods that have been inlined into their single call site are pruned

Bug: 130721661
Change-Id: Ia7cda6e9d4f068705d8ae2984027f37182c00ed0
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java
index e92c6f5..b48ea11 100644
--- a/src/main/java/com/android/tools/r8/R8.java
+++ b/src/main/java/com/android/tools/r8/R8.java
@@ -639,6 +639,9 @@
                         application,
                         CollectionUtils.mergeSets(prunedTypes, pruner.getRemovedClasses())));
 
+            // TODO(b/130721661): Enable this assert.
+            // assert Inliner.verifyNoMethodsInlinedDueToSingleCallSite(appView);
+
             assert appView.verticallyMergedClasses() == null
                 || appView.verticallyMergedClasses().verifyAllSourcesPruned(appViewWithLiveness);
 
diff --git a/src/main/java/com/android/tools/r8/graph/AssemblyWriter.java b/src/main/java/com/android/tools/r8/graph/AssemblyWriter.java
index 23dbfbf..0446c21 100644
--- a/src/main/java/com/android/tools/r8/graph/AssemblyWriter.java
+++ b/src/main/java/com/android/tools/r8/graph/AssemblyWriter.java
@@ -5,7 +5,6 @@
 
 import com.android.tools.r8.ClassFileConsumer;
 import com.android.tools.r8.ir.conversion.IRConverter;
-import com.android.tools.r8.ir.conversion.OptimizationFeedback;
 import com.android.tools.r8.ir.conversion.OptimizationFeedbackIgnore;
 import com.android.tools.r8.naming.ClassNameMapper;
 import com.android.tools.r8.naming.MemberNaming.FieldSignature;
@@ -22,7 +21,6 @@
   private final boolean writeIR;
   private final AppInfoWithSubtyping appInfo;
   private final Timing timing = new Timing("AssemblyWriter");
-  private final OptimizationFeedback ignoreOptimizationFeedback = new OptimizationFeedbackIgnore();
 
   public AssemblyWriter(
       DexApplication application, InternalOptions options, boolean allInfo, boolean writeIR) {
@@ -122,7 +120,7 @@
   private void writeIR(DexEncodedMethod method, PrintStream ps) {
     CfgPrinter printer = new CfgPrinter();
     new IRConverter(appInfo, options, timing, printer)
-        .processMethod(method, ignoreOptimizationFeedback, null, null, null);
+        .processMethod(method, OptimizationFeedbackIgnore.getInstance(), null, null, null);
     ps.println(printer.toString());
   }
 
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 55c1336..add6578 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
@@ -1045,6 +1045,11 @@
     }
 
     @Override
+    public boolean hasBeenInlinedIntoSingleCallSite() {
+      return false;
+    }
+
+    @Override
     public boolean isReachabilitySensitive() {
       return false;
     }
@@ -1150,6 +1155,7 @@
 
   public static class MethodOptimizationInfoImpl implements UpdatableMethodOptimizationInfo {
 
+    private boolean hasBeenInlinedIntoSingleCallSite = false;
     private Set<DexType> initializedClassesOnNormalExit =
         DefaultMethodOptimizationInfoImpl.UNKNOWN_INITIALIZED_CLASSES_ON_NORMAL_EXIT;
     private int returnedArgument = DefaultMethodOptimizationInfoImpl.UNKNOWN_RETURNED_ARGUMENT;
@@ -1259,6 +1265,16 @@
     }
 
     @Override
+    public boolean hasBeenInlinedIntoSingleCallSite() {
+      return hasBeenInlinedIntoSingleCallSite;
+    }
+
+    @Override
+    public void markInlinedIntoSingleCallSite() {
+      hasBeenInlinedIntoSingleCallSite = true;
+    }
+
+    @Override
     public boolean isReachabilitySensitive() {
       return reachabilitySensitive;
     }
diff --git a/src/main/java/com/android/tools/r8/graph/MethodOptimizationInfo.java b/src/main/java/com/android/tools/r8/graph/MethodOptimizationInfo.java
index 96bbf6d..213bb11 100644
--- a/src/main/java/com/android/tools/r8/graph/MethodOptimizationInfo.java
+++ b/src/main/java/com/android/tools/r8/graph/MethodOptimizationInfo.java
@@ -27,6 +27,8 @@
 
   BitSet getNonNullParamOnNormalExits();
 
+  boolean hasBeenInlinedIntoSingleCallSite();
+
   boolean isReachabilitySensitive();
 
   boolean returnsArgument();
diff --git a/src/main/java/com/android/tools/r8/graph/UpdatableMethodOptimizationInfo.java b/src/main/java/com/android/tools/r8/graph/UpdatableMethodOptimizationInfo.java
index 1aa2129..5791733 100644
--- a/src/main/java/com/android/tools/r8/graph/UpdatableMethodOptimizationInfo.java
+++ b/src/main/java/com/android/tools/r8/graph/UpdatableMethodOptimizationInfo.java
@@ -14,6 +14,8 @@
 
   void markInitializesClassesOnNormalExit(Set<DexType> initializedClasses);
 
+  void markInlinedIntoSingleCallSite();
+
   void markReturnsArgument(int argument);
 
   void markReturnsConstantNumber(long value);
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 d802627..0ec8d2f 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
@@ -148,7 +148,6 @@
 
   private final OptimizationFeedbackDelayed delayedOptimizationFeedback =
       new OptimizationFeedbackDelayed();
-  private final OptimizationFeedback ignoreOptimizationFeedback = new OptimizationFeedbackIgnore();
   private final OptimizationFeedback simpleOptimizationFeedback = new OptimizationFeedbackSimple();
   private DexString highestSortingString;
 
@@ -598,7 +597,7 @@
             (code, method) -> {
               outliner.applyOutliningCandidate(code, method);
               printMethod(code, "IR after outlining (SSA)", null);
-              finalizeIR(method, code, ignoreOptimizationFeedback);
+              finalizeIR(method, code, OptimizationFeedbackIgnore.getInstance());
             });
         assert outliner.checkAllOutlineSitesFoundAgain();
         builder.addSynthesizedClass(outlineClass, true);
@@ -962,7 +961,7 @@
     previous = printMethod(code, "IR after null tracking (SSA)", previous);
 
     if (!isDebugMode && options.enableInlining && inliner != null) {
-      inliner.performInlining(method, code, isProcessedConcurrently, callSiteInformation);
+      inliner.performInlining(method, code, feedback, isProcessedConcurrently, callSiteInformation);
     }
 
     previous = printMethod(code, "IR after inlining (SSA)", previous);
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/OptimizationFeedback.java b/src/main/java/com/android/tools/r8/ir/conversion/OptimizationFeedback.java
index 488c06b..bf7e94f 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/OptimizationFeedback.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/OptimizationFeedback.java
@@ -17,6 +17,8 @@
 
 public interface OptimizationFeedback {
 
+  void markInlinedIntoSingleCallSite(DexEncodedMethod method);
+
   void methodInitializesClassesOnNormalExit(
       DexEncodedMethod method, Set<DexType> initializedClasses);
 
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/OptimizationFeedbackDelayed.java b/src/main/java/com/android/tools/r8/ir/conversion/OptimizationFeedbackDelayed.java
index 874977b..4ca780d 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/OptimizationFeedbackDelayed.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/OptimizationFeedbackDelayed.java
@@ -38,6 +38,11 @@
   }
 
   @Override
+  public synchronized void markInlinedIntoSingleCallSite(DexEncodedMethod method) {
+    getOptimizationInfoForUpdating(method).markInlinedIntoSingleCallSite();
+  }
+
+  @Override
   public synchronized void methodInitializesClassesOnNormalExit(
       DexEncodedMethod method, Set<DexType> initializedClasses) {
     getOptimizationInfoForUpdating(method).markInitializesClassesOnNormalExit(initializedClasses);
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/OptimizationFeedbackIgnore.java b/src/main/java/com/android/tools/r8/ir/conversion/OptimizationFeedbackIgnore.java
index 0946ca3..7eb3549 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/OptimizationFeedbackIgnore.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/OptimizationFeedbackIgnore.java
@@ -17,6 +17,17 @@
 
 public class OptimizationFeedbackIgnore implements OptimizationFeedback {
 
+  private static final OptimizationFeedbackIgnore INSTANCE = new OptimizationFeedbackIgnore();
+
+  private OptimizationFeedbackIgnore() {}
+
+  public static OptimizationFeedbackIgnore getInstance() {
+    return INSTANCE;
+  }
+
+  @Override
+  public void markInlinedIntoSingleCallSite(DexEncodedMethod method) {}
+
   @Override
   public void methodInitializesClassesOnNormalExit(
       DexEncodedMethod method, Set<DexType> initializedClasses) {}
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/OptimizationFeedbackSimple.java b/src/main/java/com/android/tools/r8/ir/conversion/OptimizationFeedbackSimple.java
index 313148f..44639e3 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/OptimizationFeedbackSimple.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/OptimizationFeedbackSimple.java
@@ -18,6 +18,11 @@
 public class OptimizationFeedbackSimple implements OptimizationFeedback {
 
   @Override
+  public synchronized void markInlinedIntoSingleCallSite(DexEncodedMethod method) {
+    // Ignored.
+  }
+
+  @Override
   public void methodInitializesClassesOnNormalExit(
       DexEncodedMethod method, Set<DexType> initializedClasses) {
     // Ignored.
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 4cf7120..e22e6fa 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
@@ -18,6 +18,7 @@
 import com.android.tools.r8.ir.code.InvokeStatic;
 import com.android.tools.r8.ir.code.Value;
 import com.android.tools.r8.ir.conversion.CallSiteInformation;
+import com.android.tools.r8.ir.conversion.OptimizationFeedback;
 import com.android.tools.r8.ir.optimize.Inliner.InlineAction;
 import com.android.tools.r8.ir.optimize.Inliner.InlineeWithReason;
 import com.android.tools.r8.ir.optimize.Inliner.Reason;
@@ -384,13 +385,14 @@
   }
 
   @Override
-  public void ensureMethodProcessed(DexEncodedMethod target, IRCode inlinee) {
+  public void ensureMethodProcessed(
+      DexEncodedMethod target, IRCode inlinee, OptimizationFeedback feedback) {
     if (!target.isProcessed()) {
       if (Log.ENABLED) {
         Log.verbose(getClass(), "Forcing extra inline on " + target.toSourceString());
       }
       inliner.performInlining(
-          target, inlinee, isProcessedConcurrently, callSiteInformation);
+          target, inlinee, feedback, isProcessedConcurrently, callSiteInformation);
     }
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/ForcedInliningOracle.java b/src/main/java/com/android/tools/r8/ir/optimize/ForcedInliningOracle.java
index dd1f2fe..90d0ac9 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/ForcedInliningOracle.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/ForcedInliningOracle.java
@@ -14,6 +14,7 @@
 import com.android.tools.r8.ir.code.InvokeMethodWithReceiver;
 import com.android.tools.r8.ir.code.InvokePolymorphic;
 import com.android.tools.r8.ir.code.InvokeStatic;
+import com.android.tools.r8.ir.conversion.OptimizationFeedback;
 import com.android.tools.r8.ir.optimize.Inliner.InlineAction;
 import com.android.tools.r8.ir.optimize.Inliner.InlineeWithReason;
 import com.android.tools.r8.ir.optimize.Inliner.Reason;
@@ -69,7 +70,8 @@
   }
 
   @Override
-  public void ensureMethodProcessed(DexEncodedMethod target, IRCode inlinee) {
+  public void ensureMethodProcessed(
+      DexEncodedMethod target, IRCode inlinee, OptimizationFeedback feedback) {
     // Do nothing. If the method is not yet processed, we still should
     // be able to build IR for inlining, though.
   }
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 dfa9dd0..1aca825 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
@@ -13,6 +13,7 @@
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.GraphLense;
 import com.android.tools.r8.graph.NestMemberClassAttribute;
@@ -34,6 +35,7 @@
 import com.android.tools.r8.ir.conversion.IRConverter;
 import com.android.tools.r8.ir.conversion.LensCodeRewriter;
 import com.android.tools.r8.ir.conversion.OptimizationFeedback;
+import com.android.tools.r8.ir.conversion.OptimizationFeedbackIgnore;
 import com.android.tools.r8.ir.desugar.TwrCloseResourceRewriter;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
@@ -745,12 +747,13 @@
       Map<InvokeMethod, InliningInfo> invokesToInline) {
 
     ForcedInliningOracle oracle = new ForcedInliningOracle(method, invokesToInline);
-    performInliningImpl(oracle, oracle, method, code);
+    performInliningImpl(oracle, oracle, method, code, OptimizationFeedbackIgnore.getInstance());
   }
 
   public void performInlining(
       DexEncodedMethod method,
       IRCode code,
+      OptimizationFeedback feedback,
       Predicate<DexEncodedMethod> isProcessedConcurrently,
       CallSiteInformation callSiteInformation) {
     InternalOptions options = appView.options();
@@ -762,7 +765,7 @@
             callSiteInformation,
             options.inliningInstructionLimit,
             options.inliningInstructionAllowance - numberOfInstructions(code));
-    performInliningImpl(oracle, oracle, method, code);
+    performInliningImpl(oracle, oracle, method, code, feedback);
   }
 
   public DefaultInliningOracle createDefaultOracle(
@@ -784,7 +787,11 @@
   }
 
   private void performInliningImpl(
-      InliningStrategy strategy, InliningOracle oracle, DexEncodedMethod context, IRCode code) {
+      InliningStrategy strategy,
+      InliningOracle oracle,
+      DexEncodedMethod context,
+      IRCode code,
+      OptimizationFeedback feedback) {
     AssumeDynamicTypeRemover assumeDynamicTypeRemover = new AssumeDynamicTypeRemover(appView, code);
     List<BasicBlock> blocksToRemove = new ArrayList<>();
     ListIterator<BasicBlock> blockIterator = code.listIterator();
@@ -826,7 +833,7 @@
 
               // If this code did not go through the full pipeline, apply inlining to make sure
               // that force inline targets get processed.
-              strategy.ensureMethodProcessed(target, inlinee.code);
+              strategy.ensureMethodProcessed(target, inlinee.code, feedback);
 
               // Make sure constructor inlining is legal.
               assert !target.isClassInitializer();
@@ -852,6 +859,10 @@
                   blocksToRemove,
                   getDowncastTypeIfNeeded(strategy, invoke, target));
 
+              if (inlinee.reason == Reason.SINGLE_CALLER) {
+                feedback.markInlinedIntoSingleCallSite(target);
+              }
+
               classInitializationAnalysis.notifyCodeHasChanged();
               strategy.updateTypeInformationIfNeeded(inlinee.code, blockIterator, block);
 
@@ -910,4 +921,13 @@
     }
     return null;
   }
+
+  public static boolean verifyNoMethodsInlinedDueToSingleCallSite(AppView<?> appView) {
+    for (DexProgramClass clazz : appView.appInfo().classes()) {
+      for (DexEncodedMethod method : clazz.methods()) {
+        assert !method.getOptimizationInfo().hasBeenInlinedIntoSingleCallSite();
+      }
+    }
+    return true;
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/InliningStrategy.java b/src/main/java/com/android/tools/r8/ir/optimize/InliningStrategy.java
index feba578..678cbc3 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/InliningStrategy.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/InliningStrategy.java
@@ -10,6 +10,7 @@
 import com.android.tools.r8.ir.code.BasicBlock;
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.InvokeMethod;
+import com.android.tools.r8.ir.conversion.OptimizationFeedback;
 import com.android.tools.r8.ir.optimize.Inliner.InlineeWithReason;
 import java.util.ListIterator;
 
@@ -28,7 +29,8 @@
   /** Inform the strategy that the inlinee has been inlined. */
   void markInlined(InlineeWithReason inlinee);
 
-  void ensureMethodProcessed(DexEncodedMethod target, IRCode inlinee);
+  void ensureMethodProcessed(
+      DexEncodedMethod target, IRCode inlinee, OptimizationFeedback feedback);
 
   boolean isValidTarget(
       InvokeMethod invoke, DexEncodedMethod target, IRCode inlinee, ClassHierarchy hierarchy);