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();
 }