Allow class inlining candidates to escape into a single callee of the callees
Change-Id: I202fd013cf81fe62b20a369921c82c1c91e8481c
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 9bb2ba5..50c0d30 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
@@ -31,6 +31,7 @@
import com.android.tools.r8.ir.code.InstancePut;
import com.android.tools.r8.ir.code.Instruction;
import com.android.tools.r8.ir.code.InstructionOrPhi;
+import com.android.tools.r8.ir.code.Invoke;
import com.android.tools.r8.ir.code.Invoke.Type;
import com.android.tools.r8.ir.code.InvokeDirect;
import com.android.tools.r8.ir.code.InvokeMethod;
@@ -94,6 +95,8 @@
private final Map<InvokeMethodWithReceiver, InliningInfo> methodCallsOnInstance =
new IdentityHashMap<>();
+
+ private final Set<DexEncodedMethod> indirectMethodCallsOnInstance = Sets.newIdentityHashSet();
private final Map<InvokeMethod, InliningInfo> extraMethodCalls
= new IdentityHashMap<>();
private final List<Pair<InvokeMethod, Integer>> unusedArguments
@@ -343,10 +346,7 @@
boolean anyInlinedMethods = forceInlineExtraMethodInvocations(code, inliningIRProvider);
if (anyInlinedMethods) {
// Reset the collections.
- methodCallsOnInstance.clear();
- extraMethodCalls.clear();
- unusedArguments.clear();
- receivers.reset();
+ clear();
// Repeat user analysis
InstructionOrPhi ineligibleUser = areInstanceUsersEligible(defaultOracle);
@@ -360,6 +360,7 @@
}
anyInlinedMethods |= forceInlineDirectMethodInvocations(code, inliningIRProvider);
+ anyInlinedMethods |= forceInlineIndirectMethodInvocations(code, inliningIRProvider);
removeAliasIntroducingInstructionsLinkedToEligibleInstance();
removeMiscUsages(code);
removeFieldReads(code);
@@ -368,6 +369,14 @@
return anyInlinedMethods;
}
+ private void clear() {
+ methodCallsOnInstance.clear();
+ indirectMethodCallsOnInstance.clear();
+ extraMethodCalls.clear();
+ unusedArguments.clear();
+ receivers.reset();
+ }
+
private void replaceUsagesAsUnusedArgument(IRCode code) {
for (Pair<InvokeMethod, Integer> unusedArgument : unusedArguments) {
InvokeMethod invoke = unusedArgument.getFirst();
@@ -453,6 +462,53 @@
return true;
}
+ private boolean forceInlineIndirectMethodInvocations(
+ IRCode code, InliningIRProvider inliningIRProvider) throws IllegalClassInlinerStateException {
+ if (indirectMethodCallsOnInstance.isEmpty()) {
+ return false;
+ }
+
+ Map<InvokeMethodWithReceiver, InliningInfo> methodCallsOnInstance = new IdentityHashMap<>();
+
+ Set<Instruction> currentUsers = eligibleInstance.uniqueUsers();
+ while (!currentUsers.isEmpty()) {
+ Set<Instruction> indirectOutValueUsers = Sets.newIdentityHashSet();
+ for (Instruction instruction : currentUsers) {
+ if (instruction.isAssume() || instruction.isCheckCast()) {
+ indirectOutValueUsers.addAll(instruction.outValue().uniqueUsers());
+ continue;
+ }
+
+ if (instruction.isInvokeMethodWithReceiver()) {
+ InvokeMethodWithReceiver invoke = instruction.asInvokeMethodWithReceiver();
+ DexMethod invokedMethod = invoke.getInvokedMethod();
+ if (invokedMethod == appView.dexItemFactory().objectMethods.constructor) {
+ continue;
+ }
+
+ Value receiver = invoke.getReceiver().getAliasedValue(aliasesThroughAssumeAndCheckCasts);
+ if (receiver != eligibleInstance) {
+ continue;
+ }
+
+ DexEncodedMethod singleTarget =
+ invoke.lookupSingleTarget(appView, code.method.method.holder);
+ if (singleTarget == null || !indirectMethodCallsOnInstance.contains(singleTarget)) {
+ throw new IllegalClassInlinerStateException();
+ }
+
+ methodCallsOnInstance.put(invoke, new InliningInfo(singleTarget, null));
+ }
+ }
+ currentUsers = indirectOutValueUsers;
+ }
+
+ assert !methodCallsOnInstance.isEmpty();
+
+ inliner.performForcedInlining(method, code, methodCallsOnInstance, inliningIRProvider);
+ return true;
+ }
+
private void removeAliasIntroducingInstructionsLinkedToEligibleInstance() {
Set<Instruction> currentUsers = eligibleInstance.uniqueUsers();
while (!currentUsers.isEmpty()) {
@@ -784,15 +840,45 @@
return null;
}
+ ClassInlinerEligibilityInfo eligibility =
+ singleTarget.getOptimizationInfo().getClassInlinerEligibility();
+ if (eligibility.callsReceiver.size() > 1) {
+ return null;
+ }
+ if (!eligibility.callsReceiver.isEmpty()) {
+ assert eligibility.callsReceiver.get(0).getFirst() == Invoke.Type.VIRTUAL;
+ DexMethod indirectlyInvokedMethod = eligibility.callsReceiver.get(0).getSecond();
+ DexEncodedMethod indirectSingleTarget =
+ appView.appInfo().resolveMethod(eligibleClass, indirectlyInvokedMethod).getSingleTarget();
+ if (indirectSingleTarget == null) {
+ return null;
+ }
+ if (!isEligibleIndirectVirtualMethodCall(indirectlyInvokedMethod, indirectSingleTarget)) {
+ return null;
+ }
+ indirectMethodCallsOnInstance.add(indirectSingleTarget);
+ }
+
return new InliningInfo(singleTarget, eligibleClass.type);
}
- private boolean isEligibleIndirectVirtualMethodCall(DexMethod callee) {
+ private boolean isEligibleIndirectVirtualMethodCall(DexMethod invokedMethod) {
DexEncodedMethod singleTarget =
- appView.appInfo().resolveMethod(eligibleClass, callee).getSingleTarget();
- return isEligibleSingleTarget(singleTarget)
- && isEligibleVirtualMethodCall(
- null, callee, singleTarget, eligibility -> eligibility.returnsReceiver.isFalse());
+ appView.appInfo().resolveMethod(eligibleClass, invokedMethod).getSingleTarget();
+ return isEligibleIndirectVirtualMethodCall(invokedMethod, singleTarget);
+ }
+
+ private boolean isEligibleIndirectVirtualMethodCall(
+ DexMethod invokedMethod, DexEncodedMethod singleTarget) {
+ if (!isEligibleSingleTarget(singleTarget)) {
+ return false;
+ }
+ return isEligibleVirtualMethodCall(
+ null,
+ invokedMethod,
+ singleTarget,
+ eligibility ->
+ eligibility.callsReceiver.isEmpty() && eligibility.returnsReceiver.isFalse());
}
private boolean isEligibleVirtualMethodCall(
@@ -820,7 +906,7 @@
MethodOptimizationInfo optimizationInfo = singleTarget.getOptimizationInfo();
ClassInlinerEligibilityInfo eligibility = optimizationInfo.getClassInlinerEligibility();
- if (eligibility == null || !eligibility.callsReceiver.isEmpty()) {
+ if (eligibility == null) {
return false;
}