Introduce InliningIRProvider to enable the inliner to reuse previously built IR

In order to more precisely estimate the cost of class inlining, the class inliner will be extended to build IR for the methods that it plans to inline. To avoid rebuilding the IR for these methods in case they need to be inlined, these will be cached by the class inliner so that the inliner can reuse the previously built IR.

Change-Id: Iefe41a4a84cfa3ac6c50bedb4cd5c356eb4b6d5b
diff --git a/src/main/java/com/android/tools/r8/ir/code/Position.java b/src/main/java/com/android/tools/r8/ir/code/Position.java
index 51196ea..2b05e0c 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Position.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Position.java
@@ -3,6 +3,8 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.ir.code;
 
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexString;
 import com.google.common.annotations.VisibleForTesting;
@@ -76,6 +78,19 @@
     return new Position(-1, null, method, callerPosition, false);
   }
 
+  public static Position getPositionForInlining(
+      AppView<?> appView, InvokeMethod invoke, DexEncodedMethod context) {
+    Position position = invoke.getPosition();
+    if (position.method == null) {
+      assert position.isNone();
+      position = Position.noneWithMethod(context.method, null);
+    }
+    assert position.callerPosition == null
+        || position.getOutermostCaller().method
+            == appView.graphLense().getOriginalMethodSignature(context.method);
+    return position;
+  }
+
   public boolean isNone() {
     return line == -1;
   }
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 1ab2e28..68b9319 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
@@ -37,7 +37,6 @@
 import com.android.tools.r8.ir.code.Position;
 import com.android.tools.r8.ir.code.Throw;
 import com.android.tools.r8.ir.code.Value;
-import com.android.tools.r8.ir.code.ValueNumberGenerator;
 import com.android.tools.r8.ir.conversion.CodeOptimization;
 import com.android.tools.r8.ir.conversion.LensCodeRewriter;
 import com.android.tools.r8.ir.conversion.MethodProcessor;
@@ -45,11 +44,11 @@
 import com.android.tools.r8.ir.desugar.TwrCloseResourceRewriter;
 import com.android.tools.r8.ir.optimize.info.OptimizationFeedback;
 import com.android.tools.r8.ir.optimize.info.OptimizationFeedbackIgnore;
+import com.android.tools.r8.ir.optimize.inliner.InliningIRProvider;
 import com.android.tools.r8.ir.optimize.inliner.NopWhyAreYouNotInliningReporter;
 import com.android.tools.r8.ir.optimize.inliner.WhyAreYouNotInliningReporter;
 import com.android.tools.r8.ir.optimize.lambda.LambdaMerger;
 import com.android.tools.r8.kotlin.Kotlin;
-import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.shaking.MainDexClasses;
 import com.android.tools.r8.utils.InternalOptions;
@@ -571,18 +570,17 @@
     }
 
     InlineeWithReason buildInliningIR(
-        DexEncodedMethod context,
-        ValueNumberGenerator generator,
         AppView<? extends AppInfoWithSubtyping> appView,
-        Position callerPosition,
+        InvokeMethod invoke,
+        DexEncodedMethod context,
+        InliningIRProvider inliningIRProvider,
         LambdaMerger lambdaMerger,
         LensCodeRewriter lensCodeRewriter) {
       DexItemFactory dexItemFactory = appView.dexItemFactory();
       InternalOptions options = appView.options();
-      Origin origin = appView.appInfo().originFor(target.method.holder);
 
       // Build the IR for a yet not processed method, and perform minimal IR processing.
-      IRCode code = target.buildInliningIR(context, appView, generator, callerPosition, origin);
+      IRCode code = inliningIRProvider.getInliningIR(invoke, target);
 
       // Insert a null check if this is needed to preserve the implicit null check for the receiver.
       // This is only needed if we do not also insert a monitor-enter instruction, since that will
@@ -811,10 +809,11 @@
   public void performForcedInlining(
       DexEncodedMethod method,
       IRCode code,
-      Map<? extends InvokeMethod, InliningInfo> invokesToInline) {
-
+      Map<? extends InvokeMethod, InliningInfo> invokesToInline,
+      InliningIRProvider inliningIRProvider) {
     ForcedInliningOracle oracle = new ForcedInliningOracle(appView, method, invokesToInline);
-    performInliningImpl(oracle, oracle, method, code, OptimizationFeedbackIgnore.getInstance());
+    performInliningImpl(
+        oracle, oracle, method, code, OptimizationFeedbackIgnore.getInstance(), inliningIRProvider);
   }
 
   public void performInlining(
@@ -830,7 +829,9 @@
             methodProcessor,
             options.inliningInstructionLimit,
             options.inliningInstructionAllowance - numberOfInstructions(code));
-    performInliningImpl(oracle, oracle, method, code, feedback);
+    InliningIRProvider inliningIRProvider = new InliningIRProvider(appView, method, code);
+    assert inliningIRProvider.verifyIRCacheIsEmpty();
+    performInliningImpl(oracle, oracle, method, code, feedback, inliningIRProvider);
   }
 
   public DefaultInliningOracle createDefaultOracle(
@@ -854,7 +855,8 @@
       InliningOracle oracle,
       DexEncodedMethod context,
       IRCode code,
-      OptimizationFeedback feedback) {
+      OptimizationFeedback feedback,
+      InliningIRProvider inliningIRProvider) {
     AssumeDynamicTypeRemover assumeDynamicTypeRemover = new AssumeDynamicTypeRemover(appView, code);
     Set<BasicBlock> blocksToRemove = Sets.newIdentityHashSet();
     ListIterator<BasicBlock> blockIterator = code.listIterator();
@@ -908,12 +910,7 @@
 
           InlineeWithReason inlinee =
               action.buildInliningIR(
-                  context,
-                  code.valueNumberGenerator,
-                  appView,
-                  getPositionForInlining(invoke, context),
-                  lambdaMerger,
-                  lensCodeRewriter);
+                  appView, invoke, context, inliningIRProvider, lambdaMerger, lensCodeRewriter);
           if (strategy.willExceedBudget(
               code, invoke, inlinee, block, whyAreYouNotInliningReporter)) {
             assert whyAreYouNotInliningReporter.unsetReasonHasBeenReportedFlag();
@@ -995,18 +992,6 @@
     assert code.isConsistentSSA();
   }
 
-  private Position getPositionForInlining(InvokeMethod invoke, DexEncodedMethod context) {
-    Position position = invoke.getPosition();
-    if (position.method == null) {
-      assert position.isNone();
-      position = Position.noneWithMethod(context.method, null);
-    }
-    assert position.callerPosition == null
-        || position.getOutermostCaller().method
-            == appView.graphLense().getOriginalMethodSignature(context.method);
-    return position;
-  }
-
   private boolean useReflectiveOperationExceptionOrUnknownClassInCatch(IRCode code) {
     for (BasicBlock block : code.blocks) {
       for (CatchHandler<BasicBlock> catchHandler : block.getCatchHandlers()) {
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 c4d7051..2945c6e 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
@@ -17,6 +17,7 @@
 import com.android.tools.r8.ir.optimize.CodeRewriter;
 import com.android.tools.r8.ir.optimize.Inliner;
 import com.android.tools.r8.ir.optimize.InliningOracle;
+import com.android.tools.r8.ir.optimize.inliner.InliningIRProvider;
 import com.android.tools.r8.ir.optimize.string.StringOptimizer;
 import com.android.tools.r8.logging.Log;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
@@ -226,7 +227,8 @@
         }
 
         // Inline the class instance.
-        anyInlinedMethods |= processor.processInlining(code, defaultOracle);
+        InliningIRProvider inliningIRProvider = new InliningIRProvider(appView, method, code);
+        anyInlinedMethods |= processor.processInlining(code, defaultOracle, inliningIRProvider);
 
         // Restore normality.
         Set<Value> affectedValues = Sets.newIdentityHashSet();
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java
index 3e11ca6..23d2044 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java
@@ -39,6 +39,7 @@
 import com.android.tools.r8.ir.optimize.info.ParameterUsagesInfo.ParameterUsage;
 import com.android.tools.r8.ir.optimize.info.initializer.ClassInitializerInfo;
 import com.android.tools.r8.ir.optimize.info.initializer.InstanceInitializerInfo;
+import com.android.tools.r8.ir.optimize.inliner.InliningIRProvider;
 import com.android.tools.r8.ir.optimize.inliner.NopWhyAreYouNotInliningReporter;
 import com.android.tools.r8.kotlin.KotlinInfo;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
@@ -378,12 +379,13 @@
   //  * remove root instruction
   //
   // Returns `true` if at least one method was inlined.
-  boolean processInlining(IRCode code, Supplier<InliningOracle> defaultOracle) {
+  boolean processInlining(
+      IRCode code, Supplier<InliningOracle> defaultOracle, InliningIRProvider inliningIRProvider) {
     // Verify that `eligibleInstance` is not aliased.
     assert eligibleInstance == eligibleInstance.getAliasedValue();
     replaceUsagesAsUnusedArgument(code);
 
-    boolean anyInlinedMethods = forceInlineExtraMethodInvocations(code);
+    boolean anyInlinedMethods = forceInlineExtraMethodInvocations(code, inliningIRProvider);
     if (anyInlinedMethods) {
       // Reset the collections.
       methodCallsOnInstance.clear();
@@ -409,7 +411,7 @@
           : "Remaining unused arg: " + StringUtils.join(unusedArguments, ", ");
     }
 
-    anyInlinedMethods |= forceInlineDirectMethodInvocations(code);
+    anyInlinedMethods |= forceInlineDirectMethodInvocations(code, inliningIRProvider);
     removeAssumeInstructionsLinkedToEligibleInstance();
     removeMiscUsages(code);
     removeFieldReads(code);
@@ -434,23 +436,25 @@
     unusedArguments.clear();
   }
 
-  private boolean forceInlineExtraMethodInvocations(IRCode code) {
+  private boolean forceInlineExtraMethodInvocations(
+      IRCode code, InliningIRProvider inliningIRProvider) {
     if (extraMethodCalls.isEmpty()) {
       return false;
     }
     // Inline extra methods.
-    inliner.performForcedInlining(method, code, extraMethodCalls);
+    inliner.performForcedInlining(method, code, extraMethodCalls, inliningIRProvider);
     return true;
   }
 
-  private boolean forceInlineDirectMethodInvocations(IRCode code) {
+  private boolean forceInlineDirectMethodInvocations(
+      IRCode code, InliningIRProvider inliningIRProvider) {
     if (methodCallsOnInstance.isEmpty()) {
       return false;
     }
     assert methodCallsOnInstance.keySet().stream()
         .map(InvokeMethodWithReceiver::getReceiver)
         .allMatch(receivers::contains);
-    inliner.performForcedInlining(method, code, methodCallsOnInstance);
+    inliner.performForcedInlining(method, code, methodCallsOnInstance, inliningIRProvider);
     return true;
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/inliner/InliningIRProvider.java b/src/main/java/com/android/tools/r8/ir/optimize/inliner/InliningIRProvider.java
new file mode 100644
index 0000000..580e1b2
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/optimize/inliner/InliningIRProvider.java
@@ -0,0 +1,50 @@
+// Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.optimize.inliner;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.ir.code.IRCode;
+import com.android.tools.r8.ir.code.InvokeMethod;
+import com.android.tools.r8.ir.code.Position;
+import com.android.tools.r8.ir.code.ValueNumberGenerator;
+import com.android.tools.r8.origin.Origin;
+import java.util.IdentityHashMap;
+import java.util.Map;
+
+public class InliningIRProvider {
+
+  private final AppView<?> appView;
+  private final DexEncodedMethod context;
+  private final ValueNumberGenerator valueNumberGenerator;
+
+  private final Map<InvokeMethod, IRCode> cache = new IdentityHashMap<>();
+
+  public InliningIRProvider(AppView<?> appView, DexEncodedMethod context, IRCode code) {
+    this.appView = appView;
+    this.context = context;
+    this.valueNumberGenerator = code.valueNumberGenerator;
+  }
+
+  public IRCode getInliningIR(InvokeMethod invoke, DexEncodedMethod method) {
+    IRCode cached = cache.remove(invoke);
+    if (cached != null) {
+      return cached;
+    }
+    Position position = Position.getPositionForInlining(appView, invoke, context);
+    Origin origin = appView.appInfo().originFor(method.method.holder);
+    return method.buildInliningIR(context, appView, valueNumberGenerator, position, origin);
+  }
+
+  public void cacheInliningIR(InvokeMethod invoke, IRCode code) {
+    IRCode existing = cache.put(invoke, code);
+    assert existing == null;
+  }
+
+  public boolean verifyIRCacheIsEmpty() {
+    assert cache.isEmpty();
+    return true;
+  }
+}