Do not trace dead fields in Enqueuer

Change-Id: I40e6f07e64482e02f4a4d32cca20f9697f42e139
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java
index af3ab41..a0b5581 100644
--- a/src/main/java/com/android/tools/r8/R8.java
+++ b/src/main/java/com/android/tools/r8/R8.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.GraphLense;
+import com.android.tools.r8.ir.analysis.proto.GeneratedExtensionRegistryShrinker;
 import com.android.tools.r8.ir.conversion.IRConverter;
 import com.android.tools.r8.ir.desugar.R8NestBasedAccessDesugaring;
 import com.android.tools.r8.ir.optimize.EnumOrdinalMapCollector;
@@ -35,6 +36,7 @@
 import com.android.tools.r8.ir.optimize.UnusedArgumentsCollector;
 import com.android.tools.r8.jar.CfApplicationWriter;
 import com.android.tools.r8.kotlin.Kotlin;
+import com.android.tools.r8.logging.Log;
 import com.android.tools.r8.naming.ClassNameMapper;
 import com.android.tools.r8.naming.Minifier;
 import com.android.tools.r8.naming.NamingLens;
@@ -327,6 +329,16 @@
                 .run(executorService));
 
         Enqueuer enqueuer = new Enqueuer(appView, options, null, compatibility);
+
+        // If shrinking of the generated proto extension registry is enabled, then the Enqueuer
+        // won't trace the dead proto extensions fields. However, for the purpose of member value
+        // propagation, we should keep the dead proto extension fields in the program, such that
+        // member value propagation can find their definitions and the corresponding optimization
+        // info. This is handled by simply marking the dead proto extension types as live after the
+        // Enqueuer has finished. This way we don't actually trace these types.
+        enqueuer.markSkippedProtoExtensionTypesAsLive(
+            options.enableGeneratedExtensionRegistryShrinking);
+
         AppView<AppInfoWithLiveness> appViewWithLiveness =
             appView.setAppInfo(
                 enqueuer.traceApplication(
@@ -356,6 +368,9 @@
                   .withLiveness()
                   .prunedCopyFrom(application, pruner.getRemovedClasses()));
           new AbstractMethodRemover(appView.appInfo().withLiveness()).run();
+
+          // Mark dead proto extensions fields as neither being read nor written.
+          appView.withGeneratedExtensionRegistryShrinker(GeneratedExtensionRegistryShrinker::run);
         }
 
         classesToRetainInnerClassAttributeFor =
@@ -629,6 +644,11 @@
                   executorService,
                   timing));
 
+          if (Log.ENABLED) {
+            appView.withGeneratedExtensionRegistryShrinker(
+                GeneratedExtensionRegistryShrinker::logDeadProtoExtensionFields);
+          }
+
           if (options.isShrinking()) {
             TreePruner pruner = new TreePruner(application, appViewWithLiveness);
             application = pruner.run();
diff --git a/src/main/java/com/android/tools/r8/graph/AppView.java b/src/main/java/com/android/tools/r8/graph/AppView.java
index 800feb6..1d48684 100644
--- a/src/main/java/com/android/tools/r8/graph/AppView.java
+++ b/src/main/java/com/android/tools/r8/graph/AppView.java
@@ -5,6 +5,7 @@
 package com.android.tools.r8.graph;
 
 import com.android.tools.r8.OptionalBool;
+import com.android.tools.r8.ir.analysis.proto.GeneratedExtensionRegistryShrinker;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.shaking.RootSetBuilder.RootSet;
 import com.android.tools.r8.shaking.VerticalClassMerger.VerticallyMergedClasses;
@@ -12,6 +13,8 @@
 import com.google.common.base.Predicates;
 import com.google.common.collect.ImmutableSet;
 import java.util.Set;
+import java.util.function.Consumer;
+import java.util.function.Function;
 import java.util.function.Predicate;
 
 public class AppView<T extends AppInfo> implements DexDefinitionSupplier {
@@ -29,6 +32,10 @@
   private final InternalOptions options;
   private RootSet rootSet;
 
+  // Optimizations.
+  private final GeneratedExtensionRegistryShrinker generatedExtensionRegistryShrinker;
+
+  // Optimization results.
   private Predicate<DexType> classesEscapingIntoLibrary = Predicates.alwaysTrue();
   private Set<DexMethod> unneededVisibilityBridgeMethods = ImmutableSet.of();
   private VerticallyMergedClasses verticallyMergedClasses;
@@ -40,6 +47,15 @@
     this.wholeProgramOptimizations = wholeProgramOptimizations;
     this.graphLense = GraphLense.getIdentityLense();
     this.options = options;
+
+    if (enableWholeProgramOptimizations()) {
+      this.generatedExtensionRegistryShrinker =
+          options.enableGeneratedExtensionRegistryShrinking
+              ? new GeneratedExtensionRegistryShrinker(this.withLiveness())
+              : null;
+    } else {
+      this.generatedExtensionRegistryShrinker = null;
+    }
   }
 
   public static <T extends AppInfo> AppView<T> createForD8(T appInfo, InternalOptions options) {
@@ -127,6 +143,21 @@
     return wholeProgramOptimizations == WholeProgramOptimizations.ON;
   }
 
+  public void withGeneratedExtensionRegistryShrinker(
+      Consumer<GeneratedExtensionRegistryShrinker> consumer) {
+    if (generatedExtensionRegistryShrinker != null) {
+      consumer.accept(generatedExtensionRegistryShrinker);
+    }
+  }
+
+  public <U> U withGeneratedExtensionRegistryShrinker(
+      Function<GeneratedExtensionRegistryShrinker, U> fn, U defaultValue) {
+    if (generatedExtensionRegistryShrinker != null) {
+      return fn.apply(generatedExtensionRegistryShrinker);
+    }
+    return defaultValue;
+  }
+
   public GraphLense graphLense() {
     return graphLense;
   }
diff --git a/src/main/java/com/android/tools/r8/graph/FieldAccessInfo.java b/src/main/java/com/android/tools/r8/graph/FieldAccessInfo.java
index 975776a..7b4abd6 100644
--- a/src/main/java/com/android/tools/r8/graph/FieldAccessInfo.java
+++ b/src/main/java/com/android/tools/r8/graph/FieldAccessInfo.java
@@ -11,13 +11,19 @@
 /** Provides immutable access to {@link FieldAccessInfoImpl}. */
 public interface FieldAccessInfo {
 
+  FieldAccessInfoImpl asMutable();
+
   DexField getField();
 
+  DexEncodedMethod getUniqueReadContext();
+
   void forEachIndirectAccess(Consumer<DexField> consumer);
 
   void forEachIndirectAccessWithContexts(BiConsumer<DexField, Set<DexEncodedMethod>> consumer);
 
   boolean isRead();
 
+  boolean isReadOnlyIn(DexEncodedMethod method);
+
   boolean isWritten();
 }
diff --git a/src/main/java/com/android/tools/r8/graph/FieldAccessInfoCollection.java b/src/main/java/com/android/tools/r8/graph/FieldAccessInfoCollection.java
index 340d606..1f07f43 100644
--- a/src/main/java/com/android/tools/r8/graph/FieldAccessInfoCollection.java
+++ b/src/main/java/com/android/tools/r8/graph/FieldAccessInfoCollection.java
@@ -9,5 +9,7 @@
 /** Provides immutable access to {@link FieldAccessInfoCollectionImpl}. */
 public interface FieldAccessInfoCollection<T extends FieldAccessInfo> {
 
+  T get(DexField field);
+
   void forEach(Consumer<T> consumer);
 }
diff --git a/src/main/java/com/android/tools/r8/graph/FieldAccessInfoCollectionImpl.java b/src/main/java/com/android/tools/r8/graph/FieldAccessInfoCollectionImpl.java
index 8427d53..1478f2d 100644
--- a/src/main/java/com/android/tools/r8/graph/FieldAccessInfoCollectionImpl.java
+++ b/src/main/java/com/android/tools/r8/graph/FieldAccessInfoCollectionImpl.java
@@ -15,6 +15,7 @@
 
   private Map<DexField, FieldAccessInfoImpl> infos = new IdentityHashMap<>();
 
+  @Override
   public FieldAccessInfoImpl get(DexField field) {
     return infos.get(field);
   }
diff --git a/src/main/java/com/android/tools/r8/graph/FieldAccessInfoImpl.java b/src/main/java/com/android/tools/r8/graph/FieldAccessInfoImpl.java
index 23cb198..6198d50 100644
--- a/src/main/java/com/android/tools/r8/graph/FieldAccessInfoImpl.java
+++ b/src/main/java/com/android/tools/r8/graph/FieldAccessInfoImpl.java
@@ -37,11 +37,27 @@
   }
 
   @Override
+  public FieldAccessInfoImpl asMutable() {
+    return this;
+  }
+
+  @Override
   public DexField getField() {
     return field;
   }
 
   @Override
+  public DexEncodedMethod getUniqueReadContext() {
+    if (readsWithContexts != null && readsWithContexts.size() == 1) {
+      Set<DexEncodedMethod> contexts = readsWithContexts.values().iterator().next();
+      if (contexts.size() == 1) {
+        return contexts.iterator().next();
+      }
+    }
+    return null;
+  }
+
+  @Override
   public void forEachIndirectAccess(Consumer<DexField> consumer) {
     // There can be indirect reads and writes of the same field reference, so we need to keep track
     // of the previously-seen indirect accesses to avoid reporting duplicates.
@@ -99,6 +115,14 @@
     return readsWithContexts != null && !readsWithContexts.isEmpty();
   }
 
+  @Override
+  public boolean isReadOnlyIn(DexEncodedMethod method) {
+    assert isRead();
+    assert method != null;
+    DexEncodedMethod uniqueReadContext = getUniqueReadContext();
+    return uniqueReadContext != null && uniqueReadContext == method;
+  }
+
   /** Returns true if this field is written by the program. */
   @Override
   public boolean isWritten() {
@@ -123,6 +147,10 @@
         .add(context);
   }
 
+  public void clearReads() {
+    readsWithContexts = null;
+  }
+
   public void clearWrites() {
     writesWithContexts = null;
   }
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/proto/GeneratedExtensionRegistryShrinker.java b/src/main/java/com/android/tools/r8/ir/analysis/proto/GeneratedExtensionRegistryShrinker.java
new file mode 100644
index 0000000..e30bf58
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/analysis/proto/GeneratedExtensionRegistryShrinker.java
@@ -0,0 +1,171 @@
+// 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.AppView;
+import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexEncodedField;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexField;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexMethod;
+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.graph.FieldAccessInfo;
+import com.android.tools.r8.graph.FieldAccessInfoCollection;
+import com.android.tools.r8.graph.FieldAccessInfoImpl;
+import com.android.tools.r8.logging.Log;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import java.util.function.Consumer;
+
+/**
+ * This optimization is responsible for pruning dead proto extensions.
+ *
+ * <p>When using proto lite, a registry for all proto extensions is created. The generated extension
+ * registry roughly looks as follows:
+ *
+ * <pre>
+ *   class GeneratedExtensionRegistry {
+ *     public static GeneratedMessageLite$GeneratedExtension findLiteExtensionByNumber(
+ *         MessageLite message, int number) {
+ *       ...
+ *       switch (...) {
+ *         case ...:
+ *           return SomeExtension.extensionField;
+ *         case ...:
+ *           return SomeOtherExtension.extensionField;
+ *         ... // Many other cases.
+ *         default:
+ *           return null;
+ *       }
+ *     }
+ *   }
+ * </pre>
+ *
+ * <p>We consider an extension to be dead if it is only accessed via a static-get instruction inside
+ * the GeneratedExtensionRegistry. For such dead extensions, we simply rewrite the static-get
+ * instructions inside the GeneratedExtensionRegistry to null. This ensures that the extensions will
+ * be removed as a result of tree shaking.
+ */
+public class GeneratedExtensionRegistryShrinker {
+
+  static class ProtoReferences {
+
+    public final DexType generatedExtensionType;
+    public final DexType generatedMessageLiteType;
+    public final DexType messageLiteType;
+
+    public final DexString findLiteExtensionByNumberName;
+    public final DexString findLiteExtensionByNumber1Name;
+    public final DexString findLiteExtensionByNumber2Name;
+    public final DexProto findLiteExtensionByNumberProto;
+
+    private ProtoReferences(DexItemFactory factory) {
+      generatedExtensionType =
+          factory.createType(
+              factory.createString(
+                  "Lcom/google/protobuf/GeneratedMessageLite$GeneratedExtension;"));
+      generatedMessageLiteType =
+          factory.createType(factory.createString("Lcom/google/protobuf/GeneratedMessageLite;"));
+      messageLiteType =
+          factory.createType(factory.createString("Lcom/google/protobuf/MessageLite;"));
+      findLiteExtensionByNumberName = factory.createString("findLiteExtensionByNumber");
+      findLiteExtensionByNumber1Name = factory.createString("findLiteExtensionByNumber1");
+      findLiteExtensionByNumber2Name = factory.createString("findLiteExtensionByNumber2");
+      findLiteExtensionByNumberProto =
+          factory.createProto(generatedExtensionType, messageLiteType, factory.intType);
+    }
+
+    public boolean isFindLiteExtensionByNumberMethod(DexMethod method) {
+      if (method.proto == findLiteExtensionByNumberProto) {
+        assert method.name != findLiteExtensionByNumber2Name;
+        return method.name == findLiteExtensionByNumberName
+            || method.name == findLiteExtensionByNumber1Name;
+      }
+      return false;
+    }
+  }
+
+  private final AppView<AppInfoWithLiveness> appView;
+  private final ProtoReferences references;
+
+  public GeneratedExtensionRegistryShrinker(AppView<AppInfoWithLiveness> appView) {
+    assert appView.options().enableGeneratedExtensionRegistryShrinking;
+    this.appView = appView;
+    this.references = new ProtoReferences(appView.dexItemFactory());
+  }
+
+  /**
+   * Will be run after the initial round of tree shaking. This clears the reads and writes to fields
+   * that store dead proto extensions. As a result of this, the member value propagation will
+   * automatically rewrite the reads of this field by null.
+   */
+  public void run() {
+    FieldAccessInfoCollection<?> fieldAccessInfoCollection =
+        appView.appInfo().getFieldAccessInfoCollection();
+    forEachDeadProtoExtensionField(
+        field -> {
+          FieldAccessInfoImpl fieldAccessInfo = fieldAccessInfoCollection.get(field).asMutable();
+          fieldAccessInfo.clearReads();
+          fieldAccessInfo.clearWrites();
+        });
+  }
+
+  public boolean isDeadProtoExtensionField(DexField field) {
+    return isDeadProtoExtensionField(field, appView.appInfo().getFieldAccessInfoCollection());
+  }
+
+  public boolean isDeadProtoExtensionField(
+      DexField field, FieldAccessInfoCollection<?> fieldAccessInfoCollection) {
+    if (field.type != references.generatedExtensionType) {
+      return false;
+    }
+
+    DexEncodedField encodedField = appView.appInfo().resolveField(field);
+    if (encodedField == null) {
+      return false;
+    }
+
+    DexClass clazz = appView.definitionFor(encodedField.field.holder);
+    if (clazz == null || !clazz.isProgramClass()) {
+      return false;
+    }
+
+    if (!appView.isSubtype(clazz.type, references.generatedMessageLiteType).isTrue()) {
+      return false;
+    }
+
+    FieldAccessInfo fieldAccessInfo = fieldAccessInfoCollection.get(encodedField.field);
+    if (fieldAccessInfo == null) {
+      return false;
+    }
+
+    DexEncodedMethod uniqueReadContext = fieldAccessInfo.getUniqueReadContext();
+    return uniqueReadContext != null
+        && references.isFindLiteExtensionByNumberMethod(uniqueReadContext.method);
+  }
+
+  private void forEachDeadProtoExtensionField(Consumer<DexField> consumer) {
+    FieldAccessInfoCollection<?> fieldAccessInfoCollection =
+        appView.appInfo().getFieldAccessInfoCollection();
+    fieldAccessInfoCollection.forEach(
+        info -> {
+          DexField field = info.getField();
+          if (isDeadProtoExtensionField(field)) {
+            consumer.accept(field);
+          }
+        });
+  }
+
+  /** For debugging. */
+  public void logDeadProtoExtensionFields() {
+    if (Log.isLoggingEnabledFor(GeneratedExtensionRegistryShrinker.class)) {
+      forEachDeadProtoExtensionField(
+          field ->
+              System.out.println("Dead proto extension field: `" + field.toSourceString() + "`"));
+    }
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/logging/Log.java b/src/main/java/com/android/tools/r8/logging/Log.java
index 6cb91b4..2ed1b30 100644
--- a/src/main/java/com/android/tools/r8/logging/Log.java
+++ b/src/main/java/com/android/tools/r8/logging/Log.java
@@ -5,39 +5,39 @@
 
 public class Log {
 
-  static public final boolean ENABLED = false;
+  public static final boolean ENABLED = false;
 
-  static private final boolean VERBOSE_ENABLED = false;
-  static private final boolean INFO_ENABLED = true;
-  static private final boolean DEBUG_ENABLED = true;
-  static private final boolean WARN_ENABLED = true;
+  private static final boolean VERBOSE_ENABLED = false;
+  private static final boolean INFO_ENABLED = true;
+  private static final boolean DEBUG_ENABLED = true;
+  private static final boolean WARN_ENABLED = true;
 
   public static void verbose(Class<?> from, String message, Object... arguments) {
-    if (ENABLED && VERBOSE_ENABLED && isClassEnabled(from)) {
+    if (isLoggingEnabledFor(from) && VERBOSE_ENABLED) {
       log("VERB", from, message, arguments);
     }
   }
 
   public static void info(Class<?> from, String message, Object... arguments) {
-    if (ENABLED && INFO_ENABLED && isClassEnabled(from)) {
+    if (isLoggingEnabledFor(from) && INFO_ENABLED) {
       log("INFO", from, message, arguments);
     }
   }
 
   public static void debug(Class<?> from, String message, Object... arguments) {
-    if (ENABLED && DEBUG_ENABLED && isClassEnabled(from)) {
+    if (isLoggingEnabledFor(from) && DEBUG_ENABLED) {
       log("DBG", from, message, arguments);
     }
   }
 
   public static void warn(Class<?> from, String message, Object... arguments) {
-    if (ENABLED && WARN_ENABLED && isClassEnabled(from)) {
+    if (isLoggingEnabledFor(from) && WARN_ENABLED) {
       log("WARN", from, message, arguments);
     }
   }
 
-  private static boolean isClassEnabled(Class<?> clazz) {
-    return true;
+  public static boolean isLoggingEnabledFor(Class<?> clazz) {
+    return ENABLED;
   }
 
   synchronized private static void log(String kind, Class<?> from, String message, Object... args) {
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 9d43839..3560e2f 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -111,6 +111,14 @@
   private final boolean forceProguardCompatibility;
   private boolean tracingMainDex = false;
 
+  // If shrinking of the generated proto extension registry is enabled, then the Enqueuer
+  // won't trace the dead proto extensions fields. However, for the purpose of member value
+  // propagation, we should keep the dead proto extension fields in the program, such that
+  // member value propagation can find their definitions and the corresponding optimization
+  // info. This is handled by simply marking the dead proto extension types as live after the
+  // Enqueuer has finished. This way we don't actually trace these types.
+  private boolean markSkippedProtoExtensionTypesAsLive = false;
+
   private final AppInfoWithSubtyping appInfo;
   private final AppView<? extends AppInfoWithSubtyping> appView;
   private final InternalOptions options;
@@ -168,6 +176,14 @@
    */
   private final Set<DexType> liveTypes = Sets.newIdentityHashSet();
 
+  /**
+   * Set of proto extension types that are technically live, but which we have not traced because
+   * they are dead according to the generated extension registry shrinker.
+   *
+   * <p>Only used if {@link InternalOptions#enableGeneratedExtensionRegistryShrinking} is set.
+   */
+  private final Set<DexType> skippedProtoExtensionTypes = Sets.newIdentityHashSet();
+
   /** Set of annotation types that are instantiated. */
   private final SetWithReason<DexAnnotation> liveAnnotations =
       new SetWithReason<>(this::registerAnnotation);
@@ -287,6 +303,10 @@
     this.options = options;
   }
 
+  public void markSkippedProtoExtensionTypesAsLive(boolean value) {
+    markSkippedProtoExtensionTypesAsLive = value;
+  }
+
   private Set<DexField> staticFieldsWrittenOnlyInEnclosingStaticInitializer() {
     Set<DexField> result = Sets.newIdentityHashSet();
     fieldAccessInfoCollection.forEach(
@@ -609,9 +629,30 @@
       if (!registerFieldRead(field, currentMethod)) {
         return false;
       }
+
       if (Log.ENABLED) {
         Log.verbose(getClass(), "Register Sget `%s`.", field);
       }
+
+      DexEncodedField encodedField = appInfo.resolveField(field);
+      if (encodedField != null && encodedField.isProgramField(appView)) {
+        if (appView.options().enableGeneratedExtensionRegistryShrinking) {
+          // If it is a dead proto extension field, don't trace onwards.
+          boolean skipTracing =
+              appView.withGeneratedExtensionRegistryShrinker(
+                  shrinker ->
+                      shrinker.isDeadProtoExtensionField(
+                          encodedField.field, fieldAccessInfoCollection),
+                  false);
+          if (skipTracing) {
+            if (markSkippedProtoExtensionTypesAsLive) {
+              skippedProtoExtensionTypes.add(encodedField.field.holder);
+            }
+            return false;
+          }
+        }
+      }
+
       markStaticFieldAsLive(field, KeepReason.fieldReferencedIn(currentMethod));
       return true;
     }
@@ -621,12 +662,30 @@
       if (!registerFieldWrite(field, currentMethod)) {
         return false;
       }
+
       if (Log.ENABLED) {
         Log.verbose(getClass(), "Register Sput `%s`.", field);
       }
 
       DexEncodedField encodedField = appInfo.resolveField(field);
       if (encodedField != null && encodedField.isProgramField(appView)) {
+        if (appView.options().enableGeneratedExtensionRegistryShrinking) {
+          // If it is a dead proto extension field, don't trace onwards.
+          boolean skipTracing =
+              appView.withGeneratedExtensionRegistryShrinker(
+                  shrinker ->
+                      shrinker.isDeadProtoExtensionField(
+                          encodedField.field, fieldAccessInfoCollection),
+                  false);
+          if (skipTracing) {
+            if (markSkippedProtoExtensionTypesAsLive) {
+              skippedProtoExtensionTypes.add(encodedField.field.holder);
+            }
+            return false;
+          }
+        }
+
+        // If it is written outside of the <clinit> of its enclosing class, record it.
         boolean isWrittenOutsideEnclosingStaticInitializer =
             currentMethod.method.holder != encodedField.field.holder
                 || !currentMethod.isClassInitializer();
@@ -1564,6 +1623,14 @@
         (field, info) -> field != info.getField() || info == MISSING_FIELD_ACCESS_INFO);
     assert fieldAccessInfoCollection.verifyMappingIsOneToOne();
 
+    // Mark the proto extensions types that have not been traced as live if specified by the
+    // configuration. This enables the member value propagation to find the definition of the dead
+    // proto extension fields, and thereby optimize the corresponding static-get instructions (will
+    // always be null).
+    if (markSkippedProtoExtensionTypesAsLive) {
+      liveTypes.addAll(skippedProtoExtensionTypes);
+    }
+
     AppInfoWithLiveness appInfoWithLiveness =
         new AppInfoWithLiveness(
             appInfo,
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 0f6aace..8318f91 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -293,6 +293,9 @@
   public boolean enableLambdaMerging = false;
   // Flag to turn on/off desugaring in D8/R8.
   public boolean enableDesugaring = true;
+  // Flag to turn on/off GeneratedExtensionRegistry shrinking.
+  public boolean enableGeneratedExtensionRegistryShrinking =
+      System.getProperty("com.android.tools.r8.generatedExtensionRegistryShrinking") != null;
   // Flag to turn on/off JDK11+ nest-access control
   public boolean enableNestBasedAccessDesugaring = true;
   // Flag to turn on/off reduction of nest to improve class merging optimizations.
diff --git a/tools/run_on_app.py b/tools/run_on_app.py
index b8bd0ed..4c9a844 100755
--- a/tools/run_on_app.py
+++ b/tools/run_on_app.py
@@ -317,6 +317,8 @@
         args.extend(['--main-dex-rules', rules])
     if 'allow-type-errors' in values:
       extra_args.append('-Dcom.android.tools.r8.allowTypeErrors=1')
+    if 'proto-shrinking' in values:
+      extra_args.append('-Dcom.android.tools.r8.generatedExtensionRegistryShrinking=1')
 
   if not options.no_libraries and 'libraries' in values:
     for lib in values['libraries']:
diff --git a/tools/youtube_data.py b/tools/youtube_data.py
index f26af8d..94a97ce 100644
--- a/tools/youtube_data.py
+++ b/tools/youtube_data.py
@@ -131,6 +131,7 @@
       'pgconf': [
           '%s_proguard.config' % V14_19_PREFIX,
           '%s/proguardsettings/YouTubeRelease_proguard.config' % utils.THIRD_PARTY],
+      'proto-shrinking': 1,
       # Build for native multi dex, as Currently R8 cannot meet the main-dex
       # constraints.
       #'maindexrules' : [