Handle the remaining -whyareyounotinlining cases

Bug: 142108662
Change-Id: Iff62762dbd162cdb505f09d24954a1967926c09f
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 f12c956..f34e2bc 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
@@ -384,33 +384,36 @@
         if (appInfo.isSubtype(containerType, method.holder)) {
           return true;
         }
-        whyAreYouNotInliningReporter.reportUnknownReason();
+        whyAreYouNotInliningReporter.reportCallerNotSubtype();
         return false;
 
       case PROCESSED_INLINING_CANDIDATE_SAME_PACKAGE:
         if (containerType.isSamePackage(method.holder)) {
           return true;
         }
-        whyAreYouNotInliningReporter.reportUnknownReason();
+        whyAreYouNotInliningReporter.reportCallerNotSamePackage();
         return false;
 
       case PROCESSED_INLINING_CANDIDATE_SAME_NEST:
         if (NestUtils.sameNest(containerType, method.holder, appInfo)) {
           return true;
         }
-        whyAreYouNotInliningReporter.reportUnknownReason();
+        whyAreYouNotInliningReporter.reportCallerNotSameNest();
         return false;
 
       case PROCESSED_INLINING_CANDIDATE_SAME_CLASS:
         if (containerType == method.holder) {
           return true;
         }
-        whyAreYouNotInliningReporter.reportUnknownReason();
+        whyAreYouNotInliningReporter.reportCallerNotSameClass();
         return false;
 
       case PROCESSED_NOT_INLINING_CANDIDATE:
+        whyAreYouNotInliningReporter.reportInlineeNotInliningCandidate();
+        return false;
+
       case NOT_PROCESSED:
-        whyAreYouNotInliningReporter.reportUnknownReason();
+        whyAreYouNotInliningReporter.reportInlineeNotProcessed();
         return false;
 
       default:
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 cd59784..87350e7 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
@@ -209,11 +209,11 @@
 
   private boolean passesInliningConstraints(
       InvokeMethod invoke,
-      DexEncodedMethod candidate,
+      DexEncodedMethod singleTarget,
       Reason reason,
       WhyAreYouNotInliningReporter whyAreYouNotInliningReporter) {
-    if (candidate.getOptimizationInfo().neverInline()) {
-      whyAreYouNotInliningReporter.reportUnknownReason();
+    if (singleTarget.getOptimizationInfo().neverInline()) {
+      whyAreYouNotInliningReporter.reportMarkedAsNeverInline();
       return false;
     }
 
@@ -222,15 +222,15 @@
     if (method.isInstanceInitializer()
         && appView.options().isGeneratingClassFiles()
         && reason != Reason.FORCE) {
-      whyAreYouNotInliningReporter.reportUnknownReason();
+      whyAreYouNotInliningReporter.reportNoInliningIntoConstructorsWhenGeneratingClassFiles();
       return false;
     }
 
-    if (method == candidate) {
+    if (method == singleTarget) {
       // Cannot handle recursive inlining at this point.
       // Force inlined method should never be recursive.
-      assert !candidate.getOptimizationInfo().forceInline();
-      whyAreYouNotInliningReporter.reportUnknownReason();
+      assert !singleTarget.getOptimizationInfo().forceInline();
+      whyAreYouNotInliningReporter.reportRecursiveMethod();
       return false;
     }
 
@@ -239,67 +239,64 @@
     // or optimized code. Right now this happens for the class class staticizer, as it just
     // processes all relevant methods in parallel with the full optimization pipeline enabled.
     // TODO(sgjesse): Add this assert "assert !isProcessedConcurrently.test(candidate);"
-    if (reason != Reason.FORCE && isProcessedConcurrently.test(candidate)) {
-      whyAreYouNotInliningReporter.reportUnknownReason();
+    if (reason != Reason.FORCE && isProcessedConcurrently.test(singleTarget)) {
+      whyAreYouNotInliningReporter.reportProcessedConcurrently();
       return false;
     }
 
     InternalOptions options = appView.options();
     if (options.featureSplitConfiguration != null
         && !options.featureSplitConfiguration.inSameFeatureOrBase(
-            candidate.method, method.method)) {
-      whyAreYouNotInliningReporter.reportUnknownReason();
+            singleTarget.method, method.method)) {
+      whyAreYouNotInliningReporter.reportInliningAcrossFeatureSplit();
       return false;
     }
 
-    if (options.testing.validInliningReasons != null
-        && !options.testing.validInliningReasons.contains(reason)) {
-      whyAreYouNotInliningReporter.reportUnknownReason();
+    Set<Reason> validInliningReasons = options.testing.validInliningReasons;
+    if (validInliningReasons != null && !validInliningReasons.contains(reason)) {
+      whyAreYouNotInliningReporter.reportInvalidInliningReason(reason, validInliningReasons);
       return false;
     }
 
     // Abort inlining attempt if method -> target access is not right.
-    if (!inliner.hasInliningAccess(method, candidate)) {
-      whyAreYouNotInliningReporter.reportUnknownReason();
+    if (!inliner.hasInliningAccess(method, singleTarget)) {
+      whyAreYouNotInliningReporter.reportInaccessible();
       return false;
     }
 
-    DexClass holder = appView.definitionFor(candidate.method.holder);
-
+    DexClass holder = appView.definitionFor(singleTarget.method.holder);
     if (holder.isInterface()) {
       // Art978_virtual_interfaceTest correctly expects an IncompatibleClassChangeError exception at
       // runtime.
-      whyAreYouNotInliningReporter.reportUnknownReason();
-      return false;
-    }
-
-    if (holder.isNotProgramClass()) {
-      whyAreYouNotInliningReporter.reportUnknownReason();
+      whyAreYouNotInliningReporter.reportIncompatibleClassChangeError();
       return false;
     }
 
     // Don't inline if target is synchronized.
-    if (candidate.accessFlags.isSynchronized()) {
-      whyAreYouNotInliningReporter.reportUnknownReason();
+    if (singleTarget.accessFlags.isSynchronized()) {
+      whyAreYouNotInliningReporter.reportSynchronizedMethod();
       return false;
     }
 
     if (reason == Reason.DUAL_CALLER) {
-      if (satisfiesRequirementsForSimpleInlining(invoke, candidate)) {
+      if (satisfiesRequirementsForSimpleInlining(invoke, singleTarget)) {
         // When we have a method with two call sites, we simply inline the method as we normally do
         // when the method is small. We still need to ensure that the other call site is also
         // inlined, though. Therefore, we record here that we have seen one of the two call sites
         // as we normally do.
-        inliner.recordDoubleInliningCandidate(method, candidate);
-      } else {
-        if (inliner.satisfiesRequirementsForDoubleInlining(method, candidate)) {
-          whyAreYouNotInliningReporter.reportUnknownReason();
+        inliner.recordDoubleInliningCandidate(method, singleTarget);
+      } else if (inliner.isDoubleInliningEnabled()) {
+        if (!inliner.satisfiesRequirementsForDoubleInlining(method, singleTarget)) {
+          whyAreYouNotInliningReporter.reportInvalidDoubleInliningCandidate();
           return false;
         }
+      } else {
+        // TODO(b/142300882): Should in principle disallow inlining in this case.
+        inliner.recordDoubleInliningCandidate(method, singleTarget);
       }
     } else if (reason == Reason.SIMPLE
-        && !satisfiesRequirementsForSimpleInlining(invoke, candidate)) {
-      whyAreYouNotInliningReporter.reportUnknownReason();
+        && !satisfiesRequirementsForSimpleInlining(invoke, singleTarget)) {
+      whyAreYouNotInliningReporter.reportInlineeNotSimple();
       return false;
     }
 
@@ -308,8 +305,8 @@
       // If we do this it can increase the size of the main dex dependent classes.
       if (inliner.mainDexClasses.getRoots().contains(method.method.holder)
           && MainDexDirectReferenceTracer.hasReferencesOutsideFromCode(
-          appView.appInfo(), candidate, inliner.mainDexClasses.getRoots())) {
-        whyAreYouNotInliningReporter.reportUnknownReason();
+              appView.appInfo(), singleTarget, inliner.mainDexClasses.getRoots())) {
+        whyAreYouNotInliningReporter.reportInlineeRefersToClassesNotInMainDex();
         return false;
       }
       // Allow inlining into the classes in the main dex dependent set without restrictions.
@@ -391,7 +388,7 @@
     Value receiver = invoke.getReceiver();
     if (receiver.getTypeLattice().isDefinitelyNull()) {
       // A definitely null receiver will throw an error on call site.
-      whyAreYouNotInliningReporter.reportUnknownReason();
+      whyAreYouNotInliningReporter.reportReceiverDefinitelyNull();
       return null;
     }
 
@@ -405,7 +402,7 @@
       if (!singleTarget.getOptimizationInfo().checksNullReceiverBeforeAnySideEffect()) {
         InternalOptions options = appView.options();
         if (!options.enableInliningOfInvokesWithNullableReceivers) {
-          whyAreYouNotInliningReporter.reportUnknownReason();
+          whyAreYouNotInliningReporter.reportReceiverMaybeNull();
           return null;
         }
         action.setShouldSynthesizeNullCheckForReceiver();
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 18c101c..44c72c5 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
@@ -106,6 +106,10 @@
     return false;
   }
 
+  boolean isDoubleInliningEnabled() {
+    return applyDoubleInlining;
+  }
+
   private ConstraintWithTarget instructionAllowedForInlining(
       Instruction instruction, InliningConstraints inliningConstraints, DexType invocationContext) {
     ConstraintWithTarget result =
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/inliner/NopWhyAreYouNotInliningReporter.java b/src/main/java/com/android/tools/r8/ir/optimize/inliner/NopWhyAreYouNotInliningReporter.java
index 81bdb7c..818b8c2 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/inliner/NopWhyAreYouNotInliningReporter.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/inliner/NopWhyAreYouNotInliningReporter.java
@@ -7,6 +7,8 @@
 import com.android.tools.r8.ir.code.InstancePut;
 import com.android.tools.r8.ir.code.Instruction;
 import com.android.tools.r8.ir.code.InvokeDirect;
+import com.android.tools.r8.ir.optimize.Inliner.Reason;
+import java.util.Set;
 
 public class NopWhyAreYouNotInliningReporter extends WhyAreYouNotInliningReporter {
 
@@ -23,18 +25,57 @@
   public void reportBlacklisted() {}
 
   @Override
+  public void reportCallerNotSameClass() {}
+
+  @Override
+  public void reportCallerNotSameNest() {}
+
+  @Override
+  public void reportCallerNotSamePackage() {}
+
+  @Override
+  public void reportCallerNotSubtype() {}
+
+  @Override
   public void reportClasspathMethod() {}
 
   @Override
+  public void reportInaccessible() {}
+
+  @Override
+  public void reportIncompatibleClassChangeError() {}
+
+  @Override
   public void reportIncorrectArity(int numberOfArguments, int arity) {}
 
   @Override
   public void reportInlineeDoesNotHaveCode() {}
 
   @Override
+  public void reportInlineeNotInliningCandidate() {}
+
+  @Override
+  public void reportInlineeNotProcessed() {}
+
+  @Override
+  public void reportInlineeNotSimple() {}
+
+  @Override
+  public void reportInlineeRefersToClassesNotInMainDex() {}
+
+  @Override
+  public void reportInliningAcrossFeatureSplit() {}
+
+  @Override
   public void reportInstructionBudgetIsExceeded() {}
 
   @Override
+  public void reportInvalidDoubleInliningCandidate() {}
+
+  @Override
+  public void reportInvalidInliningReason(Reason reason, Set<Reason> validInliningReasons) {}
+
+  @Override
   public void reportLibraryMethod() {}
 
   @Override
@@ -44,6 +85,9 @@
   public void reportMustTriggerClassInitialization() {}
 
   @Override
+  public void reportNoInliningIntoConstructorsWhenGeneratingClassFiles() {}
+
+  @Override
   public void reportPinned() {}
 
   @Override
@@ -51,7 +95,19 @@
       int estimatedNumberOfControlFlowResolutionBlocks, int threshold) {}
 
   @Override
-  public void reportUnknownReason() {}
+  public void reportProcessedConcurrently() {}
+
+  @Override
+  public void reportReceiverDefinitelyNull() {}
+
+  @Override
+  public void reportReceiverMaybeNull() {}
+
+  @Override
+  public void reportRecursiveMethod() {}
+
+  @Override
+  public void reportSynchronizedMethod() {}
 
   @Override
   public void reportUnknownTarget() {}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/inliner/WhyAreYouNotInliningReporter.java b/src/main/java/com/android/tools/r8/ir/optimize/inliner/WhyAreYouNotInliningReporter.java
index 9bfd769..c560944 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/inliner/WhyAreYouNotInliningReporter.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/inliner/WhyAreYouNotInliningReporter.java
@@ -10,8 +10,10 @@
 import com.android.tools.r8.ir.code.Instruction;
 import com.android.tools.r8.ir.code.InvokeDirect;
 import com.android.tools.r8.ir.code.InvokeMethod;
+import com.android.tools.r8.ir.optimize.Inliner.Reason;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import java.util.Collection;
+import java.util.Set;
 
 public abstract class WhyAreYouNotInliningReporter {
 
@@ -46,26 +48,62 @@
 
   public abstract void reportBlacklisted();
 
+  public abstract void reportCallerNotSameClass();
+
+  public abstract void reportCallerNotSameNest();
+
+  public abstract void reportCallerNotSamePackage();
+
+  public abstract void reportCallerNotSubtype();
+
   public abstract void reportClasspathMethod();
 
+  public abstract void reportInaccessible();
+
+  public abstract void reportIncompatibleClassChangeError();
+
   public abstract void reportIncorrectArity(int numberOfArguments, int arity);
 
   public abstract void reportInlineeDoesNotHaveCode();
 
+  public abstract void reportInlineeNotInliningCandidate();
+
+  public abstract void reportInlineeNotProcessed();
+
+  public abstract void reportInlineeNotSimple();
+
+  public abstract void reportInlineeRefersToClassesNotInMainDex();
+
+  public abstract void reportInliningAcrossFeatureSplit();
+
   public abstract void reportInstructionBudgetIsExceeded();
 
+  public abstract void reportInvalidDoubleInliningCandidate();
+
+  public abstract void reportInvalidInliningReason(Reason reason, Set<Reason> validInliningReasons);
+
   public abstract void reportLibraryMethod();
 
   public abstract void reportMarkedAsNeverInline();
 
   public abstract void reportMustTriggerClassInitialization();
 
+  public abstract void reportNoInliningIntoConstructorsWhenGeneratingClassFiles();
+
   public abstract void reportPinned();
 
   public abstract void reportPotentialExplosionInExceptionalControlFlowResolutionBlocks(
       int estimatedNumberOfControlFlowResolutionBlocks, int threshold);
 
-  public abstract void reportUnknownReason();
+  public abstract void reportProcessedConcurrently();
+
+  public abstract void reportReceiverDefinitelyNull();
+
+  public abstract void reportReceiverMaybeNull();
+
+  public abstract void reportRecursiveMethod();
+
+  public abstract void reportSynchronizedMethod();
 
   abstract void reportUnknownTarget();
 
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/inliner/WhyAreYouNotInliningReporterImpl.java b/src/main/java/com/android/tools/r8/ir/optimize/inliner/WhyAreYouNotInliningReporterImpl.java
index e6ad63e..6bc09fd 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/inliner/WhyAreYouNotInliningReporterImpl.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/inliner/WhyAreYouNotInliningReporterImpl.java
@@ -8,7 +8,10 @@
 import com.android.tools.r8.ir.code.InstancePut;
 import com.android.tools.r8.ir.code.Instruction;
 import com.android.tools.r8.ir.code.InvokeDirect;
+import com.android.tools.r8.ir.optimize.Inliner.Reason;
+import com.android.tools.r8.utils.StringUtils;
 import java.io.PrintStream;
+import java.util.Set;
 
 class WhyAreYouNotInliningReporterImpl extends WhyAreYouNotInliningReporter {
 
@@ -50,11 +53,46 @@
   }
 
   @Override
+  public void reportCallerNotSameClass() {
+    print("inlinee can only be inlined into methods in the same class.");
+  }
+
+  @Override
+  public void reportCallerNotSameNest() {
+    print("inlinee can only be inlined into methods in the same class (and its nest members).");
+  }
+
+  @Override
+  public void reportCallerNotSamePackage() {
+    print(
+        "inlinee can only be inlined into methods in the same package "
+            + "(declared package private or accesses package private type or member).");
+  }
+
+  @Override
+  public void reportCallerNotSubtype() {
+    print(
+        "inlinee can only be inlined into methods in the same package and methods in subtypes of "
+            + "the inlinee's enclosing class"
+            + "(declared protected or accesses protected type or member).");
+  }
+
+  @Override
   public void reportClasspathMethod() {
     print("inlinee is on the classpath.");
   }
 
   @Override
+  public void reportInaccessible() {
+    print("inlinee is not accessible from the caller context.");
+  }
+
+  @Override
+  public void reportIncompatibleClassChangeError() {
+    print("invoke may fail with an IncompatibleClassChangeError.");
+  }
+
+  @Override
   public void reportIncorrectArity(int numberOfArguments, int arity) {
     print(
         "number of arguments ("
@@ -70,11 +108,55 @@
   }
 
   @Override
+  public void reportInlineeNotInliningCandidate() {
+    print("unsupported instruction in inlinee.");
+  }
+
+  @Override
+  public void reportInlineeNotProcessed() {
+    print("inlinee not processed yet.");
+  }
+
+  @Override
+  public void reportInlineeNotSimple() {
+    print(
+        "not inlining due to code size heuristic "
+            + "(inlinee may have multiple callers and is not considered trivial).");
+  }
+
+  @Override
+  public void reportInlineeRefersToClassesNotInMainDex() {
+    print(
+        "inlining could increase the main dex size "
+            + "(caller is in main dex and inlinee refers to classes not in main dex).");
+  }
+
+  @Override
+  public void reportInliningAcrossFeatureSplit() {
+    print("cannot inline across feature splits.");
+  }
+
+  @Override
   public void reportInstructionBudgetIsExceeded() {
     print("caller's instruction budget is exceeded.");
   }
 
   @Override
+  public void reportInvalidDoubleInliningCandidate() {
+    print("inlinee is invoked more than once and could not be inlined into all call sites.");
+  }
+
+  @Override
+  public void reportInvalidInliningReason(Reason reason, Set<Reason> validInliningReasons) {
+    print(
+        "not a valid inlining reason (was: "
+            + reason
+            + ", allowed: one of "
+            + StringUtils.join(validInliningReasons, ", ")
+            + ").");
+  }
+
+  @Override
   public void reportLibraryMethod() {
     print("inlinee is a library method.");
   }
@@ -92,6 +174,11 @@
   }
 
   @Override
+  public void reportNoInliningIntoConstructorsWhenGeneratingClassFiles() {
+    print("inlining into constructors not supported when generating class files.");
+  }
+
+  @Override
   public void reportPinned() {
     print("method is kept by a Proguard configuration rule.");
   }
@@ -106,10 +193,30 @@
         threshold);
   }
 
-  // TODO(b/142108662): Always report a meaningful reason.
   @Override
-  public void reportUnknownReason() {
-    print(null);
+  public void reportProcessedConcurrently() {
+    print(
+        "could lead to nondeterministic output since the inlinee is being optimized concurrently.");
+  }
+
+  @Override
+  public void reportReceiverDefinitelyNull() {
+    print("the receiver is always null at the call site.");
+  }
+
+  @Override
+  public void reportReceiverMaybeNull() {
+    print("the receiver may be null at the call site.");
+  }
+
+  @Override
+  public void reportRecursiveMethod() {
+    print("recursive calls are not inlined.");
+  }
+
+  @Override
+  public void reportSynchronizedMethod() {
+    print("synchronized methods are not inlined.");
   }
 
   @Override