Merge "Implement -alwaysinline directive."
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>;
+}