Add a @NeverSingleCallerInline annotation for testing

Fixes: 176066007
Change-Id: Ifa750f8b3dd558e6428af39f19f92cd704b2b3db
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/inliner/DefaultInliningReasonStrategy.java b/src/main/java/com/android/tools/r8/ir/optimize/inliner/DefaultInliningReasonStrategy.java
index cc2a612..e6b5afe 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/inliner/DefaultInliningReasonStrategy.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/inliner/DefaultInliningReasonStrategy.java
@@ -51,11 +51,8 @@
       // program.
       return Reason.SIMPLE;
     }
-    if (callSiteInformation.hasSingleCallSite(target)) {
-      if (appView.options().testing.validInliningReasons == null
-          || appView.options().testing.validInliningReasons.contains(Reason.SINGLE_CALLER)) {
-        return Reason.SINGLE_CALLER;
-      }
+    if (isSingleCallerInliningTarget(target)) {
+      return Reason.SINGLE_CALLER;
     }
     if (isDoubleInliningTarget(target)) {
       return Reason.DUAL_CALLER;
@@ -63,6 +60,20 @@
     return Reason.SIMPLE;
   }
 
+  private boolean isSingleCallerInliningTarget(ProgramMethod method) {
+    if (!callSiteInformation.hasSingleCallSite(method)) {
+      return false;
+    }
+    if (appView.appInfo().isNeverInlineDueToSingleCallerMethod(method)) {
+      return false;
+    }
+    if (appView.options().testing.validInliningReasons != null
+        && !appView.options().testing.validInliningReasons.contains(Reason.SINGLE_CALLER)) {
+      return false;
+    }
+    return true;
+  }
+
   private boolean isDoubleInliningTarget(ProgramMethod candidate) {
     // 10 is found from measuring.
     if (callSiteInformation.hasDoubleCallSite(candidate)
diff --git a/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java b/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
index 6fe65b8..49cff50 100644
--- a/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
+++ b/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
@@ -134,6 +134,11 @@
   private final Set<DexMethod> forceInline;
   /** All methods that *must* never be inlined due to a configuration directive (testing only). */
   private final Set<DexMethod> neverInline;
+  /**
+   * All methods that *must* never be inlined as a result of having a single caller due to a
+   * configuration directive (testing only).
+   */
+  private final Set<DexMethod> neverInlineDueToSingleCaller;
   /** Items for which to print inlining decisions for (testing only). */
   private final Set<DexMethod> whyAreYouNotInlining;
   /** All methods that may not have any parameters with a constant value removed. */
@@ -207,6 +212,7 @@
       Set<DexMethod> alwaysInline,
       Set<DexMethod> forceInline,
       Set<DexMethod> neverInline,
+      Set<DexMethod> neverInlineDueToSingleCaller,
       Set<DexMethod> whyAreYouNotInlining,
       Set<DexMethod> keepConstantArguments,
       Set<DexMethod> keepUnusedArguments,
@@ -244,6 +250,7 @@
     this.alwaysInline = alwaysInline;
     this.forceInline = forceInline;
     this.neverInline = neverInline;
+    this.neverInlineDueToSingleCaller = neverInlineDueToSingleCaller;
     this.whyAreYouNotInlining = whyAreYouNotInlining;
     this.keepConstantArguments = keepConstantArguments;
     this.keepUnusedArguments = keepUnusedArguments;
@@ -289,6 +296,7 @@
         previous.alwaysInline,
         previous.forceInline,
         previous.neverInline,
+        previous.neverInlineDueToSingleCaller,
         previous.whyAreYouNotInlining,
         previous.keepConstantArguments,
         previous.keepUnusedArguments,
@@ -335,6 +343,7 @@
         previous.alwaysInline,
         previous.forceInline,
         previous.neverInline,
+        previous.neverInlineDueToSingleCaller,
         previous.whyAreYouNotInlining,
         previous.keepConstantArguments,
         previous.keepUnusedArguments,
@@ -426,6 +435,7 @@
     this.alwaysInline = previous.alwaysInline;
     this.forceInline = previous.forceInline;
     this.neverInline = previous.neverInline;
+    this.neverInlineDueToSingleCaller = previous.neverInlineDueToSingleCaller;
     this.whyAreYouNotInlining = previous.whyAreYouNotInlining;
     this.keepConstantArguments = previous.keepConstantArguments;
     this.keepUnusedArguments = previous.keepUnusedArguments;
@@ -563,6 +573,10 @@
     return neverInline.contains(method);
   }
 
+  public boolean isNeverInlineDueToSingleCallerMethod(ProgramMethod method) {
+    return neverInlineDueToSingleCaller.contains(method.getReference());
+  }
+
   public boolean isWhyAreYouNotInliningMethod(DexMethod method) {
     return whyAreYouNotInlining.contains(method);
   }
@@ -978,6 +992,7 @@
         lens.rewriteMethods(alwaysInline),
         lens.rewriteMethods(forceInline),
         lens.rewriteMethods(neverInline),
+        lens.rewriteMethods(neverInlineDueToSingleCaller),
         lens.rewriteMethods(whyAreYouNotInlining),
         lens.rewriteMethods(keepConstantArguments),
         lens.rewriteMethods(keepUnusedArguments),
diff --git a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
index 21d1d50..826740f 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -3329,6 +3329,7 @@
             rootSet.alwaysInline,
             rootSet.forceInline,
             rootSet.neverInline,
+            rootSet.neverInlineDueToSingleCaller,
             rootSet.whyAreYouNotInlining,
             rootSet.keepConstantArguments,
             rootSet.keepUnusedArguments,
diff --git a/src/main/java/com/android/tools/r8/shaking/InlineRule.java b/src/main/java/com/android/tools/r8/shaking/InlineRule.java
index 41b6b33..32412d7 100644
--- a/src/main/java/com/android/tools/r8/shaking/InlineRule.java
+++ b/src/main/java/com/android/tools/r8/shaking/InlineRule.java
@@ -18,7 +18,10 @@
   };
 
   public enum Type {
-    ALWAYS, FORCE, NEVER
+    ALWAYS,
+    FORCE,
+    NEVER,
+    NEVER_SINGLE_CALLER
   }
 
   public static class Builder extends ProguardConfigurationRule.Builder<InlineRule, Builder> {
@@ -127,6 +130,8 @@
         return "forceinline";
       case NEVER:
         return "neverinline";
+      case NEVER_SINGLE_CALLER:
+        return "neversinglecaller";
     }
     throw new Unreachable("Unknown inline type " + type);
   }
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
index f71d027..8294be0 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
@@ -469,6 +469,11 @@
           configurationBuilder.addRule(rule);
           return true;
         }
+        if (acceptString("neversinglecallerinline")) {
+          InlineRule rule = parseInlineRule(InlineRule.Type.NEVER_SINGLE_CALLER, optionStart);
+          configurationBuilder.addRule(rule);
+          return true;
+        }
         if (acceptString(NoUnusedInterfaceRemovalRule.RULE_NAME)) {
           ProguardConfigurationRule rule = parseNoUnusedInterfaceRemovalRule(optionStart);
           configurationBuilder.addRule(rule);
diff --git a/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java b/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java
index be431d3..bbcf912 100644
--- a/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java
+++ b/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java
@@ -91,6 +91,7 @@
   private final Set<DexMethod> alwaysInline = Sets.newIdentityHashSet();
   private final Set<DexMethod> forceInline = Sets.newIdentityHashSet();
   private final Set<DexMethod> neverInline = Sets.newIdentityHashSet();
+  private final Set<DexMethod> neverInlineDueToSingleCaller = Sets.newIdentityHashSet();
   private final Set<DexMethod> bypassClinitforInlining = Sets.newIdentityHashSet();
   private final Set<DexMethod> whyAreYouNotInlining = Sets.newIdentityHashSet();
   private final Set<DexMethod> keepParametersWithConstantValue = Sets.newIdentityHashSet();
@@ -347,6 +348,7 @@
         alwaysInline,
         forceInline,
         neverInline,
+        neverInlineDueToSingleCaller,
         bypassClinitforInlining,
         whyAreYouNotInlining,
         keepParametersWithConstantValue,
@@ -431,6 +433,7 @@
   ConsequentRootSet buildConsequentRootSet() {
     return new ConsequentRootSet(
         neverInline,
+        neverInlineDueToSingleCaller,
         neverClassInline,
         noShrinking,
         softPinned,
@@ -1198,15 +1201,19 @@
       context.markAsUsed();
     } else if (context instanceof InlineRule) {
       if (item.isDexEncodedMethod()) {
+        DexMethod reference = item.asDexEncodedMethod().getReference();
         switch (((InlineRule) context).getType()) {
           case ALWAYS:
-            alwaysInline.add(item.asDexEncodedMethod().method);
+            alwaysInline.add(reference);
             break;
           case FORCE:
-            forceInline.add(item.asDexEncodedMethod().method);
+            forceInline.add(reference);
             break;
           case NEVER:
-            neverInline.add(item.asDexEncodedMethod().method);
+            neverInline.add(reference);
+            break;
+          case NEVER_SINGLE_CALLER:
+            neverInlineDueToSingleCaller.add(reference);
             break;
           default:
             throw new Unreachable();
@@ -1331,6 +1338,7 @@
   abstract static class RootSetBase {
 
     final Set<DexMethod> neverInline;
+    final Set<DexMethod> neverInlineDueToSingleCaller;
     final Set<DexType> neverClassInline;
     final MutableItemsWithRules noShrinking;
     final MutableItemsWithRules softPinned;
@@ -1342,6 +1350,7 @@
 
     RootSetBase(
         Set<DexMethod> neverInline,
+        Set<DexMethod> neverInlineDueToSingleCaller,
         Set<DexType> neverClassInline,
         MutableItemsWithRules noShrinking,
         MutableItemsWithRules softPinned,
@@ -1351,6 +1360,7 @@
         Map<DexType, Set<ProguardKeepRuleBase>> dependentKeepClassCompatRule,
         List<DelayedRootSetActionItem> delayedRootSetActionItems) {
       this.neverInline = neverInline;
+      this.neverInlineDueToSingleCaller = neverInlineDueToSingleCaller;
       this.neverClassInline = neverClassInline;
       this.noShrinking = noShrinking;
       this.softPinned = softPinned;
@@ -1777,6 +1787,7 @@
         Set<DexMethod> alwaysInline,
         Set<DexMethod> forceInline,
         Set<DexMethod> neverInline,
+        Set<DexMethod> neverInlineDueToSingleCaller,
         Set<DexMethod> bypassClinitForInlining,
         Set<DexMethod> whyAreYouNotInlining,
         Set<DexMethod> keepConstantArguments,
@@ -1801,6 +1812,7 @@
         List<DelayedRootSetActionItem> delayedRootSetActionItems) {
       super(
           neverInline,
+          neverInlineDueToSingleCaller,
           neverClassInline,
           noShrinking,
           softPinned,
@@ -1850,6 +1862,7 @@
 
     void addConsequentRootSet(ConsequentRootSet consequentRootSet, boolean addNoShrinking) {
       neverInline.addAll(consequentRootSet.neverInline);
+      neverInlineDueToSingleCaller.addAll(consequentRootSet.neverInlineDueToSingleCaller);
       neverClassInline.addAll(consequentRootSet.neverClassInline);
       noObfuscation.addAll(consequentRootSet.noObfuscation);
       if (addNoShrinking) {
@@ -2107,6 +2120,7 @@
 
     ConsequentRootSet(
         Set<DexMethod> neverInline,
+        Set<DexMethod> neverInlineDueToSingleCaller,
         Set<DexType> neverClassInline,
         MutableItemsWithRules noShrinking,
         MutableItemsWithRules softPinned,
@@ -2117,6 +2131,7 @@
         List<DelayedRootSetActionItem> delayedRootSetActionItems) {
       super(
           neverInline,
+          neverInlineDueToSingleCaller,
           neverClassInline,
           noShrinking,
           softPinned,
diff --git a/src/test/java/com/android/tools/r8/NeverSingleCallerInline.java b/src/test/java/com/android/tools/r8/NeverSingleCallerInline.java
new file mode 100644
index 0000000..8c710fb
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/NeverSingleCallerInline.java
@@ -0,0 +1,13 @@
+// Copyright (c) 2018, 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;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+@Retention(RetentionPolicy.CLASS)
+@Target({ElementType.CONSTRUCTOR, ElementType.METHOD})
+public @interface NeverSingleCallerInline {}
diff --git a/src/test/java/com/android/tools/r8/R8TestBuilder.java b/src/test/java/com/android/tools/r8/R8TestBuilder.java
index ebae827..e831d72 100644
--- a/src/test/java/com/android/tools/r8/R8TestBuilder.java
+++ b/src/test/java/com/android/tools/r8/R8TestBuilder.java
@@ -367,6 +367,14 @@
         "-forceinline class * { @" + annotationPackageName + ".ForceInline *; }");
   }
 
+  public T enableNeverSingleCallerInlineAnnotations() {
+    return addNeverSingleCallerInlineAnnotations()
+        .addInternalKeepRules(
+            "-neversinglecallerinline class * {",
+            "  @com.android.tools.r8.NeverSingleCallerInline <methods>;",
+            "}");
+  }
+
   public T enableNeverClassInliningAnnotations() {
     return addNeverClassInliningAnnotations()
         .addInternalKeepRules("-neverclassinline @com.android.tools.r8.NeverClassInline class *");
diff --git a/src/test/java/com/android/tools/r8/TestShrinkerBuilder.java b/src/test/java/com/android/tools/r8/TestShrinkerBuilder.java
index 37673c2..1bcbc55 100644
--- a/src/test/java/com/android/tools/r8/TestShrinkerBuilder.java
+++ b/src/test/java/com/android/tools/r8/TestShrinkerBuilder.java
@@ -343,6 +343,10 @@
     return addTestingAnnotation(NeverReprocessMethod.class);
   }
 
+  public final T addNeverSingleCallerInlineAnnotations() {
+    return addTestingAnnotation(NeverSingleCallerInline.class);
+  }
+
   public final T addNoHorizontalClassMergingAnnotations() {
     return addTestingAnnotation(NoHorizontalClassMerging.class);
   }
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/inliner/conditionalsimpleinlining/ConditionalSimpleInliningTestBase.java b/src/test/java/com/android/tools/r8/ir/optimize/inliner/conditionalsimpleinlining/ConditionalSimpleInliningTestBase.java
index 2aef42a..61c9edc 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/inliner/conditionalsimpleinlining/ConditionalSimpleInliningTestBase.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/inliner/conditionalsimpleinlining/ConditionalSimpleInliningTestBase.java
@@ -36,18 +36,10 @@
   public void configure(R8FullTestBuilder testBuilder) {
     testBuilder
         .addOptionsModification(this::enableSimpleInliningConstraints)
-        .addOptionsModification(this::disableSingleCallerInlining)
         .setMinApi(parameters.getApiLevel());
   }
 
   private void enableSimpleInliningConstraints(InternalOptions options) {
     options.enableSimpleInliningConstraints = enableSimpleInliningConstraints;
   }
-
-  // TODO(b/176066007): Introduce a @NeverSingleCallerInline instead.
-  private void disableSingleCallerInlining(InternalOptions options) {
-    assert options.testing.validInliningReasons == null;
-    options.testing.validInliningReasons = SetUtils.newIdentityHashSet(Inliner.Reason.values());
-    options.testing.validInliningReasons.remove(Inliner.Reason.SINGLE_CALLER);
-  }
 }
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/inliner/conditionalsimpleinlining/SimpleIfNullOrNotNullInliningTest.java b/src/test/java/com/android/tools/r8/ir/optimize/inliner/conditionalsimpleinlining/SimpleIfNullOrNotNullInliningTest.java
index 2f605ac..c5073c5 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/inliner/conditionalsimpleinlining/SimpleIfNullOrNotNullInliningTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/inliner/conditionalsimpleinlining/SimpleIfNullOrNotNullInliningTest.java
@@ -10,6 +10,7 @@
 import static org.hamcrest.CoreMatchers.not;
 import static org.hamcrest.MatcherAssert.assertThat;
 
+import com.android.tools.r8.NeverSingleCallerInline;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.utils.BooleanUtils;
@@ -50,6 +51,7 @@
         .addProgramClasses(mainClass, TestMethods.class)
         .addKeepMainRule(mainClass)
         .apply(this::configure)
+        .enableNeverSingleCallerInlineAnnotations()
         .compile()
         .inspect(this::inspect)
         .run(parameters.getRuntime(), mainClass)
@@ -114,6 +116,7 @@
 
   static class TestMethods {
 
+    @NeverSingleCallerInline
     static void simpleIfNullTest(Object o) {
       if (o == null) {
         return;
@@ -132,6 +135,7 @@
       System.out.println("!");
     }
 
+    @NeverSingleCallerInline
     static void simpleIfBothNullTest(Object o1, Object o2) {
       if (o1 == null && o2 == null) {
         return;
@@ -150,6 +154,7 @@
       System.out.println("!");
     }
 
+    @NeverSingleCallerInline
     static void simpleIfNotNullTest(Object o) {
       if (o != null) {
         return;
@@ -168,6 +173,7 @@
       System.out.println("!");
     }
 
+    @NeverSingleCallerInline
     static void simpleIfBothNotNullTest(Object o1, Object o2) {
       if (o1 != null && o2 != null) {
         return;
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/inliner/conditionalsimpleinlining/SimpleIfTrueOrFalseInliningTest.java b/src/test/java/com/android/tools/r8/ir/optimize/inliner/conditionalsimpleinlining/SimpleIfTrueOrFalseInliningTest.java
index 6c0debe..b7a7186 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/inliner/conditionalsimpleinlining/SimpleIfTrueOrFalseInliningTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/inliner/conditionalsimpleinlining/SimpleIfTrueOrFalseInliningTest.java
@@ -10,6 +10,7 @@
 import static org.hamcrest.CoreMatchers.not;
 import static org.hamcrest.MatcherAssert.assertThat;
 
+import com.android.tools.r8.NeverSingleCallerInline;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.utils.BooleanUtils;
@@ -50,6 +51,7 @@
         .addProgramClasses(mainClass, TestMethods.class)
         .addKeepMainRule(mainClass)
         .apply(this::configure)
+        .enableNeverSingleCallerInlineAnnotations()
         .compile()
         .inspect(this::inspect)
         .run(parameters.getRuntime(), mainClass)
@@ -112,6 +114,7 @@
 
   static class TestMethods {
 
+    @NeverSingleCallerInline
     static void simpleIfTrueTest(boolean b) {
       if (b) {
         return;
@@ -130,6 +133,7 @@
       System.out.println("!");
     }
 
+    @NeverSingleCallerInline
     static void simpleIfBothTrueTest(boolean b1, boolean b2) {
       if (b1 && b2) {
         return;
@@ -148,6 +152,7 @@
       System.out.println("!");
     }
 
+    @NeverSingleCallerInline
     static void simpleIfFalseTest(boolean b) {
       if (!b) {
         return;
@@ -166,6 +171,7 @@
       System.out.println("!");
     }
 
+    @NeverSingleCallerInline
     static void simpleIfBothFalseTest(boolean b1, boolean b2) {
       if (!b1 && !b2) {
         return;