Fix is-receiver checks in class inliner
Prior to this CL, the class inliner would sometimes fail to recognize that a value is an alias of the receiver, when the value is an alias returned by another method.
Change-Id: Iee2181e36379e8a85b4c9eff2471546c8dbb8856
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 47930c2..56ac89d 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
@@ -1094,9 +1094,14 @@
public static class ClassInlinerEligibility {
- public final boolean returnsReceiver;
+ /**
+ * Set to {@link OptionalBool#TRUE} if the method is guaranteed to return the receiver, {@link
+ * OptionalBool#FALSE} if the method is guaranteed not to return the receiver, and {@link
+ * OptionalBool#UNKNOWN} if the method may return the receiver.
+ */
+ public final OptionalBool returnsReceiver;
- public ClassInlinerEligibility(boolean returnsReceiver) {
+ public ClassInlinerEligibility(OptionalBool returnsReceiver) {
this.returnsReceiver = returnsReceiver;
}
}
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 2945c6e..b3a13b5 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
@@ -220,6 +220,10 @@
continue;
}
+ assert Sets.intersection(
+ processor.getMaybeReceiverAliases(), processor.getDefiniteReceiverAliases())
+ .isEmpty();
+
// Is inlining allowed.
if (processor.getEstimatedCombinedSizeForInlining()
>= appView.options().classInliningInstructionAllowance) {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerReceiverAnalysis.java b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerReceiverAnalysis.java
new file mode 100644
index 0000000..6143a15
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerReceiverAnalysis.java
@@ -0,0 +1,135 @@
+// 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.classinliner;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import com.android.tools.r8.ir.code.BasicBlock;
+import com.android.tools.r8.ir.code.IRCode;
+import com.android.tools.r8.ir.code.Instruction;
+import com.android.tools.r8.ir.code.InvokeMethod;
+import com.android.tools.r8.ir.code.Value;
+import com.android.tools.r8.utils.BooleanLatticeElement;
+import com.android.tools.r8.utils.OptionalBool;
+import java.util.IdentityHashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * This analysis determines whether a method returns the receiver. The analysis is specific to the
+ * class inliner, and the result is therefore not sound in general.
+ *
+ * <p>The analysis makes the following assumptions.
+ *
+ * <ul>
+ * <li>None of the given method's arguments is an alias of the receiver (except for the receiver
+ * itself).
+ * <li>The receiver is not stored in the heap. Thus, it is guaranteed that (i) all field-get
+ * instructions do not return an alias of the receiver, and (ii) invoke instructions can only
+ * return an alias of the receiver if the receiver is given as an argument.
+ * </ul>
+ */
+public class ClassInlinerReceiverAnalysis {
+
+ private final AppView<?> appView;
+ private final DexEncodedMethod method;
+ private final IRCode code;
+ private final Value receiver;
+
+ private final Map<Value, OptionalBool> isReceiverAliasCache = new IdentityHashMap<>();
+
+ public ClassInlinerReceiverAnalysis(AppView<?> appView, DexEncodedMethod method, IRCode code) {
+ this.appView = appView;
+ this.method = method;
+ this.code = code;
+ this.receiver = code.getThis();
+ assert !receiver.hasAliasedValue();
+ }
+
+ public OptionalBool computeReturnsReceiver() {
+ if (method.method.proto.returnType.isVoidType()) {
+ return OptionalBool.FALSE;
+ }
+
+ List<BasicBlock> normalExitBlocks = code.computeNormalExitBlocks();
+ if (normalExitBlocks.isEmpty()) {
+ return OptionalBool.FALSE;
+ }
+
+ BooleanLatticeElement result = OptionalBool.BOTTOM;
+ for (BasicBlock block : normalExitBlocks) {
+ Value returnValue = block.exit().asReturn().returnValue();
+ result = result.join(getOrComputeIsReceiverAlias(returnValue));
+
+ // Stop as soon as we reach unknown.
+ if (result.isUnknown()) {
+ return OptionalBool.UNKNOWN;
+ }
+ }
+ assert !result.isBottom();
+ return result.asOptionalBool();
+ }
+
+ private OptionalBool getOrComputeIsReceiverAlias(Value value) {
+ Value root = value.getAliasedValue();
+ return isReceiverAliasCache.computeIfAbsent(root, this::computeIsReceiverAlias);
+ }
+
+ private OptionalBool computeIsReceiverAlias(Value value) {
+ assert !value.hasAliasedValue();
+
+ if (value == receiver) {
+ // Guaranteed to return the receiver.
+ return OptionalBool.TRUE;
+ }
+
+ TypeLatticeElement receiverType = receiver.getTypeLattice();
+ TypeLatticeElement valueType = value.getTypeLattice();
+ if (!valueType.isValueTypeCompatible(receiverType)) {
+ return OptionalBool.FALSE;
+ }
+
+ if (!receiverType.lessThanOrEqualUpToNullability(valueType, appView)
+ && !valueType.lessThanOrEqualUpToNullability(receiverType, appView)) {
+ // Guaranteed not to return the receiver.
+ return OptionalBool.FALSE;
+ }
+
+ if (value.isPhi()) {
+ // Not sure what is returned.
+ return OptionalBool.UNKNOWN;
+ }
+
+ Instruction definition = value.definition;
+ if (definition.isArrayGet() || definition.isFieldGet()) {
+ // Guaranteed not to return the receiver, since the class inliner does not allow the
+ // receiver to flow into any arrays or fields.
+ return OptionalBool.FALSE;
+ }
+
+ if (definition.isConstInstruction() || definition.isCreatingInstanceOrArray()) {
+ // Guaranteed not to return the receiver.
+ return OptionalBool.FALSE;
+ }
+
+ if (definition.isInvokeMethod()) {
+ // Since the class inliner does not allow the receiver to flow into the heap, the only way for
+ // the invoked method to return the receiver is if one of the given arguments is an alias of
+ // the receiver.
+ InvokeMethod invoke = definition.asInvokeMethod();
+ for (Value argument : invoke.arguments()) {
+ if (getOrComputeIsReceiverAlias(argument).isPossiblyTrue()) {
+ return OptionalBool.UNKNOWN;
+ }
+ }
+
+ return OptionalBool.FALSE;
+ }
+
+ // Not sure what is returned.
+ return OptionalBool.UNKNOWN;
+ }
+}
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 096699b..abcfb51 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
@@ -43,7 +43,6 @@
import com.android.tools.r8.ir.optimize.inliner.NopWhyAreYouNotInliningReporter;
import com.android.tools.r8.kotlin.KotlinInfo;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
-import com.android.tools.r8.utils.ListUtils;
import com.android.tools.r8.utils.Pair;
import com.android.tools.r8.utils.SetUtils;
import com.android.tools.r8.utils.StringUtils;
@@ -58,11 +57,18 @@
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
+import java.util.function.BooleanSupplier;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.function.Supplier;
final class InlineCandidateProcessor {
+
+ enum AliasKind {
+ DEFINITE,
+ MAYBE
+ }
+
private static final ImmutableSet<If.Type> ALLOWED_ZERO_TEST_TYPES =
ImmutableSet.of(If.Type.EQ, If.Type.NE);
@@ -88,10 +94,17 @@
private int estimatedCombinedSizeForInlining = 0;
- // Set of values that may be an alias of the "root" instance (including the root instance itself).
- // TODO(b/144825216): Distinguish the "may-aliases" from the "must-aliases" such that the cost
- // analysis is not optimistic.
- private final Set<Value> receivers;
+ // Sets of values that must/may be an alias of the "root" instance (including the root instance
+ // itself).
+ private final Set<Value> definiteReceiverAliases;
+ private final Set<Value> maybeReceiverAliases = Sets.newIdentityHashSet();
+
+ // Set of values that are not allowed to become an alias of the receiver.
+ private final Set<Value> illegalReceiverAliases = Sets.newIdentityHashSet();
+
+ // Set of values that are allowed to become an alias of the receiver under certain circumstances.
+ private final Map<Value, List<BooleanSupplier>> deferredAliasValidityChecks =
+ new IdentityHashMap<>();
InlineCandidateProcessor(
AppView<AppInfoWithLiveness> appView,
@@ -108,15 +121,90 @@
this.method = method;
this.root = root;
this.isProcessedConcurrently = isProcessedConcurrently;
- this.receivers = SetUtils.newIdentityHashSet(root.outValue());
+ this.definiteReceiverAliases = SetUtils.newIdentityHashSet(root.outValue());
}
int getEstimatedCombinedSizeForInlining() {
return estimatedCombinedSizeForInlining;
}
- Set<Value> getReceivers() {
- return receivers;
+ Set<Value> getDefiniteReceiverAliases() {
+ return definiteReceiverAliases;
+ }
+
+ Set<Value> getMaybeReceiverAliases() {
+ return maybeReceiverAliases;
+ }
+
+ private boolean addReceiverAlias(Value alias, AliasKind kind) {
+ if (isIllegalReceiverAlias(alias)) {
+ return false; // Not allowed.
+ }
+ // All checks passed.
+ deferredAliasValidityChecks.remove(alias);
+ boolean changed;
+ if (kind == AliasKind.DEFINITE) {
+ assert !maybeReceiverAliases.contains(alias);
+ changed = definiteReceiverAliases.add(alias);
+ } else {
+ assert !definiteReceiverAliases.contains(alias);
+ changed = maybeReceiverAliases.add(alias);
+ }
+ // Verify that the state changed. Otherwise, we are analyzing the same instruction more than
+ // once.
+ assert changed : alias.toString() + " already added as an alias";
+ return true;
+ }
+
+ private boolean addIllegalReceiverAlias(Value value) {
+ if (isReceiverAlias(value)) {
+ return false;
+ }
+ illegalReceiverAliases.add(value);
+ // Since `value` is never allowed as a receiver, there is no need to keep the validity checks
+ // around.
+ deferredAliasValidityChecks.remove(value);
+ return true;
+ }
+
+ private void addDeferredAliasValidityCheck(Value value, BooleanSupplier deferredValidityCheck) {
+ assert !isReceiverAlias(value);
+ // Only add the deferred validity check if `value` may be allowed as a receiver (i.e., it is not
+ // already illegal).
+ if (illegalReceiverAliases.contains(value)) {
+ assert !deferredAliasValidityChecks.containsKey(value);
+ } else {
+ deferredAliasValidityChecks
+ .computeIfAbsent(value, ignore -> new ArrayList<>())
+ .add(deferredValidityCheck);
+ }
+ }
+
+ private boolean isReceiverAlias(Value value) {
+ return isDefiniteReceiverAlias(value) || isMaybeReceiverAlias(value);
+ }
+
+ private boolean isDefiniteReceiverAlias(Value value) {
+ return definiteReceiverAliases.contains(value);
+ }
+
+ private boolean isMaybeReceiverAlias(Value value) {
+ return maybeReceiverAliases.contains(value);
+ }
+
+ private boolean isIllegalReceiverAlias(Value value) {
+ if (illegalReceiverAliases.contains(value)) {
+ return true;
+ }
+ List<BooleanSupplier> deferredValidityChecks = deferredAliasValidityChecks.get(value);
+ if (deferredValidityChecks != null) {
+ for (BooleanSupplier deferredValidityCheck : deferredValidityChecks) {
+ if (!deferredValidityCheck.getAsBoolean()) {
+ return true;
+ }
+ }
+ }
+ return false;
}
// Checks if the root instruction defines eligible value, i.e. the value
@@ -282,16 +370,21 @@
for (Instruction user : currentUsers) {
if (user.isAssume()) {
Value alias = user.outValue();
+ if (isReceiverAlias(alias)) {
+ continue; // Already processed.
+ }
if (alias.hasPhiUsers()) {
return alias.firstPhiUser(); // Not eligible.
}
- receivers.add(alias);
+ if (!addReceiverAlias(alias, AliasKind.DEFINITE)) {
+ return user; // Not eligible.
+ }
indirectUsers.addAll(alias.uniqueUsers());
continue;
}
// Field read/write.
if (user.isInstanceGet()
- || (user.isInstancePut() && user.asInstancePut().value() != eligibleInstance)) {
+ || (user.isInstancePut() && addIllegalReceiverAlias(user.asInstancePut().value()))) {
DexField field = user.asFieldInstruction().getField();
if (field.holder == eligibleClass
&& eligibleClassDefinition.lookupInstanceField(field) != null) {
@@ -393,6 +486,10 @@
methodCallsOnInstance.clear();
extraMethodCalls.clear();
unusedArguments.clear();
+ definiteReceiverAliases.clear();
+ definiteReceiverAliases.add(eligibleInstance);
+ maybeReceiverAliases.clear();
+ deferredAliasValidityChecks.clear();
estimatedCombinedSizeForInlining = 0;
// Repeat user analysis
@@ -455,7 +552,7 @@
}
assert methodCallsOnInstance.keySet().stream()
.map(InvokeMethodWithReceiver::getReceiver)
- .allMatch(receivers::contains);
+ .allMatch(this::isReceiverAlias);
inliner.performForcedInlining(method, code, methodCallsOnInstance, inliningIRProvider);
return true;
}
@@ -468,7 +565,7 @@
Assume<?> assumeInstruction = user.asAssume();
Value src = assumeInstruction.src();
Value dest = assumeInstruction.outValue();
- assert receivers.contains(dest);
+ assert isReceiverAlias(dest);
assert !dest.hasPhiUsers();
dest.replaceUsers(src);
removeInstruction(user);
@@ -618,11 +715,18 @@
assert isEligibleSingleTarget(singleTarget);
// Must be a constructor called on the receiver.
- if (ListUtils.lastIndexMatching(
- invoke.inValues(), v -> v.getAliasedValue() == eligibleInstance) != 0) {
+ if (!isDefiniteReceiverAlias(invoke.getReceiver())) {
return null;
}
+ // None of the subsequent arguments may be an alias of the receiver.
+ List<Value> inValues = invoke.inValues();
+ for (int i = 1; i < inValues.size(); i++) {
+ if (!addIllegalReceiverAlias(inValues.get(i))) {
+ return null;
+ }
+ }
+
// Must be a constructor of the exact same class.
DexMethod init = invoke.getInvokedMethod();
if (init.holder != eligibleClass) {
@@ -684,7 +788,7 @@
ClassInlinerEligibility eligibility,
InvokeMethodWithReceiver invoke,
Set<Instruction> indirectUsers) {
- if (!eligibility.returnsReceiver) {
+ if (eligibility.returnsReceiver.isFalse()) {
return true;
}
@@ -700,9 +804,13 @@
return false;
}
- // Since the invoke-instruction may return the receiver, the out-value may be an alias of the
- // receiver.
- receivers.add(outValue);
+ // Add the out-value as a definite-alias if the invoke instruction is guaranteed to return the
+ // receiver. Otherwise, the out-value may be an alias of the receiver, and it is added to the
+ // may-alias set.
+ AliasKind kind = eligibility.returnsReceiver.isTrue() ? AliasKind.DEFINITE : AliasKind.MAYBE;
+ if (!addReceiverAlias(outValue, kind)) {
+ return false;
+ }
Set<Instruction> currentUsers = outValue.uniqueUsers();
while (!currentUsers.isEmpty()) {
@@ -713,24 +821,29 @@
if (outValueAlias.hasPhiUsers() || outValueAlias.hasDebugUsers()) {
return false;
}
- receivers.add(outValueAlias);
+ if (!addReceiverAlias(outValueAlias, kind)) {
+ return false;
+ }
indirectOutValueUsers.addAll(outValueAlias.uniqueUsers());
continue;
}
- if (!instruction.isInvokeMethodWithReceiver()) {
- return false;
- }
- InvokeMethodWithReceiver user = instruction.asInvokeMethodWithReceiver();
- if (user.getReceiver().getAliasedValue() != outValue) {
- return false;
- }
- for (int i = 1; i < user.inValues().size(); i++) {
- if (user.inValues().get(i).getAliasedValue() == outValue) {
+
+ if (instruction.isInvokeMethodWithReceiver()) {
+ InvokeMethodWithReceiver user = instruction.asInvokeMethodWithReceiver();
+ if (user.getReceiver().getAliasedValue() != outValue) {
return false;
}
+ for (int i = 1; i < user.inValues().size(); i++) {
+ if (user.inValues().get(i).getAliasedValue() == outValue) {
+ return false;
+ }
+ }
+ indirectUsers.add(user);
+ continue;
}
+
+ return false;
}
- indirectUsers.addAll(currentUsers);
currentUsers = indirectOutValueUsers;
}
@@ -743,10 +856,15 @@
Set<Instruction> indirectUsers,
Supplier<InliningOracle> defaultOracle) {
assert isEligibleSingleTarget(singleTarget);
- if (ListUtils.lastIndexMatching(
- invoke.inValues(), v -> v.getAliasedValue() == eligibleInstance) > 0) {
- return null; // Instance passed as an argument.
+
+ // None of the none-receiver arguments may be an alias of the receiver.
+ List<Value> inValues = invoke.inValues();
+ for (int i = 1; i < inValues.size(); i++) {
+ if (!addIllegalReceiverAlias(inValues.get(i))) {
+ return null;
+ }
}
+
// TODO(b/141719453): Should not constrain library overrides if all instantiations are inlined.
if (singleTarget.isLibraryMethodOverride().isTrue()) {
InliningOracle inliningOracle = defaultOracle.get();
@@ -756,7 +874,6 @@
}
}
return isEligibleVirtualMethodCall(
- !invoke.getBlock().hasCatchHandlers(),
invoke.getInvokedMethod(),
singleTarget,
eligibility -> isEligibleInvokeWithAllUsersAsReceivers(eligibility, invoke, indirectUsers));
@@ -766,13 +883,12 @@
DexEncodedMethod singleTarget = eligibleClassDefinition.lookupVirtualMethod(callee);
if (isEligibleSingleTarget(singleTarget)) {
return isEligibleVirtualMethodCall(
- false, callee, singleTarget, eligibility -> !eligibility.returnsReceiver);
+ callee, singleTarget, eligibility -> eligibility.returnsReceiver.isFalse());
}
return null;
}
private InliningInfo isEligibleVirtualMethodCall(
- boolean allowMethodsWithoutNormalReturns,
DexMethod callee,
DexEncodedMethod singleTarget,
Predicate<ClassInlinerEligibility> eligibilityAcceptanceCheck) {
@@ -820,10 +936,11 @@
&& appView.dexItemFactory().isConstructor(invoke.getInvokedMethod())) {
return false;
}
- if (invoke.isInvokeMethodWithReceiver()
- && invoke.asInvokeMethodWithReceiver().getReceiver().getAliasedValue()
- == eligibleInstance) {
- return false;
+ if (invoke.isInvokeMethodWithReceiver()) {
+ Value receiver = invoke.asInvokeMethodWithReceiver().getReceiver();
+ if (!addIllegalReceiverAlias(receiver)) {
+ return false;
+ }
}
if (invoke.isInvokeSuper()) {
return false;
@@ -863,13 +980,13 @@
// If we got here with invocation on receiver the user is ineligible.
if (invoke.isInvokeMethodWithReceiver()) {
- if (arguments.get(0).getAliasedValue() == eligibleInstance) {
+ InvokeMethodWithReceiver invokeMethodWithReceiver = invoke.asInvokeMethodWithReceiver();
+ Value receiver = invokeMethodWithReceiver.getReceiver();
+ if (!addIllegalReceiverAlias(receiver)) {
return false;
}
// TODO(b/124842076) Extend this check to use checksNullReceiverBeforeAnySideEffect.
- InvokeMethodWithReceiver invokeMethodWithReceiver = invoke.asInvokeMethodWithReceiver();
- Value receiver = invokeMethodWithReceiver.getReceiver();
if (receiver.getTypeLattice().isNullable()) {
return false;
}
@@ -883,8 +1000,9 @@
}
for (int argIndex = 0; argIndex < arguments.size(); argIndex++) {
- Value argument = arguments.get(argIndex);
- if (argument == eligibleInstance && optimizationInfo.getParameterUsages(argIndex).notUsed()) {
+ Value argument = arguments.get(argIndex).getAliasedValue();
+ if (isDefiniteReceiverAlias(argument)
+ && optimizationInfo.getParameterUsages(argIndex).notUsed()) {
// Reference can be removed since it's not used.
unusedArguments.add(new Pair<>(invoke, argIndex));
}
@@ -904,16 +1022,19 @@
Supplier<InliningOracle> defaultOracle) {
// Go through all arguments, see if all usages of eligibleInstance are good.
for (int argIndex = 0; argIndex < arguments.size(); argIndex++) {
- Value argument = arguments.get(argIndex).getAliasedValue();
- if (argument != eligibleInstance) {
- continue; // Nothing to worry about.
- }
-
- // Have parameter usage info?
MethodOptimizationInfo optimizationInfo = singleTarget.getOptimizationInfo();
ParameterUsage parameterUsage = optimizationInfo.getParameterUsages(argIndex);
- if (!isEligibleParameterUsage(parameterUsage, invoke, defaultOracle)) {
- return false;
+
+ Value argument = arguments.get(argIndex);
+ if (isReceiverAlias(argument)) {
+ // Have parameter usage info?
+ if (!isEligibleParameterUsage(parameterUsage, invoke, defaultOracle)) {
+ return false;
+ }
+ } else {
+ // Nothing to worry about, unless `argument` becomes an alias of the receiver later.
+ addDeferredAliasValidityCheck(
+ argument, () -> isEligibleParameterUsage(parameterUsage, invoke, defaultOracle));
}
}
return true;
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfoCollector.java b/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfoCollector.java
index 13844a6..ebd2a0f 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfoCollector.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfoCollector.java
@@ -38,6 +38,7 @@
import com.android.tools.r8.ir.code.StaticPut;
import com.android.tools.r8.ir.code.Value;
import com.android.tools.r8.ir.optimize.DynamicTypeOptimization;
+import com.android.tools.r8.ir.optimize.classinliner.ClassInlinerReceiverAnalysis;
import com.android.tools.r8.ir.optimize.info.ParameterUsagesInfo.ParameterUsage;
import com.android.tools.r8.ir.optimize.info.ParameterUsagesInfo.ParameterUsageBuilder;
import com.android.tools.r8.ir.optimize.info.initializer.ClassInitializerInfo;
@@ -123,7 +124,6 @@
return;
}
- boolean receiverUsedAsReturnValue = false;
boolean seenSuperInitCall = false;
for (Instruction insn : receiver.aliasedUsers()) {
if (insn.isAssume()) {
@@ -134,11 +134,6 @@
continue;
}
- if (insn.isReturn()) {
- receiverUsedAsReturnValue = true;
- continue;
- }
-
if (insn.isInstanceGet() || insn.isInstancePut()) {
if (insn.isInstancePut()) {
InstancePut instancePutInstruction = insn.asInstancePut();
@@ -176,16 +171,23 @@
return;
}
+ if (insn.isReturn()) {
+ continue;
+ }
+
// Other receiver usages make the method not eligible.
return;
}
+
if (instanceInitializer && !seenSuperInitCall) {
// Call to super constructor not found?
return;
}
feedback.setClassInlinerEligibility(
- method, new ClassInlinerEligibility(receiverUsedAsReturnValue));
+ method,
+ new ClassInlinerEligibility(
+ new ClassInlinerReceiverAnalysis(appView, method, code).computeReturnsReceiver()));
}
private void identifyParameterUsages(
diff --git a/src/main/java/com/android/tools/r8/utils/OptionalBool.java b/src/main/java/com/android/tools/r8/utils/OptionalBool.java
index 54029d5..abb048d 100644
--- a/src/main/java/com/android/tools/r8/utils/OptionalBool.java
+++ b/src/main/java/com/android/tools/r8/utils/OptionalBool.java
@@ -62,4 +62,18 @@
public OptionalBool asOptionalBool() {
return this;
}
+
+ @Override
+ public boolean equals(Object other) {
+ return this == other;
+ }
+
+ @Override
+ public int hashCode() {
+ return System.identityHashCode(this);
+ }
+
+ // Force all subtypes to implement toString().
+ @Override
+ public abstract String toString();
}