Introduce a testing directive -alwaysclassinline

Similar to -alwaysinline for the inliner, -alwaysclassinline will cause the class inliner to inline a class if possible (ignoring the size heuristics).

Change-Id: Id7e79b2a286b9779eaa52569ef77a8da8c729740
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 18c56e6..36fd914 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
@@ -231,7 +231,10 @@
             new ClassInlinerCostAnalysis(
                 appView, inliningIRProvider, processor.getReceivers().getDefiniteReceiverAliases());
         if (costAnalysis.willExceedInstructionBudget(
-            code, processor.getDirectInlinees(), processor.getIndirectInlinees())) {
+            code,
+            processor.getEligibleClass(),
+            processor.getDirectInlinees(),
+            processor.getIndirectInlinees())) {
           // This root is unlikely to be inlined in the future.
           rootsIterator.remove();
           continue;
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerCostAnalysis.java b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerCostAnalysis.java
index 9b9670e..da76b7b 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerCostAnalysis.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerCostAnalysis.java
@@ -11,11 +11,13 @@
 
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexProgramClass;
 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.ir.optimize.inliner.InliningIRProvider;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.google.common.collect.Sets;
 import java.util.List;
 import java.util.Map;
@@ -24,14 +26,14 @@
 /** Analysis that estimates the cost of class inlining an object allocation. */
 class ClassInlinerCostAnalysis {
 
-  private final AppView<?> appView;
+  private final AppView<AppInfoWithLiveness> appView;
   private final InliningIRProvider inliningIRProvider;
   private final Set<Value> definiteReceiverAliases;
 
   private int estimatedCost = 0;
 
   ClassInlinerCostAnalysis(
-      AppView<?> appView,
+      AppView<AppInfoWithLiveness> appView,
       InliningIRProvider inliningIRProvider,
       Set<Value> definiteReceiverAliases) {
     this.appView = appView;
@@ -41,8 +43,13 @@
 
   boolean willExceedInstructionBudget(
       IRCode code,
+      DexProgramClass eligibleClass,
       Map<InvokeMethod, DexEncodedMethod> directInlinees,
       List<DexEncodedMethod> indirectInlinees) {
+    if (appView.appInfo().alwaysClassInline.contains(eligibleClass.type)) {
+      return false;
+    }
+
     for (DexEncodedMethod inlinee : indirectInlinees) {
       // We do not have the corresponding invoke instruction for the inlinees that are not called
       // directly from `code` (these are called indirectly from one of the methods in
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 9afba98..a831b86 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
@@ -4,6 +4,7 @@
 
 package com.android.tools.r8.ir.optimize.classinliner;
 
+import static com.android.tools.r8.graph.DexProgramClass.asProgramClassOrNull;
 
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.AppView;
@@ -13,6 +14,7 @@
 import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.ResolutionResult;
 import com.android.tools.r8.ir.analysis.ClassInitializationAnalysis;
@@ -85,7 +87,7 @@
 
   private Value eligibleInstance;
   private DexType eligibleClass;
-  private DexClass eligibleClassDefinition;
+  private DexProgramClass eligibleClassDefinition;
   private boolean isDesugaredLambda;
 
   private final Map<InvokeMethodWithReceiver, InliningInfo> methodCallsOnInstance =
@@ -120,6 +122,10 @@
     this.receivers = new ClassInlinerReceiverSet(root.outValue());
   }
 
+  DexProgramClass getEligibleClass() {
+    return eligibleClassDefinition;
+  }
+
   Map<InvokeMethod, DexEncodedMethod> getDirectInlinees() {
     return directInlinees;
   }
@@ -165,7 +171,7 @@
       isDesugaredLambda = eligibleClassDefinition != null;
     }
     if (eligibleClassDefinition == null) {
-      eligibleClassDefinition = appView.definitionFor(eligibleClass);
+      eligibleClassDefinition = asProgramClassOrNull(appView.definitionFor(eligibleClass));
     }
     if (eligibleClassDefinition != null) {
       return EligibilityStatus.ELIGIBLE;
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 347ae0f..0aea415 100644
--- a/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
+++ b/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
@@ -142,6 +142,8 @@
   public final Set<DexMethod> keepConstantArguments;
   /** All methods that may not have any unused arguments removed. */
   public final Set<DexMethod> keepUnusedArguments;
+  /** All types that should be inlined if possible due to a configuration directive. */
+  public final Set<DexType> alwaysClassInline;
   /** All types that *must* never be inlined due to a configuration directive (testing only). */
   public final Set<DexType> neverClassInline;
   /** All types that *must* never be merged due to a configuration directive (testing only). */
@@ -210,6 +212,7 @@
       Set<DexMethod> whyAreYouNotInlining,
       Set<DexMethod> keepConstantArguments,
       Set<DexMethod> keepUnusedArguments,
+      Set<DexType> alwaysClassInline,
       Set<DexType> neverClassInline,
       Set<DexType> neverMerge,
       Set<DexReference> neverPropagateValue,
@@ -248,6 +251,7 @@
     this.whyAreYouNotInlining = whyAreYouNotInlining;
     this.keepConstantArguments = keepConstantArguments;
     this.keepUnusedArguments = keepUnusedArguments;
+    this.alwaysClassInline = alwaysClassInline;
     this.neverClassInline = neverClassInline;
     this.neverMerge = neverMerge;
     this.neverPropagateValue = neverPropagateValue;
@@ -289,6 +293,7 @@
       Set<DexMethod> whyAreYouNotInlining,
       Set<DexMethod> keepConstantArguments,
       Set<DexMethod> keepUnusedArguments,
+      Set<DexType> alwaysClassInline,
       Set<DexType> neverClassInline,
       Set<DexType> neverMerge,
       Set<DexReference> neverPropagateValue,
@@ -327,6 +332,7 @@
     this.whyAreYouNotInlining = whyAreYouNotInlining;
     this.keepConstantArguments = keepConstantArguments;
     this.keepUnusedArguments = keepUnusedArguments;
+    this.alwaysClassInline = alwaysClassInline;
     this.neverClassInline = neverClassInline;
     this.neverMerge = neverMerge;
     this.neverPropagateValue = neverPropagateValue;
@@ -369,6 +375,7 @@
         previous.whyAreYouNotInlining,
         previous.keepConstantArguments,
         previous.keepUnusedArguments,
+        previous.alwaysClassInline,
         previous.neverClassInline,
         previous.neverMerge,
         previous.neverPropagateValue,
@@ -418,6 +425,7 @@
         previous.whyAreYouNotInlining,
         previous.keepConstantArguments,
         previous.keepUnusedArguments,
+        previous.alwaysClassInline,
         previous.neverClassInline,
         previous.neverMerge,
         previous.neverPropagateValue,
@@ -499,6 +507,7 @@
             .map(this::definitionFor)
             .filter(Objects::nonNull)
             .collect(Collectors.toList()));
+    this.alwaysClassInline = rewriteItems(previous.alwaysClassInline, lense::lookupType);
     this.neverClassInline = rewriteItems(previous.neverClassInline, lense::lookupType);
     this.neverMerge = rewriteItems(previous.neverMerge, lense::lookupType);
     this.neverPropagateValue = lense.rewriteReferencesConservatively(previous.neverPropagateValue);
@@ -550,6 +559,7 @@
     this.whyAreYouNotInlining = previous.whyAreYouNotInlining;
     this.keepConstantArguments = previous.keepConstantArguments;
     this.keepUnusedArguments = previous.keepUnusedArguments;
+    this.alwaysClassInline = previous.alwaysClassInline;
     this.neverClassInline = previous.neverClassInline;
     this.neverMerge = previous.neverMerge;
     this.neverPropagateValue = previous.neverPropagateValue;
diff --git a/src/main/java/com/android/tools/r8/shaking/ClassInlineRule.java b/src/main/java/com/android/tools/r8/shaking/ClassInlineRule.java
index e7dc76c..4e69cd2 100644
--- a/src/main/java/com/android/tools/r8/shaking/ClassInlineRule.java
+++ b/src/main/java/com/android/tools/r8/shaking/ClassInlineRule.java
@@ -11,6 +11,7 @@
 public class ClassInlineRule extends ProguardConfigurationRule {
 
   public enum Type {
+    ALWAYS,
     NEVER
   }
 
@@ -95,8 +96,20 @@
   }
 
   @Override
+  public boolean isClassInlineRule() {
+    return true;
+  }
+
+  @Override
+  public ClassInlineRule asClassInlineRule() {
+    return this;
+  }
+
+  @Override
   String typeString() {
     switch (type) {
+      case ALWAYS:
+        return "alwaysclassinline";
       case NEVER:
         return "neverclassinline";
     }
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 8f41082..8329fcf 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -2308,6 +2308,7 @@
             rootSet.whyAreYouNotInlining,
             rootSet.keepConstantArguments,
             rootSet.keepUnusedArguments,
+            rootSet.alwaysClassInline,
             rootSet.neverClassInline,
             rootSet.neverMerge,
             rootSet.neverPropagateValue,
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 cee2d66..4778c35 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
@@ -432,6 +432,11 @@
           configurationBuilder.addRule(rule);
           return true;
         }
+        if (acceptString("alwaysclassinline")) {
+          ClassInlineRule rule = parseClassInlineRule(ClassInlineRule.Type.ALWAYS, optionStart);
+          configurationBuilder.addRule(rule);
+          return true;
+        }
         if (acceptString("neverclassinline")) {
           ClassInlineRule rule = parseClassInlineRule(ClassInlineRule.Type.NEVER, optionStart);
           configurationBuilder.addRule(rule);
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationRule.java b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationRule.java
index ed49a01..f899a6b 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationRule.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationRule.java
@@ -57,6 +57,14 @@
     return null;
   }
 
+  public boolean isClassInlineRule() {
+    return false;
+  }
+
+  public ClassInlineRule asClassInlineRule() {
+    return null;
+  }
+
   Iterable<DexProgramClass> relevantCandidatesForRule(
       AppView<? extends AppInfoWithSubtyping> appView, Iterable<DexProgramClass> defaultValue) {
     if (hasInheritanceClassName() && getInheritanceClassName().hasSpecificType()) {
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 24b5004..b86857a 100644
--- a/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java
+++ b/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java
@@ -3,7 +3,6 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.shaking;
 
-
 import com.android.tools.r8.dex.Constants;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.AppInfo;
@@ -82,6 +81,7 @@
   private final Set<DexMethod> whyAreYouNotInlining = Sets.newIdentityHashSet();
   private final Set<DexMethod> keepParametersWithConstantValue = Sets.newIdentityHashSet();
   private final Set<DexMethod> keepUnusedArguments = Sets.newIdentityHashSet();
+  private final Set<DexType> alwaysClassInline = Sets.newIdentityHashSet();
   private final Set<DexType> neverClassInline = Sets.newIdentityHashSet();
   private final Set<DexType> neverMerge = Sets.newIdentityHashSet();
   private final Set<DexReference> neverPropagateValue = Sets.newIdentityHashSet();
@@ -312,6 +312,7 @@
         whyAreYouNotInlining,
         keepParametersWithConstantValue,
         keepUnusedArguments,
+        alwaysClassInline,
         neverClassInline,
         neverMerge,
         neverPropagateValue,
@@ -1108,12 +1109,23 @@
       }
       whyAreYouNotInlining.add(item.asDexEncodedMethod().method);
       context.markAsUsed();
-    } else if (context instanceof ClassInlineRule) {
-      switch (((ClassInlineRule) context).getType()) {
+    } else if (context.isClassInlineRule()) {
+      ClassInlineRule classInlineRule = context.asClassInlineRule();
+      DexClass clazz = item.asDexClass();
+      if (clazz == null) {
+        throw new IllegalStateException(
+            "Unexpected -"
+                + classInlineRule.typeString()
+                + " rule for a non-class type: `"
+                + item.toReference().toSourceString()
+                + "`");
+      }
+      switch (classInlineRule.getType()) {
+        case ALWAYS:
+          alwaysClassInline.add(item.asDexClass().type);
+          break;
         case NEVER:
-          if (item.isDexClass()) {
-            neverClassInline.add(item.asDexClass().type);
-          }
+          neverClassInline.add(item.asDexClass().type);
           break;
         default:
           throw new Unreachable();
@@ -1189,6 +1201,7 @@
     public final Set<DexMethod> whyAreYouNotInlining;
     public final Set<DexMethod> keepConstantArguments;
     public final Set<DexMethod> keepUnusedArguments;
+    public final Set<DexType> alwaysClassInline;
     public final Set<DexType> neverClassInline;
     public final Set<DexType> neverMerge;
     public final Set<DexReference> neverPropagateValue;
@@ -1215,6 +1228,7 @@
         Set<DexMethod> whyAreYouNotInlining,
         Set<DexMethod> keepConstantArguments,
         Set<DexMethod> keepUnusedArguments,
+        Set<DexType> alwaysClassInline,
         Set<DexType> neverClassInline,
         Set<DexType> neverMerge,
         Set<DexReference> neverPropagateValue,
@@ -1238,6 +1252,7 @@
       this.whyAreYouNotInlining = whyAreYouNotInlining;
       this.keepConstantArguments = keepConstantArguments;
       this.keepUnusedArguments = keepUnusedArguments;
+      this.alwaysClassInline = alwaysClassInline;
       this.neverClassInline = neverClassInline;
       this.neverMerge = Collections.unmodifiableSet(neverMerge);
       this.neverPropagateValue = neverPropagateValue;