Inlining heuristics for optimizing proto builders

This CL introduces a new option options.protoShrinking().generatedMessageLiteBuilderShrinking, and adds additional items into the alwaysInline and neverInline sets if the option is set.

Change-Id: Ibe11b2d314fc6ca6789af68280166a9a33f66be9
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/proto/GeneratedMessageLiteBuilderShrinker.java b/src/main/java/com/android/tools/r8/ir/analysis/proto/GeneratedMessageLiteBuilderShrinker.java
new file mode 100644
index 0000000..b60e3f1
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/analysis/proto/GeneratedMessageLiteBuilderShrinker.java
@@ -0,0 +1,86 @@
+// Copyright (c) 2019, 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.ir.analysis.proto;
+
+import com.android.tools.r8.graph.AppInfoWithSubtyping;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexType;
+import java.util.Set;
+
+public class GeneratedMessageLiteBuilderShrinker {
+
+  public static void addInliningHeuristicsForBuilderInlining(
+      AppView<? extends AppInfoWithSubtyping> appView,
+      Set<DexMethod> alwaysInline,
+      Set<DexMethod> neverInline) {
+    new RootSetExtension(appView, alwaysInline, neverInline).extend();
+  }
+
+  private static class RootSetExtension {
+
+    private final AppView<? extends AppInfoWithSubtyping> appView;
+    private final ProtoReferences references;
+
+    private final Set<DexMethod> alwaysInline;
+    private final Set<DexMethod> neverInline;
+
+    RootSetExtension(
+        AppView<? extends AppInfoWithSubtyping> appView,
+        Set<DexMethod> alwaysInline,
+        Set<DexMethod> neverInline) {
+      this.appView = appView;
+      this.references = appView.protoShrinker().references;
+      this.alwaysInline = alwaysInline;
+      this.neverInline = neverInline;
+    }
+
+    void extend() {
+      // GeneratedMessageLite heuristics.
+      alwaysInlineCreateBuilderFromGeneratedMessageLite();
+      neverInlineIsInitializedFromGeneratedMessageLite();
+
+      // * extends GeneratedMessageLite heuristics.
+      alwaysInlineDynamicMethodFromGeneratedMessageLiteImplementations();
+
+      // GeneratedMessageLite$Builder heuristics.
+      alwaysInlineBuildPartialFromGeneratedMessageLiteBuilder();
+    }
+
+    private void alwaysInlineBuildPartialFromGeneratedMessageLiteBuilder() {
+      alwaysInline.add(references.generatedMessageLiteBuilderMethods.buildPartialMethod);
+    }
+
+    private void alwaysInlineCreateBuilderFromGeneratedMessageLite() {
+      alwaysInline.add(references.generatedMessageLiteMethods.createBuilderMethod);
+    }
+
+    private void alwaysInlineDynamicMethodFromGeneratedMessageLiteImplementations() {
+      // TODO(b/132600418): We should be able to determine that dynamicMethod() becomes 'SIMPLE'
+      //  when the MethodToInvoke argument is MethodToInvoke.NEW_BUILDER.
+      DexItemFactory dexItemFactory = appView.dexItemFactory();
+      for (DexType type : appView.appInfo().subtypes(references.generatedMessageLiteType)) {
+        alwaysInline.add(
+            dexItemFactory.createMethod(
+                type,
+                dexItemFactory.createProto(
+                    dexItemFactory.objectType,
+                    references.methodToInvokeType,
+                    dexItemFactory.objectType,
+                    dexItemFactory.objectType),
+                "dynamicMethod"));
+      }
+    }
+
+    /**
+     * Without this rule, GeneratedMessageLite$Builder.build() becomes too big for class inlining.
+     * TODO(b/112437944): Maybe introduce a -neverinlineinto rule instead?
+     */
+    private void neverInlineIsInitializedFromGeneratedMessageLite() {
+      neverInline.add(references.generatedMessageLiteMethods.isInitializedMethod);
+    }
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/proto/ProtoReferences.java b/src/main/java/com/android/tools/r8/ir/analysis/proto/ProtoReferences.java
index 0919db9..4b1cb0e 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/proto/ProtoReferences.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/proto/ProtoReferences.java
@@ -17,10 +17,14 @@
   public final DexType extensionRegistryLiteType;
   public final DexType generatedExtensionType;
   public final DexType generatedMessageLiteType;
+  public final DexType generatedMessageLiteBuilderType;
   public final DexType rawMessageInfoType;
   public final DexType messageLiteType;
   public final DexType methodToInvokeType;
 
+  public final GeneratedMessageLiteMethods generatedMessageLiteMethods;
+  public final GeneratedMessageLiteBuilderMethods generatedMessageLiteBuilderMethods;
+
   public final DexString dynamicMethodName;
   public final DexString findLiteExtensionByNumberName;
 
@@ -42,6 +46,9 @@
             factory.createString("Lcom/google/protobuf/GeneratedMessageLite$GeneratedExtension;"));
     generatedMessageLiteType =
         factory.createType(factory.createString("Lcom/google/protobuf/GeneratedMessageLite;"));
+    generatedMessageLiteBuilderType =
+        factory.createType(
+            factory.createString("Lcom/google/protobuf/GeneratedMessageLite$Builder;"));
     rawMessageInfoType =
         factory.createType(factory.createString("Lcom/google/protobuf/RawMessageInfo;"));
     messageLiteType = factory.createType(factory.createString("Lcom/google/protobuf/MessageLite;"));
@@ -73,6 +80,9 @@
             factory.createProto(
                 factory.voidType, messageLiteType, factory.stringType, factory.objectArrayType),
             factory.constructorMethodName);
+
+    generatedMessageLiteMethods = new GeneratedMessageLiteMethods(factory);
+    generatedMessageLiteBuilderMethods = new GeneratedMessageLiteBuilderMethods(factory);
   }
 
   public boolean isDynamicMethod(DexMethod method) {
@@ -91,4 +101,36 @@
   public boolean isMessageInfoConstructionMethod(DexMethod method) {
     return method.match(newMessageInfoMethod) || method == rawMessageInfoConstructor;
   }
+
+  class GeneratedMessageLiteMethods {
+
+    public final DexMethod createBuilderMethod;
+    public final DexMethod isInitializedMethod;
+
+    private GeneratedMessageLiteMethods(DexItemFactory dexItemFactory) {
+      createBuilderMethod =
+          dexItemFactory.createMethod(
+              generatedMessageLiteType,
+              dexItemFactory.createProto(generatedMessageLiteBuilderType),
+              "createBuilder");
+      isInitializedMethod =
+          dexItemFactory.createMethod(
+              generatedMessageLiteType,
+              dexItemFactory.createProto(dexItemFactory.booleanType),
+              "isInitialized");
+    }
+  }
+
+  class GeneratedMessageLiteBuilderMethods {
+
+    public final DexMethod buildPartialMethod;
+
+    private GeneratedMessageLiteBuilderMethods(DexItemFactory dexItemFactory) {
+      buildPartialMethod =
+          dexItemFactory.createMethod(
+              generatedMessageLiteBuilderType,
+              dexItemFactory.createProto(generatedMessageLiteType),
+              "buildPartial");
+    }
+  }
 }
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 55b880b..4cacde6 100644
--- a/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java
+++ b/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java
@@ -25,6 +25,7 @@
 import com.android.tools.r8.graph.DexReference;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.DirectMappedDexApplication;
+import com.android.tools.r8.ir.analysis.proto.GeneratedMessageLiteBuilderShrinker;
 import com.android.tools.r8.logging.Log;
 import com.android.tools.r8.utils.Consumer3;
 import com.android.tools.r8.utils.InternalOptions;
@@ -280,6 +281,10 @@
       BottomUpClassHierarchyTraversal.forAllClasses(appView)
           .visit(appView.appInfo().classes(), this::propagateAssumeRules);
     }
+    if (appView.options().protoShrinking().enableGeneratedMessageLiteBuilderShrinking) {
+      GeneratedMessageLiteBuilderShrinker.addInliningHeuristicsForBuilderInlining(
+          appView, alwaysInline, neverInline);
+    }
     assert Sets.intersection(neverInline, alwaysInline).isEmpty()
             && Sets.intersection(neverInline, forceInline).isEmpty()
         : "A method cannot be marked as both -neverinline and -forceinline/-alwaysinline.";
diff --git a/src/main/java/com/android/tools/r8/utils/InternalOptions.java b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
index 27fbefe..8faa274 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -933,11 +933,16 @@
     public boolean enableGeneratedMessageLiteShrinking =
         System.getProperty("com.android.tools.r8.generatedMessageLiteShrinking") != null;
 
+    public boolean enableGeneratedMessageLiteBuilderShrinking =
+        System.getProperty("com.android.tools.r8.generatedMessageLiteBuilderShrinking") != null;
+
     public boolean traverseOneOfAndRepeatedProtoFields =
         System.getProperty("com.android.tools.r8.traverseOneOfAndRepeatedProtoFields") == null;
 
     public boolean isProtoShrinkingEnabled() {
-      return enableGeneratedExtensionRegistryShrinking || enableGeneratedMessageLiteShrinking;
+      return enableGeneratedExtensionRegistryShrinking
+          || enableGeneratedMessageLiteShrinking
+          || enableGeneratedMessageLiteBuilderShrinking;
     }
   }
 
diff --git a/src/test/java/com/android/tools/r8/internal/proto/Proto2BuilderShrinkingTest.java b/src/test/java/com/android/tools/r8/internal/proto/Proto2BuilderShrinkingTest.java
index 07ccabd..ff0d4bd 100644
--- a/src/test/java/com/android/tools/r8/internal/proto/Proto2BuilderShrinkingTest.java
+++ b/src/test/java/com/android/tools/r8/internal/proto/Proto2BuilderShrinkingTest.java
@@ -35,7 +35,8 @@
 
   @Parameterized.Parameters(name = "{1}, enable minification: {0}")
   public static List<Object[]> data() {
-    return buildParameters(BooleanUtils.values(), getTestParameters().withAllRuntimes().build());
+    return buildParameters(
+        BooleanUtils.values(), getTestParameters().withAllRuntimesAndApiLevels().build());
   }
 
   public Proto2BuilderShrinkingTest(boolean enableMinification, TestParameters parameters) {
@@ -54,15 +55,16 @@
         .addOptionsModification(
             options -> {
               options.enableFieldBitAccessAnalysis = true;
-              options.protoShrinking().enableGeneratedMessageLiteShrinking = true;
               options.protoShrinking().enableGeneratedExtensionRegistryShrinking = true;
+              options.protoShrinking().enableGeneratedMessageLiteShrinking = true;
+              options.protoShrinking().enableGeneratedMessageLiteBuilderShrinking = true;
               options.enableStringSwitchConversion = true;
             })
         .allowAccessModification()
         .allowUnusedProguardConfigurationRules()
         .enableInliningAnnotations()
         .minification(enableMinification)
-        .setMinApi(parameters.getRuntime())
+        .setMinApi(parameters.getApiLevel())
         .compile()
         .inspect(this::inspect)
         .run(parameters.getRuntime(), TEST_CLASS)
diff --git a/src/test/java/com/android/tools/r8/internal/proto/Proto2ShrinkingTest.java b/src/test/java/com/android/tools/r8/internal/proto/Proto2ShrinkingTest.java
index 2071b82..aed2c26 100644
--- a/src/test/java/com/android/tools/r8/internal/proto/Proto2ShrinkingTest.java
+++ b/src/test/java/com/android/tools/r8/internal/proto/Proto2ShrinkingTest.java
@@ -83,8 +83,9 @@
             .addOptionsModification(
                 options -> {
                   options.enableFieldBitAccessAnalysis = true;
-                  options.protoShrinking().enableGeneratedMessageLiteShrinking = true;
                   options.protoShrinking().enableGeneratedExtensionRegistryShrinking = true;
+                  options.protoShrinking().enableGeneratedMessageLiteShrinking = true;
+                  options.protoShrinking().enableGeneratedMessageLiteBuilderShrinking = true;
                   options.enableStringSwitchConversion = true;
                 })
             .allowAccessModification(allowAccessModification)
diff --git a/src/test/java/com/android/tools/r8/internal/proto/Proto3ShrinkingTest.java b/src/test/java/com/android/tools/r8/internal/proto/Proto3ShrinkingTest.java
index 7a67427..faa91e5 100644
--- a/src/test/java/com/android/tools/r8/internal/proto/Proto3ShrinkingTest.java
+++ b/src/test/java/com/android/tools/r8/internal/proto/Proto3ShrinkingTest.java
@@ -57,8 +57,10 @@
         .addKeepRuleFiles(PROTOBUF_LITE_PROGUARD_RULES)
         .addOptionsModification(
             options -> {
-              options.protoShrinking().enableGeneratedMessageLiteShrinking = true;
+              options.enableFieldBitAccessAnalysis = true;
               options.protoShrinking().enableGeneratedExtensionRegistryShrinking = true;
+              options.protoShrinking().enableGeneratedMessageLiteShrinking = true;
+              options.protoShrinking().enableGeneratedMessageLiteBuilderShrinking = true;
               options.enableStringSwitchConversion = true;
             })
         .allowAccessModification(allowAccessModification)
diff --git a/tools/run_on_app.py b/tools/run_on_app.py
index add4b77..0941623 100755
--- a/tools/run_on_app.py
+++ b/tools/run_on_app.py
@@ -512,6 +512,7 @@
       extra_args.append('-Dcom.android.tools.r8.fieldBitAccessAnalysis=1')
       extra_args.append('-Dcom.android.tools.r8.generatedExtensionRegistryShrinking=1')
       extra_args.append('-Dcom.android.tools.r8.generatedMessageLiteShrinking=1')
+      extra_args.append('-Dcom.android.tools.r8.generatedMessageLiteBuilderShrinking=1')
       extra_args.append('-Dcom.android.tools.r8.stringSwitchConversion=1')
       extra_args.append('-Dcom.android.tools.r8.traverseOneOfAndRepeatedProtoFields=0')