Version 2.1.29

Cherry pick: Fix bug in proto builder optimization
CL: https://r8-review.googlesource.com/c/r8/+/51692

Cherry pick: Reproduce proto builder optimization bug
CL: https://r8-review.googlesource.com/c/r8/+/51702

Bug: 155416893
Change-Id: I8fbf0a3224d32e65bfd2da7010a130e4ad5cdc04
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java
index 8406706..622224b 100644
--- a/src/main/java/com/android/tools/r8/R8.java
+++ b/src/main/java/com/android/tools/r8/R8.java
@@ -697,7 +697,7 @@
 
           appView.withGeneratedMessageLiteBuilderShrinker(
               shrinker ->
-                  shrinker.removeDeadBuilderReferencesFromDynamicMethods(
+                  shrinker.rewriteDeadBuilderReferencesFromDynamicMethods(
                       appViewWithLiveness, executorService, timing));
 
           if (options.isShrinking()) {
@@ -914,6 +914,10 @@
           shrinker ->
               shrinker.setDeadProtoTypes(appViewWithLiveness.appInfo().getDeadProtoTypes()));
     }
+    appView.withGeneratedMessageLiteBuilderShrinker(
+        shrinker ->
+            shrinker.rewriteDeadBuilderReferencesFromDynamicMethods(
+                appViewWithLiveness, executorService, timing));
     return appViewWithLiveness;
   }
 
diff --git a/src/main/java/com/android/tools/r8/Version.java b/src/main/java/com/android/tools/r8/Version.java
index 283f647..47f1b12 100644
--- a/src/main/java/com/android/tools/r8/Version.java
+++ b/src/main/java/com/android/tools/r8/Version.java
@@ -11,7 +11,7 @@
 
   // This field is accessed from release scripts using simple pattern matching.
   // Therefore, changing this field could break our release scripts.
-  public static final String LABEL = "2.1.28";
+  public static final String LABEL = "2.1.29";
 
   private Version() {
   }
diff --git a/src/main/java/com/android/tools/r8/graph/ClassAccessFlags.java b/src/main/java/com/android/tools/r8/graph/ClassAccessFlags.java
index 030497d..e33792c 100644
--- a/src/main/java/com/android/tools/r8/graph/ClassAccessFlags.java
+++ b/src/main/java/com/android/tools/r8/graph/ClassAccessFlags.java
@@ -167,6 +167,10 @@
     return isSet(Constants.ACC_ABSTRACT);
   }
 
+  public void demoteFromAbstract() {
+    demote(Constants.ACC_ABSTRACT);
+  }
+
   public void setAbstract() {
     set(Constants.ACC_ABSTRACT);
   }
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/FieldAssignmentTracker.java b/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/FieldAssignmentTracker.java
index fbb6443..1481061 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/FieldAssignmentTracker.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/FieldAssignmentTracker.java
@@ -64,8 +64,13 @@
 
   FieldAssignmentTracker(AppView<AppInfoWithLiveness> appView) {
     this.appView = appView;
-    this.fieldAccessGraph = new FieldAccessGraph(appView);
-    this.objectAllocationGraph = new ObjectAllocationGraph(appView);
+    this.fieldAccessGraph = new FieldAccessGraph();
+    this.objectAllocationGraph = new ObjectAllocationGraph();
+  }
+
+  public void initialize() {
+    fieldAccessGraph.initialize(appView);
+    objectAllocationGraph.initialize(appView);
     initializeAbstractInstanceFieldValues();
   }
 
@@ -308,10 +313,11 @@
     private final Reference2IntMap<DexEncodedField> pendingFieldWrites =
         new Reference2IntOpenHashMap<>();
 
-    FieldAccessGraph(AppView<AppInfoWithLiveness> appView) {
+    FieldAccessGraph() {}
+
+    public void initialize(AppView<AppInfoWithLiveness> appView) {
       FieldAccessInfoCollection<?> fieldAccessInfoCollection =
           appView.appInfo().getFieldAccessInfoCollection();
-      fieldAccessInfoCollection.flattenAccessContexts();
       fieldAccessInfoCollection.forEach(
           info -> {
             DexEncodedField field =
@@ -356,7 +362,9 @@
     private final Reference2IntMap<DexProgramClass> pendingObjectAllocations =
         new Reference2IntOpenHashMap<>();
 
-    ObjectAllocationGraph(AppView<AppInfoWithLiveness> appView) {
+    ObjectAllocationGraph() {}
+
+    public void initialize(AppView<AppInfoWithLiveness> appView) {
       ObjectAllocationInfoCollection objectAllocationInfos =
           appView.appInfo().getObjectAllocationInfoCollection();
       objectAllocationInfos.forEachClassWithKnownAllocationSites(
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 deadfa0..c2cd2aa 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,41 +4,48 @@
 
 package com.android.tools.r8.ir.analysis.proto;
 
+import static com.android.tools.r8.graph.DexProgramClass.asProgramClassOrNull;
+import static com.android.tools.r8.ir.analysis.type.Nullability.definitelyNotNull;
+import static com.android.tools.r8.ir.analysis.type.Nullability.maybeNull;
+
 import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexField;
 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.graph.EnumValueInfoMapCollection.EnumValueInfo;
-import com.android.tools.r8.graph.EnumValueInfoMapCollection.EnumValueInfoMap;
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.graph.SubtypingInfo;
+import com.android.tools.r8.graph.analysis.EnqueuerAnalysis;
 import com.android.tools.r8.ir.analysis.type.ClassTypeElement;
 import com.android.tools.r8.ir.analysis.type.TypeAnalysis;
-import com.android.tools.r8.ir.analysis.type.TypeElement;
 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.InvokeDirect;
 import com.android.tools.r8.ir.code.InvokeVirtual;
-import com.android.tools.r8.ir.code.Switch;
+import com.android.tools.r8.ir.code.NewInstance;
+import com.android.tools.r8.ir.code.StaticGet;
 import com.android.tools.r8.ir.code.Value;
 import com.android.tools.r8.ir.conversion.CallGraph.Node;
 import com.android.tools.r8.ir.conversion.IRConverter;
 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.controlflow.SwitchCaseAnalyzer;
 import com.android.tools.r8.ir.optimize.enums.EnumValueOptimizer;
 import com.android.tools.r8.ir.optimize.info.OptimizationFeedback;
 import com.android.tools.r8.ir.optimize.info.OptimizationFeedbackSimple;
 import com.android.tools.r8.ir.optimize.inliner.FixedInliningReasonStrategy;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.shaking.Enqueuer;
+import com.android.tools.r8.shaking.EnqueuerWorklist;
+import com.android.tools.r8.utils.ObjectUtils;
 import com.android.tools.r8.utils.PredicateSet;
 import com.android.tools.r8.utils.ThreadUtils;
 import com.android.tools.r8.utils.Timing;
+import com.google.common.collect.ImmutableList;
 import com.google.common.collect.Sets;
 import java.util.ArrayList;
 import java.util.IdentityHashMap;
@@ -53,6 +60,7 @@
 
   private final AppView<? extends AppInfoWithClassHierarchy> appView;
   private final ProtoReferences references;
+  private final boolean enableAggressiveBuilderOptimization;
 
   private final Map<DexProgramClass, ProgramMethod> builders = new IdentityHashMap<>();
 
@@ -60,11 +68,98 @@
       AppView<? extends AppInfoWithClassHierarchy> appView, ProtoReferences references) {
     this.appView = appView;
     this.references = references;
+    this.enableAggressiveBuilderOptimization = computeEnableAggressiveBuilderOptimization();
+    // If this fails it is likely an unsupported version of the protobuf library.
+    assert enableAggressiveBuilderOptimization;
+  }
+
+  private boolean computeEnableAggressiveBuilderOptimization() {
+    boolean unexpectedGeneratedMessageLiteBuilder =
+        ObjectUtils.getBooleanOrElse(
+            appView
+                .appInfo()
+                .definitionForWithoutExistenceAssert(references.generatedMessageLiteBuilderType),
+            clazz -> clazz.getMethodCollection().hasMethods(DexEncodedMethod::isAbstract),
+            true);
+    boolean unexpectedGeneratedMessageLiteExtendableBuilder =
+        ObjectUtils.getBooleanOrElse(
+            appView
+                .appInfo()
+                .definitionForWithoutExistenceAssert(
+                    references.generatedMessageLiteExtendableBuilderType),
+            clazz -> clazz.getMethodCollection().hasMethods(DexEncodedMethod::isAbstract),
+            true);
+    if (unexpectedGeneratedMessageLiteBuilder) {
+      appView
+          .options()
+          .reporter
+          .warning(
+              "Unexpected implementation of `"
+                  + references.generatedMessageLiteBuilderType.toSourceString()
+                  + "`: disabling aggressive protobuf builder optimization.");
+      return false;
+    }
+    if (unexpectedGeneratedMessageLiteExtendableBuilder) {
+      appView
+          .options()
+          .reporter
+          .warning(
+              "Unexpected implementation of `"
+                  + references.generatedMessageLiteExtendableBuilderType.toSourceString()
+                  + "`: disabling aggressive protobuf builder optimization.");
+      return false;
+    }
+    return true;
+  }
+
+  public EnqueuerAnalysis createEnqueuerAnalysis() {
+    Set<DexProgramClass> seen = Sets.newIdentityHashSet();
+    return new EnqueuerAnalysis() {
+      @Override
+      public void notifyFixpoint(Enqueuer enqueuer, EnqueuerWorklist worklist, Timing timing) {
+        builders.forEach(
+            (builder, dynamicMethod) -> {
+              if (seen.add(builder)) {
+                // This builder class is never used in the program except from dynamicMethod(),
+                // which creates an instance of the builder. Instead of creating an instance of the
+                // builder class, we just instantiate the parent builder class. For this to work,
+                // we make the parent builder non-abstract.
+                DexProgramClass superClass =
+                    asProgramClassOrNull(appView.definitionFor(builder.superType));
+                assert superClass != null;
+                superClass.accessFlags.demoteFromAbstract();
+                if (superClass.type == references.generatedMessageLiteBuilderType) {
+                  // Manually trace `new GeneratedMessageLite.Builder(DEFAULT_INSTANCE)` since we
+                  // haven't rewritten the code yet.
+                  worklist.enqueueTraceNewInstanceAction(
+                      references.generatedMessageLiteBuilderType, dynamicMethod);
+                  worklist.enqueueTraceInvokeDirectAction(
+                      references.generatedMessageLiteBuilderMethods.constructorMethod,
+                      dynamicMethod);
+                } else {
+                  assert superClass.type == references.generatedMessageLiteExtendableBuilderType;
+                  // Manually trace `new GeneratedMessageLite.ExtendableBuilder(DEFAULT_INSTANCE)`
+                  // since we haven't rewritten the code yet.
+                  worklist.enqueueTraceNewInstanceAction(
+                      references.generatedMessageLiteExtendableBuilderType, dynamicMethod);
+                  worklist.enqueueTraceInvokeDirectAction(
+                      references.generatedMessageLiteExtendableBuilderMethods.constructorMethod,
+                      dynamicMethod);
+                }
+                worklist.enqueueTraceStaticFieldRead(
+                    references.getDefaultInstanceField(dynamicMethod.getHolder()), dynamicMethod);
+              }
+            });
+      }
+    };
   }
 
   /** Returns true if an action was deferred. */
   public boolean deferDeadProtoBuilders(
       DexProgramClass clazz, ProgramMethod method, BooleanSupplier register) {
+    if (!enableAggressiveBuilderOptimization) {
+      return false;
+    }
     DexEncodedMethod definition = method.getDefinition();
     if (references.isDynamicMethod(definition) && references.isGeneratedMessageLiteBuilder(clazz)) {
       if (register.getAsBoolean()) {
@@ -77,40 +172,79 @@
   }
 
   /**
-   * Reprocesses each dynamicMethod() that references a dead builder to remove the dead builder
+   * Reprocesses each dynamicMethod() that references a dead builder to rewrite the dead builder
    * references.
    */
-  public void removeDeadBuilderReferencesFromDynamicMethods(
+  public void rewriteDeadBuilderReferencesFromDynamicMethods(
       AppView<AppInfoWithLiveness> appView, ExecutorService executorService, Timing timing)
       throws ExecutionException {
+    if (builders.isEmpty()) {
+      return;
+    }
     timing.begin("Remove dead builder references");
     AppInfoWithLiveness appInfo = appView.appInfo();
     IRConverter converter = new IRConverter(appView, Timing.empty());
-    CodeRewriter codeRewriter = new CodeRewriter(appView, converter);
-    MethodToInvokeSwitchCaseAnalyzer switchCaseAnalyzer = new MethodToInvokeSwitchCaseAnalyzer();
-    if (switchCaseAnalyzer.isInitialized()) {
-      ThreadUtils.processItems(
-          builders.entrySet(),
-          entry -> {
-            if (!appInfo.isLiveProgramClass(entry.getKey())) {
-              removeDeadBuilderReferencesFromDynamicMethod(
-                  appView, entry.getValue(), converter, codeRewriter, switchCaseAnalyzer);
-            }
-          },
-          executorService);
-    }
+    ThreadUtils.processMap(
+        builders,
+        (builder, dynamicMethod) -> {
+          if (!appInfo.isLiveProgramClass(builder)) {
+            rewriteDeadBuilderReferencesFromDynamicMethod(
+                appView, builder, dynamicMethod, converter);
+          }
+        },
+        executorService);
     builders.clear();
     timing.end(); // Remove dead builder references
   }
 
-  private void removeDeadBuilderReferencesFromDynamicMethod(
+  private void rewriteDeadBuilderReferencesFromDynamicMethod(
       AppView<AppInfoWithLiveness> appView,
+      DexProgramClass builder,
       ProgramMethod dynamicMethod,
-      IRConverter converter,
-      CodeRewriter codeRewriter,
-      SwitchCaseAnalyzer switchCaseAnalyzer) {
+      IRConverter converter) {
     IRCode code = dynamicMethod.buildIR(appView);
-    codeRewriter.rewriteSwitch(code, switchCaseAnalyzer);
+    InstructionListIterator instructionIterator = code.instructionListIterator();
+
+    assert builder.superType == references.generatedMessageLiteBuilderType
+        || builder.superType == references.generatedMessageLiteExtendableBuilderType;
+
+    DexField defaultInstanceField = references.getDefaultInstanceField(dynamicMethod.getHolder());
+    Value builderValue =
+        code.createValue(ClassTypeElement.create(builder.superType, definitelyNotNull(), appView));
+    Value defaultInstanceValue =
+        code.createValue(ClassTypeElement.create(defaultInstanceField.type, maybeNull(), appView));
+
+    // Replace `new Message.Builder()` by `new GeneratedMessageLite.Builder()`
+    // (or `new GeneratedMessageLite.ExtendableBuilder()`).
+    NewInstance newInstance =
+        instructionIterator.nextUntil(
+            instruction ->
+                instruction.isNewInstance() && instruction.asNewInstance().clazz == builder.type);
+    assert newInstance != null;
+    instructionIterator.replaceCurrentInstruction(new NewInstance(builder.superType, builderValue));
+
+    // Replace `builder.<init>()` by `builder.<init>(Message.DEFAULT_INSTANCE)`.
+    //
+    // We may also see an accessibility bridge constructor, because the Builder constructor is
+    // private. The accessibility bridge takes null as an argument.
+    InvokeDirect constructorInvoke =
+        instructionIterator.nextUntil(
+            instruction -> {
+              assert instruction.isInvokeDirect() || instruction.isConstNumber();
+              return instruction.isInvokeDirect();
+            });
+    assert constructorInvoke != null;
+    instructionIterator.replaceCurrentInstruction(
+        new StaticGet(defaultInstanceValue, defaultInstanceField));
+    instructionIterator.setInsertionPosition(constructorInvoke.getPosition());
+    instructionIterator.add(
+        new InvokeDirect(
+            builder.superType == references.generatedMessageLiteBuilderType
+                ? references.generatedMessageLiteBuilderMethods.constructorMethod
+                : references.generatedMessageLiteExtendableBuilderMethods.constructorMethod,
+            null,
+            ImmutableList.of(builderValue, defaultInstanceValue)));
+
     converter.removeDeadCodeAndFinalizeIR(
         dynamicMethod, code, OptimizationFeedbackSimple.getInstance(), Timing.empty());
   }
@@ -232,51 +366,6 @@
     }
   }
 
-  private class MethodToInvokeSwitchCaseAnalyzer extends SwitchCaseAnalyzer {
-
-    private final int newBuilderOrdinal;
-
-    private MethodToInvokeSwitchCaseAnalyzer() {
-      EnumValueInfoMap enumValueInfoMap =
-          appView.appInfo().withLiveness().getEnumValueInfoMap(references.methodToInvokeType);
-      if (enumValueInfoMap != null) {
-        EnumValueInfo newBuilderValueInfo =
-            enumValueInfoMap.getEnumValueInfo(references.methodToInvokeMembers.newBuilderField);
-        if (newBuilderValueInfo != null) {
-          newBuilderOrdinal = newBuilderValueInfo.ordinal;
-          return;
-        }
-      }
-      newBuilderOrdinal = -1;
-    }
-
-    public boolean isInitialized() {
-      return newBuilderOrdinal >= 0;
-    }
-
-    @Override
-    public boolean switchCaseIsUnreachable(Switch theSwitch, int index) {
-      if (theSwitch.isStringSwitch()) {
-        assert false : "Unexpected string-switch instruction in dynamicMethod()";
-        return false;
-      }
-      if (index != newBuilderOrdinal) {
-        return false;
-      }
-      Value switchValue = theSwitch.value();
-      if (!switchValue.isDefinedByInstructionSatisfying(Instruction::isInvokeVirtual)) {
-        return false;
-      }
-      InvokeVirtual definition = switchValue.definition.asInvokeVirtual();
-      if (definition.getInvokedMethod() != appView.dexItemFactory().enumMethods.ordinal) {
-        return false;
-      }
-      TypeElement enumType = definition.getReceiver().getType();
-      return enumType.isClassType()
-          && enumType.asClassType().getClassType() == references.methodToInvokeType;
-    }
-  }
-
   private static class RootSetExtension {
 
     private final AppView<? extends AppInfoWithClassHierarchy> 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 ca4ca1e..e8770c7 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
@@ -43,11 +43,19 @@
       return Reason.ALWAYS;
     }
     return references.isDynamicMethod(target) || references.isDynamicMethodBridge(target)
-        ? computeInliningReasonForDynamicMethod(invoke)
+        ? computeInliningReasonForDynamicMethod(invoke, target, context)
         : parent.computeInliningReason(invoke, target, context);
   }
 
-  private Reason computeInliningReasonForDynamicMethod(InvokeMethod invoke) {
+  private Reason computeInliningReasonForDynamicMethod(
+      InvokeMethod invoke, ProgramMethod target, ProgramMethod context) {
+    // Do not allow inlining of dynamicMethod() into a proto library class. This should only happen
+    // if there is exactly one proto message in the program, since we would otherwise not be able
+    // to conclude a single target.
+    if (references.isDynamicMethod(target) && references.isProtoLibraryClass(context.getHolder())) {
+      return Reason.NEVER;
+    }
+
     Value methodToInvokeValue =
         invoke
             .inValues()
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 861c0cd..8fd43ee 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,6 +17,8 @@
 
 public class ProtoReferences {
 
+  private final DexItemFactory dexItemFactory;
+
   public final DexType enumLiteMapType;
   public final DexType extendableMessageType;
   public final DexType extensionDescriptorType;
@@ -25,6 +27,7 @@
   public final DexType generatedMessageLiteType;
   public final DexType generatedMessageLiteBuilderType;
   public final DexType generatedMessageLiteExtendableBuilderType;
+  public final DexType generatedMessageLiteExtendableMessageType;
   public final DexType rawMessageInfoType;
   public final DexType messageLiteType;
   public final DexType methodToInvokeType;
@@ -37,10 +40,13 @@
       generatedMessageLiteExtendableBuilderMethods;
   public final MethodToInvokeMembers methodToInvokeMembers;
 
+  public final DexString defaultInstanceFieldName;
   public final DexString dynamicMethodName;
   public final DexString findLiteExtensionByNumberName;
   public final DexString newBuilderMethodName;
 
+  public final DexString protobufPackageDescriptorPrefix;
+
   public final DexProto dynamicMethodProto;
   public final DexProto findLiteExtensionByNumberProto;
 
@@ -49,6 +55,8 @@
   public final DexMethod rawMessageInfoConstructor;
 
   public ProtoReferences(DexItemFactory factory) {
+    dexItemFactory = factory;
+
     // Types.
     enumLiteMapType = factory.createType("Lcom/google/protobuf/Internal$EnumLiteMap;");
     extendableMessageType =
@@ -63,6 +71,8 @@
         factory.createType("Lcom/google/protobuf/GeneratedMessageLite$Builder;");
     generatedMessageLiteExtendableBuilderType =
         factory.createType("Lcom/google/protobuf/GeneratedMessageLite$ExtendableBuilder;");
+    generatedMessageLiteExtendableMessageType =
+        factory.createType("Lcom/google/protobuf/GeneratedMessageLite$ExtendableMessage;");
     rawMessageInfoType = factory.createType("Lcom/google/protobuf/RawMessageInfo;");
     messageLiteType = factory.createType("Lcom/google/protobuf/MessageLite;");
     methodToInvokeType =
@@ -70,10 +80,14 @@
     wireFormatFieldType = factory.createType("Lcom/google/protobuf/WireFormat$FieldType;");
 
     // Names.
+    defaultInstanceFieldName = factory.createString("DEFAULT_INSTANCE");
     dynamicMethodName = factory.createString("dynamicMethod");
     findLiteExtensionByNumberName = factory.createString("findLiteExtensionByNumber");
     newBuilderMethodName = factory.createString("newBuilder");
 
+    // Other names.
+    protobufPackageDescriptorPrefix = factory.createString("Lcom/google/protobuf/");
+
     // Protos.
     dynamicMethodProto =
         factory.createProto(
@@ -105,6 +119,10 @@
     methodToInvokeMembers = new MethodToInvokeMembers(factory);
   }
 
+  public DexField getDefaultInstanceField(DexProgramClass holder) {
+    return dexItemFactory.createField(holder.type, holder.type, defaultInstanceFieldName);
+  }
+
   public boolean isAbstractGeneratedMessageLiteBuilder(DexProgramClass clazz) {
     return clazz.type == generatedMessageLiteBuilderType
         || clazz.type == generatedMessageLiteExtendableBuilderType;
@@ -123,7 +141,8 @@
   }
 
   public boolean isDynamicMethodBridge(DexMethod method) {
-    return method == generatedMessageLiteMethods.dynamicMethodBridgeMethod;
+    return method == generatedMessageLiteMethods.dynamicMethodBridgeMethod
+        || method == generatedMessageLiteMethods.dynamicMethodBridgeMethodWithObject;
   }
 
   public boolean isDynamicMethodBridge(DexEncodedMethod method) {
@@ -154,6 +173,10 @@
     return method.match(newMessageInfoMethod) || method == rawMessageInfoConstructor;
   }
 
+  public boolean isProtoLibraryClass(DexProgramClass clazz) {
+    return clazz.type.descriptor.startsWith(protobufPackageDescriptorPrefix);
+  }
+
   public class GeneratedExtensionMethods {
 
     public final DexMethod constructor;
@@ -192,6 +215,7 @@
 
     public final DexMethod createBuilderMethod;
     public final DexMethod dynamicMethodBridgeMethod;
+    public final DexMethod dynamicMethodBridgeMethodWithObject;
     public final DexMethod isInitializedMethod;
     public final DexMethod newRepeatedGeneratedExtension;
     public final DexMethod newSingularGeneratedExtension;
@@ -207,6 +231,12 @@
               generatedMessageLiteType,
               dexItemFactory.createProto(dexItemFactory.objectType, methodToInvokeType),
               "dynamicMethod");
+      dynamicMethodBridgeMethodWithObject =
+          dexItemFactory.createMethod(
+              generatedMessageLiteType,
+              dexItemFactory.createProto(
+                  dexItemFactory.objectType, methodToInvokeType, dexItemFactory.objectType),
+              "dynamicMethod");
       isInitializedMethod =
           dexItemFactory.createMethod(
               generatedMessageLiteType,
@@ -241,7 +271,7 @@
     }
   }
 
-  class GeneratedMessageLiteBuilderMethods {
+  public class GeneratedMessageLiteBuilderMethods {
 
     public final DexMethod buildPartialMethod;
     public final DexMethod constructorMethod;
@@ -260,9 +290,10 @@
     }
   }
 
-  class GeneratedMessageLiteExtendableBuilderMethods {
+  public class GeneratedMessageLiteExtendableBuilderMethods {
 
     public final DexMethod buildPartialMethod;
+    public final DexMethod constructorMethod;
 
     private GeneratedMessageLiteExtendableBuilderMethods(DexItemFactory dexItemFactory) {
       buildPartialMethod =
@@ -270,6 +301,12 @@
               generatedMessageLiteExtendableBuilderType,
               dexItemFactory.createProto(extendableMessageType),
               "buildPartial");
+      constructorMethod =
+          dexItemFactory.createMethod(
+              generatedMessageLiteExtendableBuilderType,
+              dexItemFactory.createProto(
+                  dexItemFactory.voidType, generatedMessageLiteExtendableMessageType),
+              dexItemFactory.constructorMethodName);
     }
   }
 
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 b9cc832..b4860aa 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
@@ -151,6 +151,11 @@
   }
 
   @Override
+  public void setInsertionPosition(Position position) {
+    instructionIterator.setInsertionPosition(position);
+  }
+
+  @Override
   public void replaceCurrentInstruction(Instruction newInstruction, Set<Value> affectedValues) {
     instructionIterator.replaceCurrentInstruction(newInstruction, affectedValues);
   }
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 844ccce..c0f75e9 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
@@ -675,6 +675,9 @@
     // 2) Revisit DexEncodedMethods for the collected candidates.
 
     printPhase("Primary optimization pass");
+    if (fieldAccessAnalysis != null) {
+      fieldAccessAnalysis.fieldAssignmentTracker().initialize();
+    }
 
     // Process the application identifying outlining candidates.
     GraphLense graphLenseForIR = appView.graphLense();
diff --git a/src/main/java/com/android/tools/r8/optimize/MemberRebindingAnalysis.java b/src/main/java/com/android/tools/r8/optimize/MemberRebindingAnalysis.java
index b992f8d..1c1de57 100644
--- a/src/main/java/com/android/tools/r8/optimize/MemberRebindingAnalysis.java
+++ b/src/main/java/com/android/tools/r8/optimize/MemberRebindingAnalysis.java
@@ -347,9 +347,9 @@
     computeMethodRebinding(appInfo.directInvokes, this::anyLookup, Type.DIRECT);
     // Likewise static invokes.
     computeMethodRebinding(appInfo.staticInvokes, this::anyLookup, Type.STATIC);
-
     computeFieldRebinding();
-
-    return builder.build(lense);
+    GraphLense lens = builder.build(lense);
+    appInfo.getFieldAccessInfoCollection().flattenAccessContexts();
+    return lens;
   }
 }
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 8a54cca..1cc091d 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -374,10 +374,15 @@
     this.useRegistryFactory = createUseRegistryFactory();
     this.workList = EnqueuerWorklist.createWorklist(appView);
 
-    if (options.protoShrinking().enableGeneratedMessageLiteShrinking
-        && mode.isInitialOrFinalTreeShaking()) {
-      registerAnalysis(new ProtoEnqueuerExtension(appView));
+    if (mode.isInitialOrFinalTreeShaking()) {
+      if (options.protoShrinking().enableGeneratedMessageLiteShrinking) {
+        registerAnalysis(new ProtoEnqueuerExtension(appView));
+      }
+      appView.withGeneratedMessageLiteBuilderShrinker(
+          shrinker -> registerAnalysis(shrinker.createEnqueuerAnalysis()));
     }
+
+
     liveTypes = new SetWithReportedReason<>();
     initializedTypes = new SetWithReportedReason<>();
     targetedMethods = new SetWithReason<>(graphReporter::registerMethod);
diff --git a/src/test/examplesProto/proto2/BuilderOnlyReferencedFromDynamicMethodTestClass.java b/src/test/examplesProto/proto2/BuilderOnlyReferencedFromDynamicMethodTestClass.java
new file mode 100644
index 0000000..e3707dc
--- /dev/null
+++ b/src/test/examplesProto/proto2/BuilderOnlyReferencedFromDynamicMethodTestClass.java
@@ -0,0 +1,24 @@
+// 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 proto2;
+
+import com.android.tools.r8.proto2.TestProto.Primitives;
+import com.google.protobuf.GeneratedMessageLite;
+import com.google.protobuf.InvalidProtocolBufferException;
+
+public class BuilderOnlyReferencedFromDynamicMethodTestClass {
+
+  public static void main(String[] args) {
+    GeneratedMessageLite<?, ?> primitivesInDisguise;
+    try {
+      primitivesInDisguise = Primitives.parseFrom(new byte[0]);
+    } catch (InvalidProtocolBufferException e) {
+      System.out.println("Unexpected exception: " + e);
+      throw new RuntimeException(e);
+    }
+    Primitives primitives = (Primitives) primitivesInDisguise.toBuilder().build();
+    Printer.print(primitives);
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/internal/proto/Proto2BuilderOnlyReferencedFromDynamicMethodTest.java b/src/test/java/com/android/tools/r8/internal/proto/Proto2BuilderOnlyReferencedFromDynamicMethodTest.java
new file mode 100644
index 0000000..6405047
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/internal/proto/Proto2BuilderOnlyReferencedFromDynamicMethodTest.java
@@ -0,0 +1,78 @@
+// 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.internal.proto;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.hamcrest.CoreMatchers.equalTo;
+import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertFalse;
+
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.google.common.collect.ImmutableList;
+import java.nio.file.Path;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class Proto2BuilderOnlyReferencedFromDynamicMethodTest extends ProtoShrinkingTestBase {
+
+  private static final String MAIN = "proto2.BuilderOnlyReferencedFromDynamicMethodTestClass";
+
+  private static List<Path> PROGRAM_FILES =
+      ImmutableList.of(PROTO2_EXAMPLES_JAR, PROTO2_PROTO_JAR, PROTOBUF_LITE_JAR);
+
+  private final TestParameters parameters;
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  public Proto2BuilderOnlyReferencedFromDynamicMethodTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void test() throws Exception {
+    testForR8(parameters.getBackend())
+        .addProgramFiles(PROGRAM_FILES)
+        .addKeepMainRule(MAIN)
+        .addKeepRuleFiles(PROTOBUF_LITE_PROGUARD_RULES)
+        .allowAccessModification()
+        .allowDiagnosticMessages()
+        .allowUnusedProguardConfigurationRules()
+        .enableProtoShrinking()
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .assertAllInfoMessagesMatch(
+            containsString("Proguard configuration rule does not match anything"))
+        .assertAllWarningMessagesMatch(equalTo("Resource 'META-INF/MANIFEST.MF' already exists."))
+        .inspect(this::inspect)
+        .run(parameters.getRuntime(), MAIN)
+        .assertSuccessWithOutputLines(
+            "false", "0", "false", "", "false", "0", "false", "0", "false", "");
+  }
+
+  private void inspect(CodeInspector outputInspector) {
+    verifyBuilderIsAbsent(outputInspector);
+  }
+
+  private void verifyBuilderIsAbsent(CodeInspector outputInspector) {
+    ClassSubject generatedMessageLiteBuilder =
+        outputInspector.clazz("com.google.protobuf.GeneratedMessageLite$Builder");
+    assertThat(generatedMessageLiteBuilder, isPresent());
+    assertFalse(generatedMessageLiteBuilder.isAbstract());
+    assertThat(
+        outputInspector.clazz("com.android.tools.r8.proto2.TestProto$Primitives$Builder"),
+        not(isPresent()));
+  }
+}
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 8c98b46..dc2b296 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
@@ -171,9 +171,7 @@
     assertThat(
         outputInspector.clazz(
             "com.android.tools.r8.proto2.Shrinking$HasFlaggedOffExtension$Builder"),
-        mains.equals(ImmutableList.of("proto2.HasFlaggedOffExtensionBuilderTestClass"))
-            ? isPresent()
-            : not(isPresent()));
+        not(isPresent()));
     assertThat(
         outputInspector.clazz("com.android.tools.r8.proto2.TestProto$Primitives$Builder"),
         not(isPresent()));