Implement -alwaysinline directive.
Bug:
Change-Id: I5c37a4a93e5d47973a2c5c41ef690f4d141a9237
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 ce6ad76..814812c 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
@@ -203,6 +203,7 @@
*/
public enum Reason {
FORCE, // Inlinee is marked for forced inlining (bridge method or renamed constructor).
+ ALWAYS, // Inlinee is marked for inlining due to alwaysinline directive.
SINGLE_CALLER, // Inlinee has precisely one caller.
DUAL_CALLER, // Inlinee has precisely two callers.
SIMPLE, // Inlinee has simple code suitable for inlining.
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/InliningOracle.java b/src/main/java/com/android/tools/r8/ir/optimize/InliningOracle.java
index d32f7f4..91f0a61 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/InliningOracle.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/InliningOracle.java
@@ -62,6 +62,10 @@
if (target.getOptimizationInfo().forceInline()) {
return Reason.FORCE;
}
+ if (inliner.appInfo.hasLiveness()
+ && inliner.appInfo.withLiveness().alwaysInline.contains(target)) {
+ return Reason.ALWAYS;
+ }
if (callGraph.hasSingleCallSite(target)) {
return Reason.SINGLE_CALLER;
}
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 c45a5c1..524207e 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -1092,6 +1092,10 @@
*/
public final Map<DexItem, ProguardMemberRule> assumedValues;
/**
+ * All methods that have to be inlined due to a configuration directive.
+ */
+ public final Set<DexItem> alwaysInline;
+ /**
* Map from the class of an extension to the state it produced.
*/
public final Map<Class, Object> extensions;
@@ -1118,6 +1122,7 @@
this.staticInvokes = joinInvokedMethods(enqueuer.staticInvokes);
this.noSideEffects = enqueuer.rootSet.noSideEffects;
this.assumedValues = enqueuer.rootSet.assumedValues;
+ this.alwaysInline = enqueuer.rootSet.alwaysInline;
this.extensions = enqueuer.extensionsState;
assert Sets.intersection(instanceFieldReads, staticFieldReads).size() == 0;
assert Sets.intersection(instanceFieldWrites, staticFieldWrites).size() == 0;
@@ -1145,6 +1150,7 @@
this.directInvokes = previous.directInvokes;
this.staticInvokes = previous.staticInvokes;
this.extensions = previous.extensions;
+ this.alwaysInline = previous.alwaysInline;
assert Sets.intersection(instanceFieldReads, staticFieldReads).size() == 0;
assert Sets.intersection(instanceFieldWrites, staticFieldWrites).size() == 0;
}
@@ -1170,6 +1176,7 @@
this.superInvokes = rewriteItems(previous.superInvokes, lense::lookupMethod);
this.directInvokes = rewriteItems(previous.directInvokes, lense::lookupMethod);
this.staticInvokes = rewriteItems(previous.staticInvokes, lense::lookupMethod);
+ this.alwaysInline = previous.alwaysInline;
this.extensions = previous.extensions;
assert Sets.intersection(instanceFieldReads, staticFieldReads).size() == 0;
assert Sets.intersection(instanceFieldWrites, staticFieldWrites).size() == 0;
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardAlwaysInlineRule.java b/src/main/java/com/android/tools/r8/shaking/ProguardAlwaysInlineRule.java
new file mode 100644
index 0000000..e404f51
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardAlwaysInlineRule.java
@@ -0,0 +1,47 @@
+// Copyright (c) 2017, 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.shaking;
+
+import com.android.tools.r8.graph.DexAccessFlags;
+import java.util.List;
+import java.util.Set;
+
+public class ProguardAlwaysInlineRule extends ProguardConfigurationRule {
+
+ public static class Builder extends ProguardConfigurationRule.Builder {
+
+ private Builder() {
+ }
+
+ public ProguardAlwaysInlineRule build() {
+ return new ProguardAlwaysInlineRule(classAnnotation, classAccessFlags,
+ negatedClassAccessFlags, classTypeNegated, classType, classNames, inheritanceAnnotation,
+ inheritanceClassName, inheritanceIsExtends, memberRules);
+ }
+ }
+
+ private ProguardAlwaysInlineRule(
+ ProguardTypeMatcher classAnnotation,
+ DexAccessFlags classAccessFlags,
+ DexAccessFlags negatedClassAccessFlags,
+ boolean classTypeNegated,
+ ProguardClassType classType,
+ List<ProguardTypeMatcher> classNames,
+ ProguardTypeMatcher inheritanceAnnotation,
+ ProguardTypeMatcher inheritanceClassName,
+ boolean inheritanceIsExtends,
+ Set<ProguardMemberRule> memberRules) {
+ super(classAnnotation, classAccessFlags, negatedClassAccessFlags, classTypeNegated, classType,
+ classNames, inheritanceAnnotation, inheritanceClassName, inheritanceIsExtends, memberRules);
+ }
+
+ public static ProguardAlwaysInlineRule.Builder builder() {
+ return new ProguardAlwaysInlineRule.Builder();
+ }
+
+ @Override
+ String typeString() {
+ return "alwaysinline";
+ }
+}
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 1ab82f6..0ad086c 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
@@ -52,7 +52,8 @@
"invokebasemethod");
private static final List<String> ignoredClassDescriptorOptions = ImmutableList
.of("isclassnamestring",
- "alwaysinline", "identifiernamestring", "whyarenotsimple");
+ "identifiernamestring",
+ "whyarenotsimple");
private static final List<String> warnedSingleArgOptions = ImmutableList
.of("renamesourcefileattribute",
@@ -245,6 +246,9 @@
configurationBuilder.setClassObfuscationDictionary(parseFileName());
} else if (acceptString("packageobfuscationdictionary")) {
configurationBuilder.setPackageObfuscationDictionary(parseFileName());
+ } else if (acceptString("alwaysinline")) {
+ ProguardAlwaysInlineRule rule = parseAlwaysInlineRule();
+ configurationBuilder.addRule(rule);
} else {
throw parseError("Unknown option");
}
@@ -414,6 +418,13 @@
return keepRuleBuilder.build();
}
+ private ProguardAlwaysInlineRule parseAlwaysInlineRule()
+ throws ProguardRuleParserException {
+ ProguardAlwaysInlineRule.Builder keepRuleBuilder = ProguardAlwaysInlineRule.builder();
+ parseClassSpec(keepRuleBuilder, false);
+ return keepRuleBuilder.build();
+ }
+
private void parseClassSpec(
ProguardConfigurationRule.Builder builder, boolean allowValueSpecification)
throws ProguardRuleParserException {
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 1ddd7d3..0fa5051 100644
--- a/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java
+++ b/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java
@@ -50,6 +50,7 @@
private final Set<ProguardConfigurationRule> rulesThatUseExtendsOrImplementsWrong =
Sets.newIdentityHashSet();
private final Set<DexItem> checkDiscarded = Sets.newIdentityHashSet();
+ private final Set<DexItem> alwaysInline = Sets.newIdentityHashSet();
private final Map<DexItem, Map<DexItem, ProguardKeepRule>> dependentNoShrinking =
new IdentityHashMap<>();
private final Map<DexItem, ProguardMemberRule> noSideEffects = new IdentityHashMap<>();
@@ -204,6 +205,8 @@
} else if (rule instanceof ProguardAssumeNoSideEffectRule) {
markMatchingVisibleMethods(clazz, memberKeepRules, rule, null);
markMatchingFields(clazz, memberKeepRules, rule, null);
+ } else if (rule instanceof ProguardAlwaysInlineRule) {
+ markMatchingMethods(clazz, memberKeepRules, rule, null);
} else {
assert rule instanceof ProguardAssumeValuesRule;
markMatchingVisibleMethods(clazz, memberKeepRules, rule, null);
@@ -250,7 +253,7 @@
application.timing.end();
}
return new RootSet(noShrinking, noOptimization, noObfuscation, reasonAsked, keepPackageName,
- checkDiscarded, noSideEffects, assumedValues, dependentNoShrinking);
+ checkDiscarded, alwaysInline, noSideEffects, assumedValues, dependentNoShrinking);
}
private void markMatchingVisibleMethods(DexClass clazz,
@@ -499,6 +502,8 @@
assumedValues.put(item, rule);
} else if (context instanceof ProguardCheckDiscardRule) {
checkDiscarded.add(item);
+ } else if (context instanceof ProguardAlwaysInlineRule) {
+ alwaysInline.add(item);
}
}
@@ -510,6 +515,7 @@
public final Set<DexItem> reasonAsked;
public final Set<DexItem> keepPackageName;
public final Set<DexItem> checkDiscarded;
+ public final Set<DexItem> alwaysInline;
public final Map<DexItem, ProguardMemberRule> noSideEffects;
public final Map<DexItem, ProguardMemberRule> assumedValues;
private final Map<DexItem, Map<DexItem, ProguardKeepRule>> dependentNoShrinking;
@@ -552,7 +558,7 @@
private RootSet(Map<DexItem, ProguardKeepRule> noShrinking,
Set<DexItem> noOptimization, Set<DexItem> noObfuscation, Set<DexItem> reasonAsked,
Set<DexItem> keepPackageName, Set<DexItem> checkDiscarded,
- Map<DexItem, ProguardMemberRule> noSideEffects,
+ Set<DexItem> alwaysInline, Map<DexItem, ProguardMemberRule> noSideEffects,
Map<DexItem, ProguardMemberRule> assumedValues,
Map<DexItem, Map<DexItem, ProguardKeepRule>> dependentNoShrinking) {
this.noShrinking = Collections.unmodifiableMap(noShrinking);
@@ -561,6 +567,7 @@
this.reasonAsked = Collections.unmodifiableSet(reasonAsked);
this.keepPackageName = Collections.unmodifiableSet(keepPackageName);
this.checkDiscarded = Collections.unmodifiableSet(checkDiscarded);
+ this.alwaysInline = Collections.unmodifiableSet(alwaysInline);
this.noSideEffects = Collections.unmodifiableMap(noSideEffects);
this.assumedValues = Collections.unmodifiableMap(assumedValues);
this.dependentNoShrinking = dependentNoShrinking;
diff --git a/src/test/examples/inlining/AlwaysInline.java b/src/test/examples/inlining/AlwaysInline.java
new file mode 100644
index 0000000..815d50a
--- /dev/null
+++ b/src/test/examples/inlining/AlwaysInline.java
@@ -0,0 +1,8 @@
+// Copyright (c) 2017, 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 inlining;
+
+public @interface AlwaysInline {
+
+}
diff --git a/src/test/examples/inlining/Inlining.java b/src/test/examples/inlining/Inlining.java
index f89a0e1..02a4b7c 100644
--- a/src/test/examples/inlining/Inlining.java
+++ b/src/test/examples/inlining/Inlining.java
@@ -196,6 +196,12 @@
Subclass.callsMethodThatCallsProtectedMethod();
// Do not inline constructors which set final field.
System.out.println(new InlineConstructorFinalField());
+
+ // Call method three times to ensure it would not normally be inlined but force inline anyway.
+ int aNumber = longMethodThatWeShouldNotInline("ha", "li", "lo");
+ aNumber += longMethodThatWeShouldNotInline("zi", "za", "zo");
+ aNumber += longMethodThatWeShouldNotInline("do", "de", "da");
+ System.out.println(aNumber);
}
private static boolean intCmpExpression(A a, A b) {
@@ -323,4 +329,11 @@
private static int onlyCalledTwice(int count) {
return count > 0 ? count + 1 : count - 1;
}
+
+ @AlwaysInline
+ @CheckDiscarded
+ private static int longMethodThatWeShouldNotInline(String a, String b, String c) {
+ String result = a + b + c + b + a + c + b;
+ return result.length();
+ }
}
diff --git a/src/test/examples/inlining/keep-rules-discard.txt b/src/test/examples/inlining/keep-rules-discard.txt
index ea5e143..123b105 100644
--- a/src/test/examples/inlining/keep-rules-discard.txt
+++ b/src/test/examples/inlining/keep-rules-discard.txt
@@ -12,3 +12,7 @@
-checkdiscard class * {
@inlining.CheckDiscarded *;
}
+
+-alwaysinline class * {
+ @inlining.AlwaysInline <methods>;
+}
diff --git a/src/test/examples/inlining/keep-rules.txt b/src/test/examples/inlining/keep-rules.txt
index d60f1dc..bf2c4ba 100644
--- a/src/test/examples/inlining/keep-rules.txt
+++ b/src/test/examples/inlining/keep-rules.txt
@@ -7,3 +7,7 @@
-keep public class inlining.Inlining {
public static void main(...);
}
+
+-alwaysinline class * {
+ @inlining.AlwaysInline <methods>;
+}