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>; +}