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;