Optimize calls to dynamicMethod() with known MethodToInvoke after class inlining

Change-Id: If6421d5818d1882d00361b3c0d2696b8c4549574
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
index 4660f87..552fa07 100644
--- 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
@@ -4,6 +4,7 @@
 
 package com.android.tools.r8.ir.analysis.proto;
 
+import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
 import com.android.tools.r8.graph.AppInfoWithSubtyping;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexEncodedMethod;
@@ -11,8 +12,23 @@
 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.ir.analysis.type.ClassTypeLatticeElement;
+import com.android.tools.r8.ir.analysis.type.TypeAnalysis;
+import com.android.tools.r8.ir.code.CheckCast;
+import com.android.tools.r8.ir.code.IRCode;
+import com.android.tools.r8.ir.code.Instruction;
+import com.android.tools.r8.ir.code.InstructionListIterator;
+import com.android.tools.r8.ir.code.InvokeVirtual;
+import com.android.tools.r8.ir.code.Value;
 import com.android.tools.r8.ir.conversion.CallGraph.Node;
+import com.android.tools.r8.ir.conversion.MethodProcessor;
+import com.android.tools.r8.ir.optimize.CodeRewriter;
+import com.android.tools.r8.ir.optimize.Inliner;
+import com.android.tools.r8.ir.optimize.Inliner.Reason;
+import com.android.tools.r8.ir.optimize.info.OptimizationFeedback;
+import com.android.tools.r8.ir.optimize.inliner.FixedInliningReasonStrategy;
 import com.android.tools.r8.utils.PredicateSet;
+import com.google.common.collect.Sets;
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Map;
@@ -23,9 +39,12 @@
 //  references a dead proto builder.
 public class GeneratedMessageLiteBuilderShrinker {
 
+  private final AppView<? extends AppInfoWithSubtyping> appView;
   private final ProtoReferences references;
 
-  GeneratedMessageLiteBuilderShrinker(ProtoReferences references) {
+  GeneratedMessageLiteBuilderShrinker(
+      AppView<? extends AppInfoWithSubtyping> appView, ProtoReferences references) {
+    this.appView = appView;
     this.references = references;
   }
 
@@ -64,6 +83,98 @@
     }
   }
 
+  public void inlineCallsToDynamicMethod(
+      DexEncodedMethod method,
+      IRCode code,
+      CodeRewriter codeRewriter,
+      OptimizationFeedback feedback,
+      MethodProcessor methodProcessor,
+      Inliner inliner) {
+    if (method.method.toSourceString().contains("proto2.BuilderWithReusedSettersTestClass")) {
+      System.out.println();
+    }
+    strengthenCheckCastInstructions(code);
+
+    ProtoInliningReasonStrategy inliningReasonStrategy =
+        new ProtoInliningReasonStrategy(appView, new FixedInliningReasonStrategy(Reason.NEVER));
+    inliner.performInlining(method, code, feedback, methodProcessor, inliningReasonStrategy);
+
+    // Run the enum optimization to optimize all Enum.ordinal() invocations. This is required to
+    // get rid of the enum switch in dynamicMethod().
+    if (appView.options().enableEnumValueOptimization) {
+      codeRewriter.rewriteConstantEnumMethodCalls(code);
+    }
+  }
+
+  /**
+   * This method tries to strengthen the type of check-cast instructions that cast a value to
+   * GeneratedMessageLite.
+   *
+   * <p>New proto messages are created by calling dynamicMethod(MethodToInvoke.NEW_MUTABLE_INSTANCE)
+   * and casting the result to GeneratedMessageLite.
+   *
+   * <p>If we encounter the following pattern, then we cannot inline the second call to
+   * dynamicMethod, because we don't have a precise receiver type.
+   *
+   * <pre>
+   *   GeneratedMessageLite msg =
+   *       (GeneratedMessageLite)
+   *           Message.DEFAULT_INSTANCE.dynamicMethod(MethodToInvoke.NEW_MUTABLE_INSTANCE);
+   *   GeneratedMessageLite msg2 =
+   *       (GeneratedMessageLite) msg.dynamicMethod(MethodToInvoke.NEW_MUTABLE_INSTANCE);
+   * </pre>
+   *
+   * <p>This method therefore optimizes the code above into:
+   *
+   * <pre>
+   *   Message msg =
+   *       (Message) Message.DEFAULT_INSTANCE.dynamicMethod(MethodToInvoke.NEW_MUTABLE_INSTANCE);
+   *   Message msg2 = (Message) msg.dynamicMethod(MethodToInvoke.NEW_MUTABLE_INSTANCE);
+   * </pre>
+   *
+   * <p>This is assuming that calling dynamicMethod() on a proto message with
+   * MethodToInvoke.NEW_MUTABLE_INSTANCE will create an instance of the enclosing class.
+   */
+  private void strengthenCheckCastInstructions(IRCode code) {
+    Set<Value> affectedValues = Sets.newIdentityHashSet();
+    InstructionListIterator instructionIterator = code.instructionListIterator();
+    CheckCast checkCast;
+    while ((checkCast = instructionIterator.nextUntil(Instruction::isCheckCast)) != null) {
+      if (checkCast.getType() != references.generatedMessageLiteType) {
+        continue;
+      }
+      Value root = checkCast.object().getAliasedValue();
+      if (root.isPhi() || !root.definition.isInvokeVirtual()) {
+        continue;
+      }
+      InvokeVirtual invoke = root.definition.asInvokeVirtual();
+      DexMethod invokedMethod = invoke.getInvokedMethod();
+      if (!references.isDynamicMethod(invokedMethod)
+          && !references.isDynamicMethodBridge(invokedMethod)) {
+        continue;
+      }
+      assert invokedMethod.proto.parameters.values[0] == references.methodToInvokeType;
+      Value methodToInvokeValue = invoke.arguments().get(1);
+      if (!references.methodToInvokeMembers.isNewMutableInstanceEnum(methodToInvokeValue)) {
+        continue;
+      }
+      ClassTypeLatticeElement receiverType =
+          invoke.getReceiver().getDynamicUpperBoundType(appView).asClassTypeLatticeElement();
+      if (receiverType != null) {
+        AppInfoWithClassHierarchy appInfo = appView.appInfo();
+        DexType rawReceiverType = receiverType.getClassType();
+        if (appInfo.isStrictSubtypeOf(rawReceiverType, references.generatedMessageLiteType)) {
+          Value dest = code.createValue(receiverType.asMaybeNull(), checkCast.getLocalInfo());
+          CheckCast replacement = new CheckCast(dest, checkCast.object(), rawReceiverType);
+          instructionIterator.replaceCurrentInstruction(replacement, affectedValues);
+        }
+      }
+    }
+    if (!affectedValues.isEmpty()) {
+      new TypeAnalysis(appView).narrowing(affectedValues);
+    }
+  }
+
   private static class RootSetExtension {
 
     private final AppView<? extends AppInfoWithSubtyping> appView;
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/proto/ProtoInliningReasonStrategy.java b/src/main/java/com/android/tools/r8/ir/analysis/proto/ProtoInliningReasonStrategy.java
index 4d04a73..56fd8e9 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/proto/ProtoInliningReasonStrategy.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/proto/ProtoInliningReasonStrategy.java
@@ -32,7 +32,7 @@
 
   @Override
   public Reason computeInliningReason(InvokeMethod invoke, DexEncodedMethod target) {
-    return references.isDynamicMethod(target)
+    return references.isDynamicMethod(target) || references.isDynamicMethodBridge(target)
         ? computeInliningReasonForDynamicMethod(invoke)
         : parent.computeInliningReason(invoke, target);
   }
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 c90eaf2..aaf3483 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
@@ -12,6 +12,7 @@
 import com.android.tools.r8.graph.DexProto;
 import com.android.tools.r8.graph.DexString;
 import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.ir.code.Value;
 
 public class ProtoReferences {
 
@@ -195,6 +196,17 @@
               methodToInvokeType, methodToInvokeType, "SET_MEMOIZED_IS_INITIALIZED");
     }
 
+    public boolean isNewMutableInstanceEnum(DexField field) {
+      return field == newMutableInstanceField;
+    }
+
+    public boolean isNewMutableInstanceEnum(Value value) {
+      Value root = value.getAliasedValue();
+      return !root.isPhi()
+          && root.definition.isStaticGet()
+          && isNewMutableInstanceEnum(root.definition.asStaticGet().getField());
+    }
+
     public boolean isMethodToInvokeWithSimpleBody(DexField field) {
       return field == getDefaultInstanceField
           || field == getMemoizedIsInitializedField
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/proto/ProtoShrinker.java b/src/main/java/com/android/tools/r8/ir/analysis/proto/ProtoShrinker.java
index d7ba0cc..b7b8ef1 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/proto/ProtoShrinker.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/proto/ProtoShrinker.java
@@ -32,7 +32,7 @@
             : null;
     this.generatedMessageLiteBuilderShrinker =
         appView.options().protoShrinking().enableGeneratedMessageLiteBuilderShrinking
-            ? new GeneratedMessageLiteBuilderShrinker(references)
+            ? new GeneratedMessageLiteBuilderShrinker(appView, references)
             : null;
     this.references = references;
   }
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/type/ReferenceTypeLatticeElement.java b/src/main/java/com/android/tools/r8/ir/analysis/type/ReferenceTypeLatticeElement.java
index 91086e1..b888ae5 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/type/ReferenceTypeLatticeElement.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/type/ReferenceTypeLatticeElement.java
@@ -83,6 +83,10 @@
     return getOrCreateVariant(Nullability.definitelyNotNull());
   }
 
+  public TypeLatticeElement asMaybeNull() {
+    return getOrCreateVariant(Nullability.maybeNull());
+  }
+
   @Override
   public boolean isReference() {
     return true;
diff --git a/src/main/java/com/android/tools/r8/ir/code/BasicBlockInstructionListIterator.java b/src/main/java/com/android/tools/r8/ir/code/BasicBlockInstructionListIterator.java
index 085c936..301be93 100644
--- a/src/main/java/com/android/tools/r8/ir/code/BasicBlockInstructionListIterator.java
+++ b/src/main/java/com/android/tools/r8/ir/code/BasicBlockInstructionListIterator.java
@@ -181,7 +181,7 @@
   }
 
   @Override
-  public void replaceCurrentInstruction(Instruction newInstruction) {
+  public void replaceCurrentInstruction(Instruction newInstruction, Set<Value> affectedValues) {
     if (current == null) {
       throw new IllegalStateException();
     }
@@ -190,6 +190,9 @@
     }
     if (current.outValue() != null && current.outValue().isUsed()) {
       assert newInstruction.outValue() != null;
+      if (affectedValues != null) {
+        current.outValue().addAffectedValuesTo(affectedValues);
+      }
       current.outValue().replaceUsers(newInstruction.outValue());
     }
     current.moveDebugValues(newInstruction);
diff --git a/src/main/java/com/android/tools/r8/ir/code/IRCodeInstructionListIterator.java b/src/main/java/com/android/tools/r8/ir/code/IRCodeInstructionListIterator.java
index fdea0f6..f070e0f 100644
--- a/src/main/java/com/android/tools/r8/ir/code/IRCodeInstructionListIterator.java
+++ b/src/main/java/com/android/tools/r8/ir/code/IRCodeInstructionListIterator.java
@@ -147,8 +147,8 @@
   }
 
   @Override
-  public void replaceCurrentInstruction(Instruction newInstruction) {
-    instructionIterator.replaceCurrentInstruction(newInstruction);
+  public void replaceCurrentInstruction(Instruction newInstruction, Set<Value> affectedValues) {
+    instructionIterator.replaceCurrentInstruction(newInstruction, affectedValues);
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/InstructionListIterator.java b/src/main/java/com/android/tools/r8/ir/code/InstructionListIterator.java
index 315568e..a8b539e 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InstructionListIterator.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InstructionListIterator.java
@@ -17,6 +17,11 @@
 public interface InstructionListIterator
     extends InstructionIterator, ListIterator<Instruction>, PreviousUntilIterator<Instruction> {
 
+  /** See {@link #replaceCurrentInstruction(Instruction, Set)}. */
+  default void replaceCurrentInstruction(Instruction newInstruction) {
+    replaceCurrentInstruction(newInstruction, null);
+  }
+
   /**
    * Replace the current instruction (aka the {@link Instruction} returned by the previous call to
    * {@link #next} with the passed in <code>newInstruction</code>.
@@ -31,8 +36,9 @@
    * <p>The debug information of the current instruction will be attached to the new instruction.
    *
    * @param newInstruction the instruction to insert instead of the current.
+   * @param affectedValues if non-null, all users of the out value will be added to this set.
    */
-  void replaceCurrentInstruction(Instruction newInstruction);
+  void replaceCurrentInstruction(Instruction newInstruction, Set<Value> affectedValues);
 
   // Do not show a deprecation warning for InstructionListIterator.remove().
   @SuppressWarnings("deprecation")
diff --git a/src/main/java/com/android/tools/r8/ir/code/LinearFlowInstructionListIterator.java b/src/main/java/com/android/tools/r8/ir/code/LinearFlowInstructionListIterator.java
index 3a098ca..6de9e58 100644
--- a/src/main/java/com/android/tools/r8/ir/code/LinearFlowInstructionListIterator.java
+++ b/src/main/java/com/android/tools/r8/ir/code/LinearFlowInstructionListIterator.java
@@ -36,8 +36,8 @@
   }
 
   @Override
-  public void replaceCurrentInstruction(Instruction newInstruction) {
-    currentBlockIterator.replaceCurrentInstruction(newInstruction);
+  public void replaceCurrentInstruction(Instruction newInstruction, Set<Value> affectedValues) {
+    currentBlockIterator.replaceCurrentInstruction(newInstruction, affectedValues);
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/NextUntilIterator.java b/src/main/java/com/android/tools/r8/ir/code/NextUntilIterator.java
index d15376e..555db34 100644
--- a/src/main/java/com/android/tools/r8/ir/code/NextUntilIterator.java
+++ b/src/main/java/com/android/tools/r8/ir/code/NextUntilIterator.java
@@ -11,14 +11,14 @@
   /**
    * Continue to call {@link #next} while {@code predicate} tests {@code false}.
    *
-   * @returns the item that matched the predicate or {@code null} if all items fail
-   * the predicate test
+   * @returns the item that matched the predicate or {@code null} if all items fail the predicate
+   *     test
    */
-  default T nextUntil(Predicate<T> predicate) {
+  default <S extends T> S nextUntil(Predicate<T> predicate) {
     while (hasNext()) {
       T item = next();
       if (predicate.test(item)) {
-        return item;
+        return (S) item;
       }
     }
     return null;
diff --git a/src/main/java/com/android/tools/r8/ir/code/Value.java b/src/main/java/com/android/tools/r8/ir/code/Value.java
index 3079026..bba9148 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Value.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Value.java
@@ -45,6 +45,7 @@
 import java.util.Map;
 import java.util.Map.Entry;
 import java.util.Set;
+import java.util.function.Consumer;
 import java.util.function.Predicate;
 
 public class Value implements Comparable<Value> {
@@ -642,13 +643,21 @@
   // Returns the set of Value that are affected if the current value's type lattice is updated.
   public Set<Value> affectedValues() {
     ImmutableSet.Builder<Value> affectedValues = ImmutableSet.builder();
+    forEachAffectedValue(affectedValues::add);
+    return affectedValues.build();
+  }
+
+  public void addAffectedValuesTo(Set<Value> affectedValues) {
+    forEachAffectedValue(affectedValues::add);
+  }
+
+  public void forEachAffectedValue(Consumer<Value> consumer) {
     for (Instruction user : uniqueUsers()) {
-      if (user.outValue() != null) {
-        affectedValues.add(user.outValue());
+      if (user.hasOutValue()) {
+        consumer.accept(user.outValue());
       }
     }
-    affectedValues.addAll(uniquePhiUsers());
-    return affectedValues.build();
+    uniquePhiUsers().forEach(consumer::accept);
   }
 
   public void replaceUsers(Value newValue) {
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
index 3547e12..e501c31 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
@@ -1317,6 +1317,7 @@
           stringOptimizer,
           method,
           code,
+          feedback,
           methodProcessor,
           inliner,
           Suppliers.memoize(
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/DefaultInliningOracle.java b/src/main/java/com/android/tools/r8/ir/optimize/DefaultInliningOracle.java
index c7e511a..145bb62 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/DefaultInliningOracle.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/DefaultInliningOracle.java
@@ -16,7 +16,6 @@
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.ir.analysis.ClassInitializationAnalysis;
-import com.android.tools.r8.ir.analysis.proto.ProtoInliningReasonStrategy;
 import com.android.tools.r8.ir.code.BasicBlock;
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.InstancePut;
@@ -32,7 +31,6 @@
 import com.android.tools.r8.ir.optimize.Inliner.InlineeWithReason;
 import com.android.tools.r8.ir.optimize.Inliner.Reason;
 import com.android.tools.r8.ir.optimize.info.OptimizationFeedback;
-import com.android.tools.r8.ir.optimize.inliner.DefaultInliningReasonStrategy;
 import com.android.tools.r8.ir.optimize.inliner.InliningReasonStrategy;
 import com.android.tools.r8.ir.optimize.inliner.WhyAreYouNotInliningReporter;
 import com.android.tools.r8.logging.Log;
@@ -64,6 +62,7 @@
   DefaultInliningOracle(
       AppView<AppInfoWithLiveness> appView,
       Inliner inliner,
+      InliningReasonStrategy inliningReasonStrategy,
       DexEncodedMethod method,
       IRCode code,
       MethodProcessor methodProcessor,
@@ -71,20 +70,13 @@
       int inliningInstructionAllowance) {
     this.appView = appView;
     this.inliner = inliner;
+    this.reasonStrategy = inliningReasonStrategy;
     this.method = method;
     this.code = code;
     this.methodProcessor = methodProcessor;
     this.isProcessedConcurrently = methodProcessor::isProcessedConcurrently;
     this.inliningInstructionLimit = inliningInstructionLimit;
     this.instructionAllowance = inliningInstructionAllowance;
-
-    DefaultInliningReasonStrategy defaultInliningReasonStrategy =
-        new DefaultInliningReasonStrategy(
-            appView, methodProcessor.getCallSiteInformation(), inliner);
-    this.reasonStrategy =
-        appView.withGeneratedMessageLiteShrinker(
-            ignore -> new ProtoInliningReasonStrategy(appView, defaultInliningReasonStrategy),
-            defaultInliningReasonStrategy);
   }
 
   @Override
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 ed35f73..eeff808 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
@@ -19,6 +19,7 @@
 import com.android.tools.r8.graph.GraphLense;
 import com.android.tools.r8.graph.NestMemberClassAttribute;
 import com.android.tools.r8.ir.analysis.ClassInitializationAnalysis;
+import com.android.tools.r8.ir.analysis.proto.ProtoInliningReasonStrategy;
 import com.android.tools.r8.ir.analysis.type.Nullability;
 import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
 import com.android.tools.r8.ir.code.BasicBlock;
@@ -44,7 +45,9 @@
 import com.android.tools.r8.ir.desugar.TwrCloseResourceRewriter;
 import com.android.tools.r8.ir.optimize.info.OptimizationFeedback;
 import com.android.tools.r8.ir.optimize.info.OptimizationFeedbackIgnore;
+import com.android.tools.r8.ir.optimize.inliner.DefaultInliningReasonStrategy;
 import com.android.tools.r8.ir.optimize.inliner.InliningIRProvider;
+import com.android.tools.r8.ir.optimize.inliner.InliningReasonStrategy;
 import com.android.tools.r8.ir.optimize.inliner.NopWhyAreYouNotInliningReporter;
 import com.android.tools.r8.ir.optimize.inliner.WhyAreYouNotInliningReporter;
 import com.android.tools.r8.ir.optimize.lambda.LambdaMerger;
@@ -830,12 +833,25 @@
     performInliningImpl(
         oracle, oracle, method, code, OptimizationFeedbackIgnore.getInstance(), inliningIRProvider);
   }
-
   public void performInlining(
       DexEncodedMethod method,
       IRCode code,
       OptimizationFeedback feedback,
       MethodProcessor methodProcessor) {
+    performInlining(
+        method,
+        code,
+        feedback,
+        methodProcessor,
+        createDefaultInliningReasonStrategy(methodProcessor));
+  }
+
+  public void performInlining(
+      DexEncodedMethod method,
+      IRCode code,
+      OptimizationFeedback feedback,
+      MethodProcessor methodProcessor,
+      InliningReasonStrategy inliningReasonStrategy) {
     InternalOptions options = appView.options();
     DefaultInliningOracle oracle =
         createDefaultOracle(
@@ -843,21 +859,48 @@
             code,
             methodProcessor,
             options.inliningInstructionLimit,
-            options.inliningInstructionAllowance - numberOfInstructions(code));
+            options.inliningInstructionAllowance - numberOfInstructions(code),
+            inliningReasonStrategy);
     InliningIRProvider inliningIRProvider = new InliningIRProvider(appView, method, code);
     assert inliningIRProvider.verifyIRCacheIsEmpty();
     performInliningImpl(oracle, oracle, method, code, feedback, inliningIRProvider);
   }
 
+  public InliningReasonStrategy createDefaultInliningReasonStrategy(
+      MethodProcessor methodProcessor) {
+    DefaultInliningReasonStrategy defaultInliningReasonStrategy =
+        new DefaultInliningReasonStrategy(appView, methodProcessor.getCallSiteInformation(), this);
+    return appView.withGeneratedMessageLiteShrinker(
+        ignore -> new ProtoInliningReasonStrategy(appView, defaultInliningReasonStrategy),
+        defaultInliningReasonStrategy);
+  }
+
   public DefaultInliningOracle createDefaultOracle(
       DexEncodedMethod method,
       IRCode code,
       MethodProcessor methodProcessor,
       int inliningInstructionLimit,
       int inliningInstructionAllowance) {
+    return createDefaultOracle(
+        method,
+        code,
+        methodProcessor,
+        inliningInstructionLimit,
+        inliningInstructionAllowance,
+        createDefaultInliningReasonStrategy(methodProcessor));
+  }
+
+  public DefaultInliningOracle createDefaultOracle(
+      DexEncodedMethod method,
+      IRCode code,
+      MethodProcessor methodProcessor,
+      int inliningInstructionLimit,
+      int inliningInstructionAllowance,
+      InliningReasonStrategy inliningReasonStrategy) {
     return new DefaultInliningOracle(
         appView,
         this,
+        inliningReasonStrategy,
         method,
         code,
         methodProcessor,
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 c71006f..c4a761b 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
@@ -4,10 +4,14 @@
 
 package com.android.tools.r8.ir.optimize.classinliner;
 
+import static com.android.tools.r8.graph.DexProgramClass.asProgramClassOrNull;
+
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.ir.analysis.type.TypeAnalysis;
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.Instruction;
@@ -18,6 +22,7 @@
 import com.android.tools.r8.ir.optimize.Inliner;
 import com.android.tools.r8.ir.optimize.InliningOracle;
 import com.android.tools.r8.ir.optimize.classinliner.InlineCandidateProcessor.IllegalClassInlinerStateException;
+import com.android.tools.r8.ir.optimize.info.OptimizationFeedback;
 import com.android.tools.r8.ir.optimize.inliner.InliningIRProvider;
 import com.android.tools.r8.ir.optimize.string.StringOptimizer;
 import com.android.tools.r8.logging.Log;
@@ -161,6 +166,7 @@
       StringOptimizer stringOptimizer,
       DexEncodedMethod method,
       IRCode code,
+      OptimizationFeedback feedback,
       MethodProcessor methodProcessor,
       Inliner inliner,
       Supplier<InliningOracle> defaultOracle) {
@@ -174,7 +180,7 @@
     // We loop inlining iterations until there was no inlining, but still use same set
     // of roots to avoid infinite inlining. Looping makes possible for some roots to
     // become eligible after other roots are inlined.
-
+    boolean anyInlinedGeneratedMessageLiteBuilders = false;
     boolean anyInlinedMethods = false;
     boolean repeat;
     do {
@@ -233,6 +239,15 @@
           continue;
         }
 
+        if (appView.protoShrinker() != null && root.isNewInstance()) {
+          DexType instantiatedType = root.asNewInstance().clazz;
+          DexProgramClass clazz = asProgramClassOrNull(appView.definitionFor(instantiatedType));
+          if (clazz != null
+              && appView.protoShrinker().references.isGeneratedMessageLiteBuilder(clazz)) {
+            anyInlinedGeneratedMessageLiteBuilders = true;
+          }
+        }
+
         // Inline the class instance.
         try {
           anyInlinedMethods |= processor.processInlining(code, defaultOracle, inliningIRProvider);
@@ -262,6 +277,14 @@
       }
     } while (repeat);
 
+    if (anyInlinedGeneratedMessageLiteBuilders) {
+      // Inline all calls to dynamicMethod() where the given MethodToInvoke is considered simple.
+      appView.withGeneratedMessageLiteBuilderShrinker(
+          shrinker ->
+              shrinker.inlineCallsToDynamicMethod(
+                  method, code, codeRewriter, feedback, methodProcessor, inliner));
+    }
+
     if (anyInlinedMethods) {
       // If a method was inlined we may be able to remove check-cast instructions because we may
       // have more information about the types of the arguments at the call site. This is
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/inliner/FixedInliningReasonStrategy.java b/src/main/java/com/android/tools/r8/ir/optimize/inliner/FixedInliningReasonStrategy.java
new file mode 100644
index 0000000..90fc4f0
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/optimize/inliner/FixedInliningReasonStrategy.java
@@ -0,0 +1,23 @@
+// Copyright (c) 2020, 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.optimize.inliner;
+
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.ir.code.InvokeMethod;
+import com.android.tools.r8.ir.optimize.Inliner.Reason;
+
+public class FixedInliningReasonStrategy implements InliningReasonStrategy {
+
+  private final Reason reason;
+
+  public FixedInliningReasonStrategy(Reason reason) {
+    this.reason = reason;
+  }
+
+  @Override
+  public Reason computeInliningReason(InvokeMethod invoke, DexEncodedMethod target) {
+    return reason;
+  }
+}
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 a717189..b17cae5 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
@@ -7,6 +7,7 @@
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
 import static org.hamcrest.CoreMatchers.not;
 import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertTrue;
 
 import com.android.tools.r8.R8TestCompileResult;
 import com.android.tools.r8.TestParameters;
@@ -14,6 +15,7 @@
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.utils.StringUtils;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.InstructionSubject;
 import com.android.tools.r8.utils.codeinspector.MethodSubject;
 import com.google.common.collect.ImmutableList;
 import java.nio.file.Path;
@@ -179,13 +181,14 @@
     for (String main : mains) {
       MethodSubject mainMethodSubject = outputInspector.clazz(main).mainMethod();
       assertThat(mainMethodSubject, isPresent());
-      // TODO(christofferqa): Enable assertion.
-      // assertTrue(
-      //     mainMethodSubject
-      //         .streamInstructions()
-      //         .filter(InstructionSubject::isStaticGet)
-      //         .map(instruction -> instruction.getField().type)
-      //         .noneMatch(methodToInvokeType::equals));
+      assertTrue(
+          main,
+          mainMethodSubject
+              .streamInstructions()
+              .filter(InstructionSubject::isStaticGet)
+              .map(instruction -> instruction.getField().type)
+              .noneMatch(methodToInvokeType::equals));
+      assertTrue(mainMethodSubject.streamInstructions().noneMatch(InstructionSubject::isSwitch));
     }
   }
 }
diff --git a/src/test/java/com/android/tools/r8/ir/regalloc/RegisterMoveSchedulerTest.java b/src/test/java/com/android/tools/r8/ir/regalloc/RegisterMoveSchedulerTest.java
index 555b94c..a449c5d 100644
--- a/src/test/java/com/android/tools/r8/ir/regalloc/RegisterMoveSchedulerTest.java
+++ b/src/test/java/com/android/tools/r8/ir/regalloc/RegisterMoveSchedulerTest.java
@@ -44,7 +44,7 @@
     }
 
     @Override
-    public void replaceCurrentInstruction(Instruction newInstruction) {
+    public void replaceCurrentInstruction(Instruction newInstruction, Set<Value> affectedValues) {
       throw new Unimplemented();
     }