Merge commit '5c7d3ce0754f28f499a6073a83d2abde5cf7bc02' into dev-release
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java
index 41ea658..d96d546 100644
--- a/src/main/java/com/android/tools/r8/R8.java
+++ b/src/main/java/com/android/tools/r8/R8.java
@@ -650,7 +650,7 @@
             appView.protoShrinker().enumLiteProtoShrinker.verifyDeadEnumLiteMapsAreDead();
           }
 
-          IRConverter converter = new IRConverter(appView, timing);
+          IRConverter converter = new IRConverter(appView);
 
           // If proto shrinking is enabled, we need to reprocess every dynamicMethod(). This ensures
           // that proto fields that have been removed by the second round of tree shaking are also
@@ -1017,7 +1017,7 @@
 
   static void processWhyAreYouKeepingAndCheckDiscarded(
       RootSet rootSet,
-      Supplier<Iterable<DexProgramClass>> classes,
+      Supplier<Collection<DexProgramClass>> classes,
       WhyAreYouKeepingConsumer whyAreYouKeepingConsumer,
       AppView<? extends AppInfoWithClassHierarchy> appView,
       Enqueuer enqueuer,
diff --git a/src/main/java/com/android/tools/r8/androidapi/ApiReferenceStubber.java b/src/main/java/com/android/tools/r8/androidapi/ApiReferenceStubber.java
index 261497d..8bbfcfd 100644
--- a/src/main/java/com/android/tools/r8/androidapi/ApiReferenceStubber.java
+++ b/src/main/java/com/android/tools/r8/androidapi/ApiReferenceStubber.java
@@ -24,10 +24,12 @@
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.synthesis.CommittedItems;
 import com.android.tools.r8.synthesis.SyntheticItems;
+import com.android.tools.r8.utils.ListUtils;
 import com.android.tools.r8.utils.ThreadUtils;
 import com.android.tools.r8.utils.WorkList;
 import com.google.common.collect.Sets;
 import java.util.Arrays;
+import java.util.Collection;
 import java.util.Map;
 import java.util.Set;
 import java.util.concurrent.ConcurrentHashMap;
@@ -58,7 +60,16 @@
   public void run(ExecutorService executorService) throws ExecutionException {
     if (appView.options().isGeneratingDex()
         && appView.options().apiModelingOptions().enableStubbingOfClasses) {
-      ThreadUtils.processItems(appView.appInfo().classes(), this::processClass, executorService);
+      Collection<DexProgramClass> classes =
+          ListUtils.filter(
+              appView.appInfo().classes(), DexProgramClass::originatesFromClassResource);
+      // Finding super types is really fast so no need to pay the overhead of threading if the
+      // number of classes is low.
+      if (classes.size() > 2) {
+        ThreadUtils.processItems(classes, this::processClass, executorService);
+      } else {
+        classes.forEach(this::processClass);
+      }
     }
     if (!libraryClassesToMock.isEmpty()) {
       libraryClassesToMock.forEach(
@@ -98,6 +109,7 @@
   }
 
   public void processClass(DexProgramClass clazz) {
+    assert clazz.originatesFromClassResource();
     if (isAlreadyOutlined(clazz)) {
       return;
     }
@@ -124,7 +136,7 @@
   }
 
   private void findReferencedLibraryClasses(DexType type, DexProgramClass context) {
-    if (!type.isClassType()) {
+    if (!type.isClassType() || isJavaType(type)) {
       return;
     }
     WorkList<DexType> workList = WorkList.newIdentityWorkList(type, seenTypes);
@@ -147,17 +159,18 @@
     }
   }
 
+  private boolean isJavaType(DexType type) {
+    return type == appView.dexItemFactory().objectType
+        || type.getDescriptor().startsWith(appView.dexItemFactory().javaDescriptorPrefix);
+  }
+
   private void mockMissingLibraryClass(
       DexLibraryClass libraryClass,
       ThrowExceptionCode throwExceptionCode,
       ApiReferenceStubberEventConsumer eventConsumer) {
     DexItemFactory factory = appView.dexItemFactory();
     // Do not stub the anything starting with java (including the object type).
-    if (libraryClass.getType() == appView.dexItemFactory().objectType
-        || libraryClass
-            .getType()
-            .getDescriptor()
-            .startsWith(appView.dexItemFactory().javaDescriptorPrefix)) {
+    if (isJavaType(libraryClass.getType())) {
       return;
     }
     // Check if desugared library will bridge the type.
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfConstDynamic.java b/src/main/java/com/android/tools/r8/cf/code/CfConstDynamic.java
index 6c379bb..9b9b2b1 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfConstDynamic.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfConstDynamic.java
@@ -46,17 +46,21 @@
   private final ConstantDynamicReference reference;
 
   public CfConstDynamic(
+      int symbolicReferenceId,
       DexString name,
       DexType type,
       DexMethodHandle bootstrapMethod,
       List<DexValue> bootstrapMethodArguments) {
+    assert symbolicReferenceId >= 0;
     assert name != null;
     assert type != null;
     assert bootstrapMethod != null;
     assert bootstrapMethodArguments != null;
     assert bootstrapMethodArguments.isEmpty();
 
-    reference = new ConstantDynamicReference(name, type, bootstrapMethod, bootstrapMethodArguments);
+    reference =
+        new ConstantDynamicReference(
+            symbolicReferenceId, name, type, bootstrapMethod, bootstrapMethodArguments);
   }
 
   @Override
@@ -86,7 +90,10 @@
   }
 
   public static CfConstDynamic fromAsmConstantDynamic(
-      ConstantDynamic insn, JarApplicationReader application, DexType clazz) {
+      int symbolicReferenceId,
+      ConstantDynamic insn,
+      JarApplicationReader application,
+      DexType clazz) {
     String constantName = insn.getName();
     String constantDescriptor = insn.getDescriptor();
     DexMethodHandle bootstrapMethodHandle =
@@ -99,6 +106,7 @@
       bootstrapMethodArguments.add(dexValue);
     }
     return new CfConstDynamic(
+        symbolicReferenceId,
         application.getString(constantName),
         application.getTypeFromDescriptor(constantDescriptor),
         bootstrapMethodHandle,
diff --git a/src/main/java/com/android/tools/r8/experimental/startup/instrumentation/StartupInstrumentation.java b/src/main/java/com/android/tools/r8/experimental/startup/instrumentation/StartupInstrumentation.java
index 72908ca..6e9c218 100644
--- a/src/main/java/com/android/tools/r8/experimental/startup/instrumentation/StartupInstrumentation.java
+++ b/src/main/java/com/android/tools/r8/experimental/startup/instrumentation/StartupInstrumentation.java
@@ -57,7 +57,7 @@
 
   private StartupInstrumentation(AppView<AppInfo> appView) {
     this.appView = appView;
-    this.converter = new IRConverter(appView, Timing.empty());
+    this.converter = new IRConverter(appView);
     this.dexItemFactory = appView.dexItemFactory();
     this.options = appView.options();
     this.references = new StartupInstrumentationReferences(dexItemFactory);
diff --git a/src/main/java/com/android/tools/r8/graph/AssemblyWriter.java b/src/main/java/com/android/tools/r8/graph/AssemblyWriter.java
index 03a4ce6..53ebcee 100644
--- a/src/main/java/com/android/tools/r8/graph/AssemblyWriter.java
+++ b/src/main/java/com/android/tools/r8/graph/AssemblyWriter.java
@@ -174,7 +174,7 @@
   }
 
   private void writeIR(ProgramMethod method) {
-    IRConverter converter = new IRConverter(appInfo, timing);
+    IRConverter converter = new IRConverter(appInfo);
     MethodProcessorEventConsumer eventConsumer = MethodProcessorEventConsumer.empty();
     OneTimeMethodProcessor methodProcessor =
         OneTimeMethodProcessor.create(
diff --git a/src/main/java/com/android/tools/r8/graph/LazyCfCode.java b/src/main/java/com/android/tools/r8/graph/LazyCfCode.java
index 45fed00..153f85f 100644
--- a/src/main/java/com/android/tools/r8/graph/LazyCfCode.java
+++ b/src/main/java/com/android/tools/r8/graph/LazyCfCode.java
@@ -79,6 +79,8 @@
 import it.unimi.dsi.fastutil.ints.Int2ReferenceMap;
 import it.unimi.dsi.fastutil.ints.IntArrayList;
 import it.unimi.dsi.fastutil.ints.IntList;
+import it.unimi.dsi.fastutil.objects.Reference2IntArrayMap;
+import it.unimi.dsi.fastutil.objects.Reference2IntMap;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.HashMap;
@@ -86,6 +88,7 @@
 import java.util.List;
 import java.util.Map;
 import java.util.function.BiFunction;
+import java.util.function.Supplier;
 import org.objectweb.asm.ClassReader;
 import org.objectweb.asm.ClassVisitor;
 import org.objectweb.asm.ConstantDynamic;
@@ -334,6 +337,7 @@
     private boolean usrJsrInliner;
     private final Origin origin;
     private final DebugParsingOptions debugParsingOptions;
+    private Reference2IntMap<ConstantDynamic> constantDynamicSymbolicReferences;
 
     ClassCodeVisitor(
         DexClass clazz,
@@ -351,6 +355,13 @@
       this.debugParsingOptions = debugParsingOptions;
     }
 
+    private Reference2IntMap<ConstantDynamic> getConstantDynamicSymbolicReferences() {
+      if (constantDynamicSymbolicReferences == null) {
+        constantDynamicSymbolicReferences = new Reference2IntArrayMap<>();
+      }
+      return constantDynamicSymbolicReferences;
+    }
+
     @Override
     public MethodVisitor visitMethod(
         int access, String name, String desc, String signature, String[] exceptions) {
@@ -360,7 +371,13 @@
         if (code != null) {
           DexMethod method = application.getMethod(clazz.type, name, desc);
           MethodCodeVisitor methodVisitor =
-              new MethodCodeVisitor(application, method, code, origin, debugParsingOptions);
+              new MethodCodeVisitor(
+                  application,
+                  method,
+                  code,
+                  origin,
+                  debugParsingOptions,
+                  this::getConstantDynamicSymbolicReferences);
           if (!usrJsrInliner) {
             return methodVisitor;
           }
@@ -391,13 +408,16 @@
     private final Origin origin;
     private int minLine = Integer.MAX_VALUE;
     private int maxLine = -1;
+    private final Supplier<Reference2IntMap<ConstantDynamic>>
+        constantDynamicSymbolicReferencesSupplier;
 
     MethodCodeVisitor(
         JarApplicationReader application,
         DexMethod method,
         LazyCfCode code,
         Origin origin,
-        DebugParsingOptions debugParsingOptions) {
+        DebugParsingOptions debugParsingOptions,
+        Supplier<Reference2IntMap<ConstantDynamic>> constantDynamicSymbolicReferencesSupplier) {
       super(InternalOptions.ASM_VERSION);
       this.debugParsingOptions = debugParsingOptions;
       assert code != null;
@@ -406,6 +426,7 @@
       this.code = code;
       this.method = method;
       this.origin = origin;
+      this.constantDynamicSymbolicReferencesSupplier = constantDynamicSymbolicReferencesSupplier;
     }
 
     private void addInstruction(CfInstruction instruction) {
@@ -1004,9 +1025,20 @@
             new CfConstMethodHandle(
                 DexMethodHandle.fromAsmHandle((Handle) cst, application, method.holder)));
       } else if (cst instanceof ConstantDynamic) {
+        // Each symbolic reference to a dynamically-computed constant has a unique ConstantDynamic
+        // instance from ASM, even when they are equal (i.e. all their components are equal). See
+        // ConstantDynamicMultipleConstantsWithDifferentSymbolicReferenceUsingSameBSMAndArgumentsTest
+        // for an example.
+        Reference2IntMap<ConstantDynamic> constantDynamicSymbolicReferences =
+            constantDynamicSymbolicReferencesSupplier.get();
+        int symbolicReferenceId = constantDynamicSymbolicReferences.getOrDefault(cst, -1);
+        if (symbolicReferenceId == -1) {
+          symbolicReferenceId = constantDynamicSymbolicReferences.size();
+          constantDynamicSymbolicReferences.put((ConstantDynamic) cst, symbolicReferenceId);
+        }
         addInstruction(
             CfConstDynamic.fromAsmConstantDynamic(
-                (ConstantDynamic) cst, application, method.holder));
+                symbolicReferenceId, (ConstantDynamic) cst, application, method.holder));
       } else {
         throw new CompilationError("Unsupported constant: " + cst.toString());
       }
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/code/SyntheticInitializerConverter.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/code/SyntheticInitializerConverter.java
index 1872754..93572c6 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/code/SyntheticInitializerConverter.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/code/SyntheticInitializerConverter.java
@@ -59,7 +59,7 @@
 
   public void convertClassInitializers(ExecutorService executorService) throws ExecutionException {
     if (!classInitializers.isEmpty()) {
-      IRConverter converter = new IRConverter(createAppViewForConversion(), Timing.empty());
+      IRConverter converter = new IRConverter(createAppViewForConversion());
       ThreadUtils.processItems(
           classInitializers, method -> processMethod(method, converter), executorService);
     }
@@ -68,7 +68,7 @@
   public void convertInstanceInitializers(ExecutorService executorService)
       throws ExecutionException {
     if (!instanceInitializers.isEmpty()) {
-      IRConverter converter = new IRConverter(createAppViewForConversion(), Timing.empty());
+      IRConverter converter = new IRConverter(createAppViewForConversion());
       ThreadUtils.processItems(
           instanceInitializers,
           clazz -> processInstanceInitializers(clazz, converter),
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 b5eec8c..2ff901b 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
@@ -195,7 +195,7 @@
     }
     timing.begin("Remove dead builder references");
     AppInfoWithLiveness appInfo = appView.appInfo();
-    IRConverter converter = new IRConverter(appView, Timing.empty());
+    IRConverter converter = new IRConverter(appView);
     ThreadUtils.processMap(
         builders,
         (builder, dynamicMethod) -> {
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 c2917b8..4291c1c 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
@@ -96,7 +96,6 @@
 
   public final AppView<?> appView;
 
-  protected final Timing timing;
   public final Outliner outliner;
   private final ClassInitializerDefaultsOptimization classInitializerDefaultsOptimization;
   protected final CfInstructionDesugaringCollection instructionDesugaring;
@@ -148,11 +147,9 @@
    * The argument `appView` is used to determine if whole program optimizations are allowed or not
    * (i.e., whether we are running R8). See {@link AppView#enableWholeProgramOptimizations()}.
    */
-  public IRConverter(AppView<?> appView, Timing timing) {
+  public IRConverter(AppView<?> appView) {
     assert appView.options() != null;
     assert appView.options().programConsumer != null;
-    assert timing != null;
-    this.timing = timing;
     this.appView = appView;
     this.options = appView.options();
     this.codeRewriter = new CodeRewriter(appView);
@@ -290,8 +287,8 @@
             : null;
   }
 
-  public IRConverter(AppInfo appInfo, Timing timing) {
-    this(AppView.createForD8(appInfo), timing);
+  public IRConverter(AppInfo appInfo) {
+    this(AppView.createForD8(appInfo));
   }
 
   public Inliner getInliner() {
@@ -428,10 +425,6 @@
     }
   }
 
-  String logCode(InternalOptions options, DexEncodedMethod method) {
-    return options.useSmaliSyntax ? method.toSmaliString(null) : method.codeToString();
-  }
-
   // TODO(b/140766440): Make this receive a list of CodeOptimizations to conduct.
   public Timing processDesugaredMethod(
       ProgramMethod method,
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/OneTimeMethodProcessor.java b/src/main/java/com/android/tools/r8/ir/conversion/OneTimeMethodProcessor.java
index c243d46..86a4feb 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/OneTimeMethodProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/OneTimeMethodProcessor.java
@@ -8,6 +8,7 @@
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.utils.ThreadUtils;
+import com.android.tools.r8.utils.ThreadUtils.WorkLoad;
 import com.android.tools.r8.utils.collections.ProgramMethodSet;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.ExecutorService;
@@ -79,11 +80,11 @@
   }
 
   @FunctionalInterface
-  public interface MethodAction<E extends Exception> {
-    void accept(ProgramMethod method, MethodProcessingContext methodProcessingContext) throws E;
+  public interface MethodAction {
+    void accept(ProgramMethod method, MethodProcessingContext methodProcessingContext);
   }
 
-  public <E extends Exception> void forEachWaveWithExtension(MethodAction<E> consumer) throws E {
+  public void forEachWaveWithExtension(MethodAction consumer) {
     while (!wave.isEmpty()) {
       for (ProgramMethod method : wave) {
         consumer.accept(method, processorContext.createMethodProcessingContext(method));
@@ -92,13 +93,15 @@
     }
   }
 
-  public <E extends Exception> void forEachWaveWithExtension(
-      MethodAction<E> consumer, ExecutorService executorService) throws ExecutionException {
+  public void forEachWaveWithExtension(MethodAction consumer, ExecutorService executorService)
+      throws ExecutionException {
     while (!wave.isEmpty()) {
       ThreadUtils.processItems(
           wave,
-          method -> consumer.accept(method, processorContext.createMethodProcessingContext(method)),
-          executorService);
+          (method, ignored) ->
+              consumer.accept(method, processorContext.createMethodProcessingContext(method)),
+          executorService,
+          WorkLoad.HEAVY);
       prepareForWaveExtensionProcessing();
     }
   }
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/PrimaryD8L8IRConverter.java b/src/main/java/com/android/tools/r8/ir/conversion/PrimaryD8L8IRConverter.java
index b2959f2..cf8e52d 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/PrimaryD8L8IRConverter.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/PrimaryD8L8IRConverter.java
@@ -45,8 +45,11 @@
 
 public class PrimaryD8L8IRConverter extends IRConverter {
 
+  private final Timing timing;
+
   public PrimaryD8L8IRConverter(AppView<AppInfo> appView, Timing timing) {
-    super(appView, timing);
+    super(appView);
+    this.timing = timing;
   }
 
   public void convert(AppView<AppInfo> appView, ExecutorService executorService)
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/PrimaryR8IRConverter.java b/src/main/java/com/android/tools/r8/ir/conversion/PrimaryR8IRConverter.java
index 1ad9a82..0ad28d4 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/PrimaryR8IRConverter.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/PrimaryR8IRConverter.java
@@ -26,8 +26,11 @@
 
 public class PrimaryR8IRConverter extends IRConverter {
 
+  private final Timing timing;
+
   public PrimaryR8IRConverter(AppView<? extends AppInfoWithClassHierarchy> appView, Timing timing) {
-    super(appView, timing);
+    super(appView);
+    this.timing = timing;
   }
 
   public void optimize(AppView<AppInfoWithLiveness> appView, ExecutorService executorService)
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/constantdynamic/ConstantDynamicReference.java b/src/main/java/com/android/tools/r8/ir/desugar/constantdynamic/ConstantDynamicReference.java
index ed08c4d..478e4cf 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/constantdynamic/ConstantDynamicReference.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/constantdynamic/ConstantDynamicReference.java
@@ -15,24 +15,24 @@
 import java.util.Objects;
 
 public class ConstantDynamicReference implements StructuralItem<ConstantDynamicReference> {
+  private final int symbolicReferenceId;
   private final DexString name;
   private final DexType type;
   private final DexMethodHandle bootstrapMethod;
   private final List<DexValue> bootstrapMethodArguments;
 
   private static void specify(StructuralSpecification<ConstantDynamicReference, ?> spec) {
-    spec.withItem(ConstantDynamicReference::getName)
-        .withItem(ConstantDynamicReference::getType)
-        .withItem(ConstantDynamicReference::getBootstrapMethod)
-        .withItemCollection(ConstantDynamicReference::getBootstrapMethodArguments);
+    spec.withInt(c -> c.symbolicReferenceId);
   }
 
   public ConstantDynamicReference(
+      int symbolicReferenceId,
       DexString name,
       DexType type,
       DexMethodHandle bootstrapMethod,
       List<DexValue> bootstrapMethodArguments) {
     assert bootstrapMethodArguments.isEmpty();
+    this.symbolicReferenceId = symbolicReferenceId;
     this.name = name;
     this.type = type;
     this.bootstrapMethod = bootstrapMethod;
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceMethodProcessorFacade.java b/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceMethodProcessorFacade.java
index c92c9ce..02f581d 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceMethodProcessorFacade.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceMethodProcessorFacade.java
@@ -11,8 +11,8 @@
 import com.android.tools.r8.ir.desugar.CfPostProcessingDesugaringEventConsumer;
 import com.android.tools.r8.ir.desugar.itf.InterfaceDesugaringSyntheticHelper.InterfaceMethodDesugaringMode;
 import com.android.tools.r8.ir.desugar.itf.InterfaceMethodRewriter.Flavor;
+import com.android.tools.r8.utils.ListUtils;
 import com.android.tools.r8.utils.ThreadUtils;
-import com.google.common.collect.Iterables;
 import java.util.Collection;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.ExecutorService;
@@ -51,7 +51,7 @@
       ExecutorService executorService)
       throws ExecutionException {
     ThreadUtils.processItems(
-        Iterables.filter(programClasses, (DexProgramClass clazz) -> shouldProcess(clazz, flavour)),
+        ListUtils.filter(programClasses, clazz -> shouldProcess(clazz, flavour)),
         clazz -> classProcessor.process(clazz, eventConsumer),
         executorService);
     classProcessor.finalizeProcessing(eventConsumer, executorService);
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/records/RecordFieldValuesRewriter.java b/src/main/java/com/android/tools/r8/ir/desugar/records/RecordFieldValuesRewriter.java
index d8a62a0..0c1c56f 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/records/RecordFieldValuesRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/records/RecordFieldValuesRewriter.java
@@ -45,7 +45,7 @@
 
   private RecordFieldValuesRewriter(AppView<AppInfoWithLiveness> appView) {
     this.appView = appView;
-    irConverter = new IRConverter(appView, Timing.empty());
+    irConverter = new IRConverter(appView);
   }
 
   // Called after final tree shaking, prune and minify field names and field values.
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedback.java b/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedback.java
index 6e7bf18..d033ca6 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedback.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedback.java
@@ -13,6 +13,7 @@
 import com.android.tools.r8.ir.conversion.MethodOptimizationFeedback;
 import com.android.tools.r8.shaking.AppInfoWithLivenessModifier;
 import com.android.tools.r8.utils.ThreadUtils;
+import java.util.Collection;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.ExecutorService;
 import java.util.function.Consumer;
@@ -55,7 +56,7 @@
   }
 
   public void fixupOptimizationInfos(
-      Iterable<DexProgramClass> classes,
+      Collection<DexProgramClass> classes,
       ExecutorService executorService,
       OptimizationInfoFixer fixer)
       throws ExecutionException {
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinClassMetadataReader.java b/src/main/java/com/android/tools/r8/kotlin/KotlinClassMetadataReader.java
index 7bae38e..a9d515a 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinClassMetadataReader.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinClassMetadataReader.java
@@ -3,6 +3,7 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.kotlin;
 
+import static com.android.tools.r8.kotlin.KotlinMetadataUtils.getInvalidKotlinInfo;
 import static com.android.tools.r8.kotlin.KotlinMetadataUtils.getNoKotlinInfo;
 import static com.android.tools.r8.kotlin.KotlinSyntheticClassInfo.getFlavour;
 
@@ -18,9 +19,11 @@
 import com.android.tools.r8.graph.DexValue;
 import com.android.tools.r8.graph.DexValue.DexValueArray;
 import com.android.tools.r8.kotlin.KotlinSyntheticClassInfo.Flavour;
+import com.android.tools.r8.utils.StringDiagnostic;
 import java.util.IdentityHashMap;
 import java.util.Map;
 import java.util.function.Consumer;
+import java.util.function.Supplier;
 import kotlin.Metadata;
 import kotlinx.metadata.InconsistentKotlinMetadataException;
 import kotlinx.metadata.jvm.KotlinClassMetadata;
@@ -34,16 +37,58 @@
   private static final int SYNTHETIC_CLASS_KIND = 3;
 
   public static KotlinClassLevelInfo getKotlinInfo(
-      DexClass clazz, AppView<?> appView, Consumer<DexEncodedMethod> keepByteCode)
-      throws KotlinMetadataException {
+      AppView<?> appView,
+      DexClass clazz,
+      Consumer<DexEncodedMethod> keepByteCode,
+      Supplier<Boolean> reportUnknownMetadata) {
     DexAnnotation meta =
         clazz.annotations().getFirstMatching(appView.dexItemFactory().kotlinMetadataType);
-    return meta != null ? getKotlinInfo(clazz, appView, keepByteCode, meta) : getNoKotlinInfo();
+    if (meta == null) {
+      return getNoKotlinInfo();
+    }
+    return getKotlinInfoFromAnnotation(appView, clazz, meta, keepByteCode, reportUnknownMetadata);
   }
 
-  public static KotlinClassLevelInfo getKotlinInfo(
-      DexClass clazz,
+  public static KotlinClassLevelInfo getKotlinInfoFromAnnotation(
       AppView<?> appView,
+      DexClass clazz,
+      DexAnnotation meta,
+      Consumer<DexEncodedMethod> keepByteCode,
+      Supplier<Boolean> reportUnknownMetadata) {
+    try {
+      return getKotlinInfo(appView, clazz, keepByteCode, meta);
+    } catch (KotlinMetadataException e) {
+      if (reportUnknownMetadata.get()) {
+        appView.reporter().warning(KotlinMetadataDiagnostic.unknownMetadataVersion());
+      }
+      appView
+          .reporter()
+          .info(
+              new StringDiagnostic(
+                  "Class "
+                      + clazz.type.toSourceString()
+                      + " has malformed kotlin.Metadata: "
+                      + e.getMessage()));
+      return getInvalidKotlinInfo();
+    } catch (Throwable e) {
+      if (reportUnknownMetadata.get()) {
+        appView.reporter().warning(KotlinMetadataDiagnostic.unknownMetadataVersion());
+      }
+      appView
+          .reporter()
+          .info(
+              new StringDiagnostic(
+                  "Unexpected error while reading "
+                      + clazz.type.toSourceString()
+                      + "'s kotlin.Metadata: "
+                      + e.getMessage()));
+      return getNoKotlinInfo();
+    }
+  }
+
+  private static KotlinClassLevelInfo getKotlinInfo(
+      AppView<?> appView,
+      DexClass clazz,
       Consumer<DexEncodedMethod> keepByteCode,
       DexAnnotation annotation)
       throws KotlinMetadataException {
@@ -55,8 +100,8 @@
     return createKotlinInfo(kotlin, clazz, kMetadata, appView, keepByteCode);
   }
 
-  public static boolean isLambda(AppView<?> appView, DexClass clazz)
-      throws KotlinMetadataException {
+  public static boolean isLambda(
+      AppView<?> appView, DexClass clazz, Supplier<Boolean> reportUnknownMetadata) {
     DexItemFactory dexItemFactory = appView.dexItemFactory();
     Kotlin kotlin = dexItemFactory.kotlin;
     Flavour flavour = getFlavour(clazz, kotlin);
@@ -69,16 +114,31 @@
       return false;
     }
     Map<DexString, DexAnnotationElement> elementMap = toElementMap(metadataAnnotation.annotation);
-    if (getKind(kotlin, elementMap) == SYNTHETIC_CLASS_KIND) {
-      KotlinClassMetadata kMetadata = toKotlinClassMetadata(kotlin, elementMap);
-      if (kMetadata instanceof SyntheticClass) {
-        return ((SyntheticClass) kMetadata).isLambda();
+    try {
+      if (getKind(kotlin, elementMap) == SYNTHETIC_CLASS_KIND) {
+        KotlinClassMetadata kMetadata = toKotlinClassMetadata(kotlin, elementMap);
+        if (kMetadata instanceof SyntheticClass) {
+          return ((SyntheticClass) kMetadata).isLambda();
+        }
       }
+      assert toKotlinClassMetadata(kotlin, elementMap) instanceof SyntheticClass
+              == (getKind(kotlin, elementMap) == SYNTHETIC_CLASS_KIND)
+          : "Synthetic class kinds should agree";
+      return false;
+    } catch (KotlinMetadataException exception) {
+      if (reportUnknownMetadata.get()) {
+        appView.reporter().warning(KotlinMetadataDiagnostic.unknownMetadataVersion());
+      }
+      appView
+          .reporter()
+          .info(
+              new StringDiagnostic(
+                  "Class "
+                      + clazz.type.toSourceString()
+                      + " has malformed kotlin.Metadata: "
+                      + exception.getMessage()));
+      return false;
     }
-    assert toKotlinClassMetadata(kotlin, elementMap) instanceof SyntheticClass
-            == (getKind(kotlin, elementMap) == SYNTHETIC_CLASS_KIND)
-        : "Synthetic class kinds should agree";
-    return false;
   }
 
   public static boolean hasKotlinClassMetadataAnnotation(
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataEnqueuerExtension.java b/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataEnqueuerExtension.java
index 6b5d589..dd7f94a 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataEnqueuerExtension.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataEnqueuerExtension.java
@@ -5,7 +5,6 @@
 package com.android.tools.r8.kotlin;
 
 import static com.android.tools.r8.kotlin.KotlinClassMetadataReader.hasKotlinClassMetadataAnnotation;
-import static com.android.tools.r8.kotlin.KotlinMetadataUtils.getInvalidKotlinInfo;
 import static com.android.tools.r8.kotlin.KotlinMetadataUtils.getNoKotlinInfo;
 
 import com.android.tools.r8.errors.Unreachable;
@@ -27,9 +26,9 @@
 import com.android.tools.r8.shaking.Enqueuer;
 import com.android.tools.r8.shaking.Enqueuer.EnqueuerDefinitionSupplier;
 import com.android.tools.r8.shaking.KeepClassInfo;
-import com.android.tools.r8.utils.StringDiagnostic;
 import com.google.common.collect.Sets;
 import java.util.Set;
+import java.util.concurrent.atomic.AtomicBoolean;
 
 public class KotlinMetadataEnqueuerExtension extends EnqueuerAnalysis {
 
@@ -38,7 +37,7 @@
   private final AppView<?> appView;
   private final EnqueuerDefinitionSupplier enqueuerDefinitionSupplier;
   private final Set<DexType> prunedTypes;
-  private boolean reportedUnknownMetadataVersion;
+  private final AtomicBoolean reportedUnknownMetadataVersion = new AtomicBoolean(false);
 
   public KotlinMetadataEnqueuerExtension(
       AppView<?> appView,
@@ -69,52 +68,30 @@
       enqueuer.forAllLiveClasses(
           clazz -> {
             assert clazz.getKotlinInfo().isNoKotlinInformation();
-            try {
-              if (enqueuer
-                  .getKeepInfo(clazz)
-                  .isKotlinMetadataRemovalAllowed(appView.options(), keepKotlinMetadata)) {
-                if (KotlinClassMetadataReader.isLambda(appView, clazz)
-                    && clazz.hasClassInitializer()) {
-                  feedback.classInitializerMayBePostponed(clazz.getClassInitializer());
-                }
-                clazz.clearKotlinInfo();
-                clazz.removeAnnotations(
-                    annotation ->
-                        annotation.getAnnotationType()
-                            == appView.dexItemFactory().kotlinMetadataType);
-              } else {
-                clazz.setKotlinInfo(
-                    KotlinClassMetadataReader.getKotlinInfo(
-                        clazz,
-                        appView,
-                        method -> keepByteCodeFunctions.add(method.getReference())));
-                if (clazz.getEnclosingMethodAttribute() != null
-                    && clazz.getEnclosingMethodAttribute().getEnclosingMethod() != null) {
-                  localOrAnonymousClasses.add(clazz);
-                }
+            if (enqueuer
+                .getKeepInfo(clazz)
+                .isKotlinMetadataRemovalAllowed(appView.options(), keepKotlinMetadata)) {
+              if (KotlinClassMetadataReader.isLambda(
+                      appView, clazz, () -> reportedUnknownMetadataVersion.getAndSet(true))
+                  && clazz.hasClassInitializer()) {
+                feedback.classInitializerMayBePostponed(clazz.getClassInitializer());
               }
-            } catch (KotlinMetadataException e) {
-              appView
-                  .reporter()
-                  .info(
-                      new StringDiagnostic(
-                          "Class "
-                              + clazz.type.toSourceString()
-                              + " has malformed kotlin.Metadata: "
-                              + e.getMessage()));
-              clazz.setKotlinInfo(getInvalidKotlinInfo());
-              reportUnknownMetadataVersion();
-            } catch (Throwable e) {
-              appView
-                  .reporter()
-                  .info(
-                      new StringDiagnostic(
-                          "Unexpected error while reading "
-                              + clazz.type.toSourceString()
-                              + "'s kotlin.Metadata: "
-                              + e.getMessage()));
-              clazz.setKotlinInfo(getNoKotlinInfo());
-              reportUnknownMetadataVersion();
+              clazz.clearKotlinInfo();
+              clazz.removeAnnotations(
+                  annotation ->
+                      annotation.getAnnotationType()
+                          == appView.dexItemFactory().kotlinMetadataType);
+            } else {
+              clazz.setKotlinInfo(
+                  KotlinClassMetadataReader.getKotlinInfo(
+                      appView,
+                      clazz,
+                      method -> keepByteCodeFunctions.add(method.getReference()),
+                      () -> reportedUnknownMetadataVersion.getAndSet(true)));
+              if (clazz.getEnclosingMethodAttribute() != null
+                  && clazz.getEnclosingMethodAttribute().getEnclosingMethod() != null) {
+                localOrAnonymousClasses.add(clazz);
+              }
             }
           });
       for (DexProgramClass localOrAnonymousClass : localOrAnonymousClasses) {
@@ -167,13 +144,6 @@
         });
   }
 
-  private void reportUnknownMetadataVersion() {
-    if (!reportedUnknownMetadataVersion) {
-      reportedUnknownMetadataVersion = true;
-      appView.reporter().warning(KotlinMetadataDiagnostic.unknownMetadataVersion());
-    }
-  }
-
   public class KotlinMetadataDefinitionSupplier implements DexDefinitionSupplier {
 
     private final ProgramDefinition context;
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataRewriter.java b/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataRewriter.java
index e57fc63..858a57c 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataRewriter.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataRewriter.java
@@ -20,6 +20,7 @@
 import com.android.tools.r8.graph.DexValue.DexValueArray;
 import com.android.tools.r8.graph.DexValue.DexValueInt;
 import com.android.tools.r8.graph.DexValue.DexValueString;
+import com.android.tools.r8.utils.BooleanBox;
 import com.android.tools.r8.utils.ConsumerUtils;
 import com.android.tools.r8.utils.Pair;
 import com.android.tools.r8.utils.ThreadUtils;
@@ -130,6 +131,7 @@
       return;
     }
     final WriteMetadataFieldInfo writeMetadataFieldInfo = WriteMetadataFieldInfo.rewriteAll();
+    BooleanBox reportedUnknownMetadataVersion = new BooleanBox();
     ThreadUtils.processItems(
         appView.appInfo().classes(),
         clazz -> {
@@ -138,8 +140,12 @@
             return;
           }
           KotlinClassLevelInfo kotlinInfo =
-              KotlinClassMetadataReader.getKotlinInfo(
-                  clazz, appView, ConsumerUtils.emptyConsumer(), metadata);
+              KotlinClassMetadataReader.getKotlinInfoFromAnnotation(
+                  appView,
+                  clazz,
+                  metadata,
+                  ConsumerUtils.emptyConsumer(),
+                  reportedUnknownMetadataVersion::getAndSet);
           if (kotlinInfo == getNoKotlinInfo()) {
             return;
           }
diff --git a/src/main/java/com/android/tools/r8/naming/signature/GenericSignatureRewriter.java b/src/main/java/com/android/tools/r8/naming/signature/GenericSignatureRewriter.java
index e4b384f..7b5a3ef 100644
--- a/src/main/java/com/android/tools/r8/naming/signature/GenericSignatureRewriter.java
+++ b/src/main/java/com/android/tools/r8/naming/signature/GenericSignatureRewriter.java
@@ -12,8 +12,9 @@
 import com.android.tools.r8.graph.GenericSignatureContextBuilder;
 import com.android.tools.r8.graph.GenericSignaturePartialTypeArgumentApplier;
 import com.android.tools.r8.graph.GenericSignatureTypeRewriter;
-import com.android.tools.r8.utils.IterableUtils;
+import com.android.tools.r8.utils.ListUtils;
 import com.android.tools.r8.utils.ThreadUtils;
+import java.util.Collection;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.ExecutorService;
 import java.util.function.BiPredicate;
@@ -35,7 +36,8 @@
     this.contextBuilder = contextBuilder;
   }
 
-  public void runForD8(Iterable<? extends DexProgramClass> classes, ExecutorService executorService)
+  public void runForD8(
+      Collection<? extends DexProgramClass> classes, ExecutorService executorService)
       throws ExecutionException {
     if (appView.getNamingLens().isIdentityLens()) {
       return;
@@ -43,7 +45,7 @@
     run(classes, executorService);
   }
 
-  public void run(Iterable<? extends DexProgramClass> classes, ExecutorService executorService)
+  public void run(Collection<? extends DexProgramClass> classes, ExecutorService executorService)
       throws ExecutionException {
     // Rewrite signature annotations for applications that are minified or if we have liveness
     // information, since we could have pruned types.
@@ -66,7 +68,7 @@
     ThreadUtils.processItems(
         // Final merging of classes can introduce pruned types that still exists in classes, we
         // therefore prune them from work here.
-        IterableUtils.filter(classes, clazz -> !wasPruned.test(clazz.getType())),
+        ListUtils.filter(classes, clazz -> !wasPruned.test(clazz.getType())),
         clazz -> {
           GenericSignaturePartialTypeArgumentApplier classArgumentApplier =
               contextBuilder != null
diff --git a/src/main/java/com/android/tools/r8/optimize/RedundantBridgeRemover.java b/src/main/java/com/android/tools/r8/optimize/RedundantBridgeRemover.java
index 815a807..763c40f 100644
--- a/src/main/java/com/android/tools/r8/optimize/RedundantBridgeRemover.java
+++ b/src/main/java/com/android/tools/r8/optimize/RedundantBridgeRemover.java
@@ -3,8 +3,6 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.optimize;
 
-import static com.android.tools.r8.utils.ThreadUtils.processItems;
-
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexClassAndMethod;
 import com.android.tools.r8.graph.DexEncodedMethod;
@@ -21,6 +19,7 @@
 import com.android.tools.r8.optimize.redundantbridgeremoval.RedundantBridgeRemovalLens;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.shaking.KeepMethodInfo;
+import com.android.tools.r8.utils.ThreadUtils;
 import com.android.tools.r8.utils.collections.ProgramMethodSet;
 import java.util.Map;
 import java.util.concurrent.ConcurrentHashMap;
@@ -134,7 +133,7 @@
       RedundantBridgeRemovalLens.Builder lensBuilder, ExecutorService executorService)
       throws ExecutionException {
     Map<DexProgramClass, ProgramMethodSet> bridgesToRemove = new ConcurrentHashMap<>();
-    processItems(
+    ThreadUtils.processItems(
         appView.appInfo().classes(),
         clazz -> {
           ProgramMethodSet bridgesToRemoveForClass = ProgramMethodSet.create();
diff --git a/src/main/java/com/android/tools/r8/shaking/DiscardedChecker.java b/src/main/java/com/android/tools/r8/shaking/DiscardedChecker.java
index dfc170e..cbc4940 100644
--- a/src/main/java/com/android/tools/r8/shaking/DiscardedChecker.java
+++ b/src/main/java/com/android/tools/r8/shaking/DiscardedChecker.java
@@ -11,6 +11,7 @@
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.ThreadUtils;
 import java.util.ArrayList;
+import java.util.Collection;
 import java.util.List;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.ExecutorService;
@@ -49,7 +50,7 @@
   }
 
   public List<ProgramDefinition> run(
-      Iterable<DexProgramClass> classes, ExecutorService executorService)
+      Collection<DexProgramClass> classes, ExecutorService executorService)
       throws ExecutionException {
     assert failed.isEmpty();
 
diff --git a/src/main/java/com/android/tools/r8/shaking/EnqueuerDeferredTracingImpl.java b/src/main/java/com/android/tools/r8/shaking/EnqueuerDeferredTracingImpl.java
index 41c48cb..650aa8b 100644
--- a/src/main/java/com/android/tools/r8/shaking/EnqueuerDeferredTracingImpl.java
+++ b/src/main/java/com/android/tools/r8/shaking/EnqueuerDeferredTracingImpl.java
@@ -32,6 +32,7 @@
 import com.android.tools.r8.shaking.EnqueuerWorklist.EnqueuerAction;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.ThreadUtils;
+import com.android.tools.r8.utils.ThreadUtils.WorkLoad;
 import com.android.tools.r8.utils.Timing;
 import com.android.tools.r8.utils.collections.ProgramFieldMap;
 import com.android.tools.r8.utils.collections.ProgramFieldSet;
@@ -237,8 +238,9 @@
         new ConcurrentHashMap<>();
     ThreadUtils.processItems(
         methodsToProcess,
-        method -> rewriteMethod(method, initializedClassesWithContexts, prunedFields),
-        executorService);
+        (method, ignored) -> rewriteMethod(method, initializedClassesWithContexts, prunedFields),
+        executorService,
+        WorkLoad.HEAVY);
 
     // Register new InitClass instructions.
     initializedClassesWithContexts.forEach(
diff --git a/src/main/java/com/android/tools/r8/synthesis/SyntheticNaming.java b/src/main/java/com/android/tools/r8/synthesis/SyntheticNaming.java
index 95105f8..301d7b4 100644
--- a/src/main/java/com/android/tools/r8/synthesis/SyntheticNaming.java
+++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticNaming.java
@@ -65,7 +65,7 @@
   // TODO(b/214901256): Sharing of synthetic classes may lead to duplicate method errors.
   public final SyntheticKind NON_FIXED_INIT_TYPE_ARGUMENT =
       generator.forNonSharableInstanceClass("$IA");
-  public final SyntheticKind CONST_DYNAMIC = generator.forInstanceClass("$Condy");
+  public final SyntheticKind CONST_DYNAMIC = generator.forNonSharableInstanceClass("$Condy");
 
   // Method synthetics.
   public final SyntheticKind ENUM_UNBOXING_CHECK_NOT_ZERO_METHOD =
diff --git a/src/main/java/com/android/tools/r8/utils/BooleanBox.java b/src/main/java/com/android/tools/r8/utils/BooleanBox.java
index 5ad9494..554176e 100644
--- a/src/main/java/com/android/tools/r8/utils/BooleanBox.java
+++ b/src/main/java/com/android/tools/r8/utils/BooleanBox.java
@@ -59,4 +59,10 @@
   public boolean isAssigned() {
     return assigned;
   }
+
+  public Boolean getAndSet() {
+    boolean current = get();
+    set();
+    return current;
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/utils/ThreadUtils.java b/src/main/java/com/android/tools/r8/utils/ThreadUtils.java
index b0a6710..45c36b6 100644
--- a/src/main/java/com/android/tools/r8/utils/ThreadUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/ThreadUtils.java
@@ -5,6 +5,7 @@
 
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.utils.ListUtils.ReferenceAndIntConsumer;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Iterator;
@@ -16,9 +17,37 @@
 import java.util.concurrent.Executors;
 import java.util.concurrent.ForkJoinPool;
 import java.util.concurrent.Future;
+import java.util.function.Consumer;
 
 public class ThreadUtils {
 
+  public enum WorkLoad {
+    // The threshold for HEAVY is basically just a fan-out when we have two items to process.
+    HEAVY(2),
+    // The threshold for LIGHT has been found by running TiviIncremental benchmark in different
+    // configurations. For partitioning inputs in buckets of 3 and use threading on 4 or more was
+    // slightly better than threading on 3:
+    // Buckets of 3 with threshold 4:
+    // TiviIncrementalLibrary(RunTimeRaw): 28076 ms
+    // TiviIncrementalMerge(RunTimeRaw): 1429 ms
+    // TiviIncrementalProgram(RunTimeRaw): 26374 ms
+    // Buckets of 3 with threshold 3:
+    // TiviIncrementalLibrary(RunTimeRaw): 30347 ms
+    // TiviIncrementalMerge(RunTimeRaw): 1558 ms
+    // TiviIncrementalProgram(RunTimeRaw): 26638 ms
+    LIGHT(4);
+
+    private final int threshold;
+
+    WorkLoad(int threshold) {
+      this.threshold = threshold;
+    }
+
+    public int getThreshold() {
+      return threshold;
+    }
+  }
+
   public static final int NOT_SPECIFIED = -1;
 
   public static <T> Future<T> processAsynchronously(
@@ -71,18 +100,26 @@
     return awaitFuturesWithResults(futures);
   }
 
-  public static <T, E extends Exception> void processItems(
-      Iterable<T> items, ThrowingConsumer<T, E> consumer, ExecutorService executorService)
+  public static <T> void processItems(
+      Collection<T> items, Consumer<T> consumer, ExecutorService executorService)
       throws ExecutionException {
-    processItems(items, (item, i) -> consumer.accept(item), executorService);
+    processItems(items, (item, i) -> consumer.accept(item), executorService, WorkLoad.LIGHT);
   }
 
-  public static <T, E extends Exception> void processItems(
-      Iterable<T> items,
-      ThrowingReferenceIntConsumer<T, E> consumer,
-      ExecutorService executorService)
+  public static <T> void processItems(
+      Collection<T> items,
+      ReferenceAndIntConsumer<T> consumer,
+      ExecutorService executorService,
+      WorkLoad workLoad)
       throws ExecutionException {
-    processItems(items::forEach, consumer, executorService);
+    if (items.size() >= workLoad.getThreshold()) {
+      processItems(items::forEach, consumer::accept, executorService);
+    } else {
+      int counter = 0;
+      for (T item : items) {
+        consumer.accept(item, counter++);
+      }
+    }
   }
 
   public static <T, E extends Exception> void processItems(
diff --git a/src/test/java/com/android/tools/r8/desugar/constantdynamic/ConstantDynamicMultipleConstantsUsingSameSymbolicReferenceTest.java b/src/test/java/com/android/tools/r8/desugar/constantdynamic/ConstantDynamicMultipleConstantsUsingSameSymbolicReferenceTest.java
new file mode 100644
index 0000000..5ba0396
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/constantdynamic/ConstantDynamicMultipleConstantsUsingSameSymbolicReferenceTest.java
@@ -0,0 +1,141 @@
+// Copyright (c) 2023, 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.desugar.constantdynamic;
+
+import static com.android.tools.r8.DiagnosticsMatcher.diagnosticMessage;
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.junit.Assert.assertThrows;
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.DesugarTestConfiguration;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.TestRuntime.CfVm;
+import com.android.tools.r8.cf.CfVersion;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.StringUtils;
+import java.io.IOException;
+import java.lang.invoke.MethodHandles;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class ConstantDynamicMultipleConstantsUsingSameSymbolicReferenceTest extends TestBase {
+
+  @Parameter() public TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimes().withAllApiLevelsAlsoForCf().build();
+  }
+
+  private static final Class<?> MAIN_CLASS = Main.class;
+  private static final String EXPECTED_OUTPUT = StringUtils.lines("true");
+
+  @Test
+  public void testReference() throws Exception {
+    parameters.assumeJvmTestParameters();
+    assumeTrue(parameters.getRuntime().asCf().isNewerThanOrEqual(CfVm.JDK11));
+    testForJvm(parameters)
+        .addProgramClassFileData(getTransformedClasses())
+        .addProgramClasses(Main.class)
+        .run(parameters.getRuntime(), MAIN_CLASS)
+        .assertSuccessWithOutput(EXPECTED_OUTPUT);
+  }
+
+  @Test
+  public void testDesugaring() throws Exception {
+    testForDesugaring(parameters)
+        .addProgramClassFileData(getTransformedClasses())
+        .addProgramClasses(Main.class)
+        .run(parameters.getRuntime(), MAIN_CLASS)
+        .applyIf(
+            // When not desugaring the CF code requires JDK 11.
+            DesugarTestConfiguration::isNotDesugared,
+            r -> {
+              if (parameters.isCfRuntime()
+                  && parameters.getRuntime().asCf().isNewerThanOrEqual(CfVm.JDK11)) {
+                r.assertSuccessWithOutput(EXPECTED_OUTPUT);
+              } else {
+                r.assertFailureWithErrorThatThrows(UnsupportedClassVersionError.class);
+              }
+            })
+        .applyIf(
+            DesugarTestConfiguration::isDesugared, r -> r.assertSuccessWithOutput(EXPECTED_OUTPUT));
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    parameters.assumeR8TestParameters();
+    testForR8(parameters.getBackend())
+        .addProgramClassFileData(getTransformedClasses())
+        .addProgramClasses(Main.class)
+        .setMinApi(parameters)
+        .addKeepMainRule(MAIN_CLASS)
+        // TODO(b/198142613): There should not be a warnings on class references which are
+        //  desugared away.
+        .applyIf(
+            parameters.getApiLevel().isLessThan(AndroidApiLevel.O),
+            b -> b.addDontWarn("java.lang.invoke.MethodHandles$Lookup"))
+        // TODO(b/198142625): Support CONSTANT_Dynamic output for class files.
+        .applyIf(
+            parameters.isCfRuntime(),
+            r -> {
+              assertThrows(
+                  CompilationFailedException.class,
+                  () ->
+                      r.compileWithExpectedDiagnostics(
+                          diagnostics -> {
+                            diagnostics.assertOnlyErrors();
+                            diagnostics.assertErrorsMatch(
+                                diagnosticMessage(
+                                    containsString(
+                                        "Unsupported dynamic constant (not desugaring)")));
+                          }));
+            },
+            r ->
+                r.run(parameters.getRuntime(), MAIN_CLASS)
+                    .assertSuccessWithOutput(EXPECTED_OUTPUT));
+  }
+
+  // When ASM writes two dynamic constants with the same BSM and arguments they are canonicialized
+  // into the same symbolic reference.
+  private byte[] getTransformedClasses() throws IOException {
+    return transformer(A.class)
+        .setVersion(CfVersion.V11)
+        .transformConstStringToConstantDynamic(
+            "condy1", A.class, "myConstant", false, "constantName", Object.class)
+        .transformConstStringToConstantDynamic(
+            "condy2", A.class, "myConstant", false, "constantName", Object.class)
+        .transform();
+  }
+
+  public static class Main {
+
+    public static void main(String[] args) {
+      // This prints "true" due to the ASM canonicalization of the ConstantDynamic when writing.
+      System.out.println(A.getConstant1() == A.getConstant2());
+    }
+  }
+
+  public static class A {
+
+    public static Object getConstant1() {
+      return "condy1"; // Will be transformed to Constant_DYNAMIC.
+    }
+
+    public static Object getConstant2() {
+      return "condy2"; // Will be transformed to Constant_DYNAMIC.
+    }
+
+    private static Object myConstant(MethodHandles.Lookup lookup, String name, Class<?> type) {
+      return new Object();
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/desugar/constantdynamic/ConstantDynamicMultipleConstantsWithDifferentSymbolicReferenceUsingSameBSMAndArgumentsTest.java b/src/test/java/com/android/tools/r8/desugar/constantdynamic/ConstantDynamicMultipleConstantsWithDifferentSymbolicReferenceUsingSameBSMAndArgumentsTest.java
new file mode 100644
index 0000000..3bf967d
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/constantdynamic/ConstantDynamicMultipleConstantsWithDifferentSymbolicReferenceUsingSameBSMAndArgumentsTest.java
@@ -0,0 +1,592 @@
+// Copyright (c) 2023, 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.desugar.constantdynamic;
+
+import static com.android.tools.r8.DiagnosticsMatcher.diagnosticMessage;
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.junit.Assert.assertThrows;
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.DesugarTestConfiguration;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.TestRuntime.CfVm;
+import com.android.tools.r8.cf.CfVersion;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.StringUtils;
+import java.io.IOException;
+import java.lang.invoke.MethodHandles;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class ConstantDynamicMultipleConstantsWithDifferentSymbolicReferenceUsingSameBSMAndArgumentsTest
+    extends TestBase {
+
+  @Parameter() public TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimes().withAllApiLevelsAlsoForCf().build();
+  }
+
+  private static final String MAIN_CLASS = "A";
+  private static final String EXPECTED_OUTPUT = StringUtils.lines("false");
+
+  @Test
+  public void testReference() throws Exception {
+    parameters.assumeJvmTestParameters();
+    assumeTrue(parameters.getRuntime().asCf().isNewerThanOrEqual(CfVm.JDK11));
+    testForJvm(parameters)
+        .addProgramClassFileData(classFileData)
+        .disassemble()
+        .run(parameters.getRuntime(), MAIN_CLASS)
+        .assertSuccessWithOutput(EXPECTED_OUTPUT);
+  }
+
+  @Test
+  public void testDesugaring() throws Exception {
+    testForDesugaring(parameters)
+        .addProgramClassFileData(classFileData)
+        .run(parameters.getRuntime(), MAIN_CLASS)
+        .applyIf(
+            // When not desugaring the CF code requires JDK 11.
+            DesugarTestConfiguration::isNotDesugared,
+            r -> {
+              if (parameters.isCfRuntime()
+                  && parameters.getRuntime().asCf().isNewerThanOrEqual(CfVm.JDK11)) {
+                r.assertSuccessWithOutput(EXPECTED_OUTPUT);
+              } else {
+                r.assertFailureWithErrorThatThrows(UnsupportedClassVersionError.class);
+              }
+            })
+        .applyIf(
+            DesugarTestConfiguration::isDesugared, r -> r.assertSuccessWithOutput(EXPECTED_OUTPUT));
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    parameters.assumeR8TestParameters();
+    testForR8(parameters.getBackend())
+        .addProgramClassFileData(classFileData)
+        .setMinApi(parameters)
+        .addKeepMainRule(MAIN_CLASS)
+        // TODO(b/198142613): There should not be a warnings on class references which are
+        //  desugared away.
+        .applyIf(
+            parameters.getApiLevel().isLessThan(AndroidApiLevel.O),
+            b -> b.addDontWarn("java.lang.invoke.MethodHandles$Lookup"))
+        // TODO(b/198142625): Support CONSTANT_Dynamic output for class files.
+        .applyIf(
+            parameters.isCfRuntime(),
+            r -> {
+              assertThrows(
+                  CompilationFailedException.class,
+                  () ->
+                      r.compileWithExpectedDiagnostics(
+                          diagnostics -> {
+                            diagnostics.assertOnlyErrors();
+                            diagnostics.assertErrorsMatch(
+                                diagnosticMessage(
+                                    containsString(
+                                        "Unsupported dynamic constant (not desugaring)")));
+                          }));
+            },
+            r ->
+                r.run(parameters.getRuntime(), MAIN_CLASS)
+                    .assertSuccessWithOutput(EXPECTED_OUTPUT));
+  }
+
+  /*
+    This test was supposed to use the following test classes to have two dynamic constants
+    using exactly the same bootstrap method and arguments as set up in getTransformedClasses
+    below. However, ASM will canonicalize both dynamic constants and bootstrap methods, so instead
+    a class file directly from bytes is used.
+  */
+
+  private byte[] getTransformedClasses() throws IOException {
+      return transformer(A.class)
+          .setVersion(CfVersion.V11)
+          .transformConstStringToConstantDynamic(
+              "condy1", A.class, "myConstant", false, "constantName", Object.class)
+          .transformConstStringToConstantDynamic(
+              "condy2", A.class, "myConstant", false, "constantName", Object.class)
+          .transform();
+  }
+
+  public static class Main {
+
+    public static void main(String[] args) {
+      System.out.println(A.getConstant1() == A.getConstant2());
+    }
+  }
+
+  public static class A {
+
+    public static Object getConstant1() {
+      return "condy1"; // Will be transformed to Constant_DYNAMIC.
+    }
+
+    public static Object getConstant2() {
+      return "condy2"; // Will be transformed to Constant_DYNAMIC.
+    }
+
+    private static Object myConstant(MethodHandles.Lookup lookup, String name, Class<?> type) {
+      return new Object();
+    }
+  }
+
+/*
+
+Class file bytes for the following class file:
+
+Classfile A.class
+  Last modified Mar 17, 2023; size 1364 bytes
+  SHA-256 checksum 4379e359d727521479cee1aa5b6d711090b2777553454d9b667fe502a1b0daa9
+  Compiled from "A.java"
+public class A
+  minor version: 0
+  major version: 55
+  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
+  this_class: #2                          // A
+  super_class: #4                         // java/lang/Object
+  interfaces: 0, fields: 0, methods: 5, attributes: 3
+Constant pool:
+   #1 = Utf8               A
+   #2 = Class              #1             // A
+   #3 = Utf8               java/lang/Object
+   #4 = Class              #3             // java/lang/Object
+   #5 = Utf8               A.java
+   #6 = Utf8               java/lang/invoke/MethodHandles$Lookup
+   #7 = Class              #6             // java/lang/invoke/MethodHandles$Lookup
+   #8 = Utf8               java/lang/invoke/MethodHandles
+   #9 = Class              #8             // java/lang/invoke/MethodHandles
+  #10 = Utf8               Lookup
+  #11 = Utf8               <init>
+  #12 = Utf8               ()V
+  #13 = NameAndType        #11:#12        // "<init>":()V
+  #14 = Methodref          #4.#13         // java/lang/Object."<init>":()V
+  #15 = Utf8               this
+  #16 = Utf8               LA;
+  #17 = Utf8               getConstant1
+  #18 = Utf8               ()Ljava/lang/Object;
+  #19 = Utf8               myConstant
+  #20 = Utf8               (Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/Class;)Ljava/lang/Object;
+  #21 = NameAndType        #19:#20        // myConstant:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/Class;)Ljava/lang/Object;
+  #22 = Methodref          #2.#21         // A.myConstant:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/Class;)Ljava/lang/Object;
+  #23 = MethodHandle       6:#22          // REF_invokeStatic A.myConstant:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/Class;)Ljava/lang/Object;
+  #24 = Utf8               constantName
+  #25 = Utf8               Ljava/lang/Object;
+  #26 = NameAndType        #24:#25        // constantName:Ljava/lang/Object;
+  #27 = Dynamic            #0:#26         // #0:constantName:Ljava/lang/Object;
+  #28 = Utf8               getConstant2
+  #29 = Dynamic            #1:#26         // #1:constantName:Ljava/lang/Object;
+  #30 = Utf8               (Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/Class<*>;)Ljava/lang/Object;
+  #31 = Utf8               lookup
+  #32 = Utf8               Ljava/lang/invoke/MethodHandles$Lookup;
+  #33 = Utf8               name
+  #34 = Utf8               Ljava/lang/String;
+  #35 = Utf8               type
+  #36 = Utf8               Ljava/lang/Class<*>;
+  #37 = Utf8               Ljava/lang/Class;
+  #38 = Utf8               main
+  #39 = Utf8               ([Ljava/lang/String;)V
+  #40 = Utf8               java/lang/System
+  #41 = Class              #40            // java/lang/System
+  #42 = Utf8               out
+  #43 = Utf8               Ljava/io/PrintStream;
+  #44 = NameAndType        #42:#43        // out:Ljava/io/PrintStream;
+  #45 = Fieldref           #41.#44        // java/lang/System.out:Ljava/io/PrintStream;
+  #46 = NameAndType        #17:#18        // getConstant1:()Ljava/lang/Object;
+  #47 = Methodref          #2.#46         // A.getConstant1:()Ljava/lang/Object;
+  #48 = NameAndType        #28:#18        // getConstant2:()Ljava/lang/Object;
+  #49 = Methodref          #2.#48         // A.getConstant2:()Ljava/lang/Object;
+  #50 = Utf8               java/io/PrintStream
+  #51 = Class              #50            // java/io/PrintStream
+  #52 = Utf8               [Ljava/lang/String;
+  #53 = Class              #52            // "[Ljava/lang/String;"
+  #54 = Utf8               println
+  #55 = Utf8               (Z)V
+  #56 = NameAndType        #54:#55        // println:(Z)V
+  #57 = Methodref          #51.#56        // java/io/PrintStream.println:(Z)V
+  #58 = Utf8               Code
+  #59 = Utf8               LineNumberTable
+  #60 = Utf8               LocalVariableTable
+  #61 = Utf8               LocalVariableTypeTable
+  #62 = Utf8               Signature
+  #63 = Utf8               StackMapTable
+  #64 = Utf8               InnerClasses
+  #65 = Utf8               SourceFile
+  #66 = Utf8               BootstrapMethods
+{
+  public A();
+    descriptor: ()V
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=1, locals=1, args_size=1
+         0: aload_0
+         1: invokespecial #14                 // Method java/lang/Object."<init>":()V
+         4: return
+      LineNumberTable:
+        line 132: 0
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+            0       5     0  this   LA;
+
+  public static java.lang.Object getConstant1();
+    descriptor: ()Ljava/lang/Object;
+    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
+    Code:
+      stack=1, locals=0, args_size=0
+         0: ldc           #27                 // Dynamic #0:constantName:Ljava/lang/Object;
+         2: areturn
+      LineNumberTable:
+        line 135: 0
+
+  public static java.lang.Object getConstant2();
+    descriptor: ()Ljava/lang/Object;
+    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
+    Code:
+      stack=1, locals=0, args_size=0
+         0: ldc           #29                 // Dynamic #1:constantName:Ljava/lang/Object;
+         2: areturn
+      LineNumberTable:
+        line 139: 0
+
+  private static java.lang.Object myConstant(java.lang.invoke.MethodHandles$Lookup, java.lang.String, java.lang.Class<?>);
+    descriptor: (Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/Class;)Ljava/lang/Object;
+    flags: (0x000a) ACC_PRIVATE, ACC_STATIC
+    Code:
+      stack=2, locals=3, args_size=3
+         0: new           #4                  // class java/lang/Object
+         3: dup
+         4: invokespecial #14                 // Method java/lang/Object."<init>":()V
+         7: areturn
+      LineNumberTable:
+        line 143: 0
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+            0       8     0 lookup   Ljava/lang/invoke/MethodHandles$Lookup;
+            0       8     1  name   Ljava/lang/String;
+            0       8     2  type   Ljava/lang/Class;
+      LocalVariableTypeTable:
+        Start  Length  Slot  Name   Signature
+            0       8     2  type   Ljava/lang/Class<*>;
+    Signature: #30                          // (Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/Class<*>;)Ljava/lang/Object;
+
+  public static void main(java.lang.String[]);
+    descriptor: ([Ljava/lang/String;)V
+    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
+    Code:
+      stack=3, locals=1, args_size=1
+         0: getstatic     #45                 // Field java/lang/System.out:Ljava/io/PrintStream;
+         3: invokestatic  #47                 // Method getConstant1:()Ljava/lang/Object;
+         6: invokestatic  #49                 // Method getConstant2:()Ljava/lang/Object;
+         9: if_acmpne     16
+        12: iconst_1
+        13: goto          17
+        16: iconst_0
+        17: invokevirtual #57                 // Method java/io/PrintStream.println:(Z)V
+        20: return
+      StackMapTable: number_of_entries = 2
+        frame_type = 80 // same_locals_1_stack_item
+  stack = [ class java/io/PrintStream ]
+  frame_type = 255 // full_frame
+  offset_delta = 0
+  locals = [ class "[Ljava/lang/String;" ]
+  stack = [ class java/io/PrintStream, int ]
+  LineNumberTable:
+  line 12: 0
+  line 13: 20
+}
+InnerClasses:
+public static final #10= #7 of #9;      // Lookup=class java/lang/invoke/MethodHandles$Lookup of class java/lang/invoke/MethodHandles
+    SourceFile: "A.java"
+    BootstrapMethods:
+    0: #23 REF_invokeStatic A.myConstant:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/Class;)Ljava/lang/Object;
+    Method arguments:
+    1: #23 REF_invokeStatic A.myConstant:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/Class;)Ljava/lang/Object;
+    Method arguments:
+*/
+
+  private byte[] classFileData = {
+      -54, -2, -70, -66, 0, 0, 0, 55, 0, 67, 1, 0, 1, 65, 7, 0, 1,
+      1, 0, 16, 106, 97, 118, 97, 47, 108, 97, 110, 103, 47, 79, 98, 106,
+      101, 99, 116, 7, 0, 3, 1, 0, 6, 65, 46, 106, 97, 118, 97, 1,
+      0, 37, 106, 97, 118, 97, 47, 108, 97, 110, 103, 47, 105, 110, 118, 111,
+      107, 101, 47, 77, 101, 116, 104, 111, 100, 72, 97, 110, 100, 108, 101, 115,
+      36, 76, 111, 111, 107, 117, 112, 7, 0, 6, 1, 0, 30, 106, 97, 118,
+      97, 47, 108, 97, 110, 103, 47, 105, 110, 118, 111, 107, 101, 47, 77, 101,
+      116, 104, 111, 100, 72, 97, 110, 100, 108, 101, 115, 7, 0, 8, 1, 0,
+      6, 76, 111, 111, 107, 117, 112, 1, 0, 6, 60, 105, 110, 105, 116, 62,
+      1, 0, 3, 40, 41, 86, 12, 0, 11, 0, 12, 10, 0, 4, 0, 13,
+      1, 0, 4, 116, 104, 105, 115, 1, 0, 3, 76, 65, 59, 1, 0, 12,
+      103, 101, 116, 67, 111, 110, 115, 116, 97, 110, 116, 49, 1, 0, 20, 40,
+      41, 76, 106, 97, 118, 97, 47, 108, 97, 110, 103, 47, 79, 98, 106, 101,
+      99, 116, 59, 1, 0, 10, 109, 121, 67, 111, 110, 115, 116, 97, 110, 116,
+      1, 0, 94, 40, 76, 106, 97, 118, 97, 47, 108, 97, 110, 103, 47, 105,
+      110, 118, 111, 107, 101, 47, 77, 101, 116, 104, 111, 100, 72, 97, 110, 100,
+      108, 101, 115, 36, 76, 111, 111, 107, 117, 112, 59, 76, 106, 97, 118, 97,
+      47, 108, 97, 110, 103, 47, 83, 116, 114, 105, 110, 103, 59, 76, 106, 97,
+      118, 97, 47, 108, 97, 110, 103, 47, 67, 108, 97, 115, 115, 59, 41, 76,
+      106, 97, 118, 97, 47, 108, 97, 110, 103, 47, 79, 98, 106, 101, 99, 116,
+      59, 12, 0, 19, 0, 20, 10, 0, 2, 0, 21, 15, 6, 0, 22, 1,
+      0, 12, 99, 111, 110, 115, 116, 97, 110, 116, 78, 97, 109, 101, 1, 0,
+      18, 76, 106, 97, 118, 97, 47, 108, 97, 110, 103, 47, 79, 98, 106, 101,
+      99, 116, 59, 12, 0, 24, 0, 25, 17, 0, 0, 0, 26, 1, 0, 12,
+      103, 101, 116, 67, 111, 110, 115, 116, 97, 110, 116, 50, 17, 0, 1, 0,
+      26, 1, 0, 97, 40, 76, 106, 97, 118, 97, 47, 108, 97, 110, 103, 47,
+      105, 110, 118, 111, 107, 101, 47, 77, 101, 116, 104, 111, 100, 72, 97, 110,
+      100, 108, 101, 115, 36, 76, 111, 111, 107, 117, 112, 59, 76, 106, 97, 118,
+      97, 47, 108, 97, 110, 103, 47, 83, 116, 114, 105, 110, 103, 59, 76, 106,
+      97, 118, 97, 47, 108, 97, 110, 103, 47, 67, 108, 97, 115, 115, 60, 42,
+      62, 59, 41, 76, 106, 97, 118, 97, 47, 108, 97, 110, 103, 47, 79, 98,
+      106, 101, 99, 116, 59, 1, 0, 6, 108, 111, 111, 107, 117, 112, 1, 0,
+      39, 76, 106, 97, 118, 97, 47, 108, 97, 110, 103, 47, 105, 110, 118, 111,
+      107, 101, 47, 77, 101, 116, 104, 111, 100, 72, 97, 110, 100, 108, 101, 115,
+      36, 76, 111, 111, 107, 117, 112, 59, 1, 0, 4, 110, 97, 109, 101, 1,
+      0, 18, 76, 106, 97, 118, 97, 47, 108, 97, 110, 103, 47, 83, 116, 114,
+      105, 110, 103, 59, 1, 0, 4, 116, 121, 112, 101, 1, 0, 20, 76, 106,
+      97, 118, 97, 47, 108, 97, 110, 103, 47, 67, 108, 97, 115, 115, 60, 42,
+      62, 59, 1, 0, 17, 76, 106, 97, 118, 97, 47, 108, 97, 110, 103, 47,
+      67, 108, 97, 115, 115, 59, 1, 0, 4, 109, 97, 105, 110, 1, 0, 22,
+      40, 91, 76, 106, 97, 118, 97, 47, 108, 97, 110, 103, 47, 83, 116, 114,
+      105, 110, 103, 59, 41, 86, 1, 0, 16, 106, 97, 118, 97, 47, 108, 97,
+      110, 103, 47, 83, 121, 115, 116, 101, 109, 7, 0, 40, 1, 0, 3, 111,
+      117, 116, 1, 0, 21, 76, 106, 97, 118, 97, 47, 105, 111, 47, 80, 114,
+      105, 110, 116, 83, 116, 114, 101, 97, 109, 59, 12, 0, 42, 0, 43, 9,
+      0, 41, 0, 44, 12, 0, 17, 0, 18, 10, 0, 2, 0, 46, 12, 0,
+      28, 0, 18, 10, 0, 2, 0, 48, 1, 0, 19, 106, 97, 118, 97, 47,
+      105, 111, 47, 80, 114, 105, 110, 116, 83, 116, 114, 101, 97, 109, 7, 0,
+      50, 1, 0, 19, 91, 76, 106, 97, 118, 97, 47, 108, 97, 110, 103, 47,
+      83, 116, 114, 105, 110, 103, 59, 7, 0, 52, 1, 0, 7, 112, 114, 105,
+      110, 116, 108, 110, 1, 0, 4, 40, 90, 41, 86, 12, 0, 54, 0, 55,
+      10, 0, 51, 0, 56, 1, 0, 4, 67, 111, 100, 101, 1, 0, 15, 76,
+      105, 110, 101, 78, 117, 109, 98, 101, 114, 84, 97, 98, 108, 101, 1, 0,
+      18, 76, 111, 99, 97, 108, 86, 97, 114, 105, 97, 98, 108, 101, 84, 97,
+      98, 108, 101, 1, 0, 22, 76, 111, 99, 97, 108, 86, 97, 114, 105, 97,
+      98, 108, 101, 84, 121, 112, 101, 84, 97, 98, 108, 101, 1, 0, 9, 83,
+      105, 103, 110, 97, 116, 117, 114, 101, 1, 0, 13, 83, 116, 97, 99, 107,
+      77, 97, 112, 84, 97, 98, 108, 101, 1, 0, 12, 73, 110, 110, 101, 114,
+      67, 108, 97, 115, 115, 101, 115, 1, 0, 10, 83, 111, 117, 114, 99, 101,
+      70, 105, 108, 101, 1, 0, 16, 66, 111, 111, 116, 115, 116, 114, 97, 112,
+      77, 101, 116, 104, 111, 100, 115, 0, 33, 0, 2, 0, 4, 0, 0, 0,
+      0, 0, 5, 0, 1, 0, 11, 0, 12, 0, 1, 0, 58, 0, 0, 0,
+      47, 0, 1, 0, 1, 0, 0, 0, 5, 42, -73, 0, 14, -79, 0, 0,
+      0, 2, 0, 59, 0, 0, 0, 6, 0, 1, 0, 0, 0, -124, 0, 60,
+      0, 0, 0, 12, 0, 1, 0, 0, 0, 5, 0, 15, 0, 16, 0, 0,
+      0, 9, 0, 17, 0, 18, 0, 1, 0, 58, 0, 0, 0, 27, 0, 1,
+      0, 0, 0, 0, 0, 3, 18, 27, -80, 0, 0, 0, 1, 0, 59, 0,
+      0, 0, 6, 0, 1, 0, 0, 0, -121, 0, 9, 0, 28, 0, 18, 0,
+      1, 0, 58, 0, 0, 0, 27, 0, 1, 0, 0, 0, 0, 0, 3, 18,
+      29, -80, 0, 0, 0, 1, 0, 59, 0, 0, 0, 6, 0, 1, 0, 0,
+      0, -117, 0, 10, 0, 19, 0, 20, 0, 2, 0, 58, 0, 0, 0, 88,
+      0, 2, 0, 3, 0, 0, 0, 8, -69, 0, 4, 89, -73, 0, 14, -80,
+      0, 0, 0, 3, 0, 59, 0, 0, 0, 6, 0, 1, 0, 0, 0, -113,
+      0, 60, 0, 0, 0, 32, 0, 3, 0, 0, 0, 8, 0, 31, 0, 32,
+      0, 0, 0, 0, 0, 8, 0, 33, 0, 34, 0, 1, 0, 0, 0, 8,
+      0, 35, 0, 37, 0, 2, 0, 61, 0, 0, 0, 12, 0, 1, 0, 0,
+      0, 8, 0, 35, 0, 36, 0, 2, 0, 62, 0, 0, 0, 2, 0, 30,
+      0, 9, 0, 38, 0, 39, 0, 1, 0, 58, 0, 0, 0, 75, 0, 3,
+      0, 1, 0, 0, 0, 21, -78, 0, 45, -72, 0, 47, -72, 0, 49, -90,
+      0, 7, 4, -89, 0, 4, 3, -74, 0, 57, -79, 0, 0, 0, 2, 0,
+      63, 0, 0, 0, 20, 0, 2, 80, 7, 0, 51, -1, 0, 0, 0, 1,
+      7, 0, 53, 0, 2, 7, 0, 51, 1, 0, 59, 0, 0, 0, 10, 0,
+      2, 0, 0, 0, 12, 0, 20, 0, 13, 0, 3, 0, 64, 0, 0, 0,
+      10, 0, 1, 0, 7, 0, 9, 0, 10, 0, 25, 0, 65, 0, 0, 0,
+      2, 0, 5, 0, 66, 0, 0, 0, 10, 0, 2, 0, 23, 0, 0, 0,
+      23, 0, 0
+  };
+
+  /*
+
+  The class file bytes above was generated from the following ASM visitor code using ASM with the
+  patch below applied (was applied on 443339a964352dcec4dd3915de8f13188920d3ac).
+
+  The thing to note is that the two calls
+
+    methodVisitor.visitLdcInsn(new ConstantDynamic(...));
+
+  have the exact same arguments, which ASM will canonicalize making it impossible to
+  write the test using standard ASM visitor.
+
+  import java.nio.file.Files;
+  import java.nio.file.Paths;
+
+  import org.objectweb.asm.AnnotationVisitor;
+  import org.objectweb.asm.Attribute;
+  import org.objectweb.asm.ClassReader;
+  import org.objectweb.asm.ClassWriter;
+  import org.objectweb.asm.ConstantDynamic;
+  import org.objectweb.asm.FieldVisitor;
+  import org.objectweb.asm.Handle;
+  import org.objectweb.asm.Label;
+  import org.objectweb.asm.MethodVisitor;
+  import org.objectweb.asm.Opcodes;
+  import org.objectweb.asm.RecordComponentVisitor;
+  import org.objectweb.asm.Type;
+  import org.objectweb.asm.TypePath;
+
+  public class DC implements Opcodes {
+
+    public static byte[] dump () throws Exception {
+
+      ClassWriter classWriter = new ClassWriter(0);
+      FieldVisitor fieldVisitor;
+      RecordComponentVisitor recordComponentVisitor;
+      MethodVisitor methodVisitor;
+      AnnotationVisitor annotationVisitor0;
+
+      classWriter.visit(V11, ACC_PUBLIC | ACC_SUPER, "A", null, "java/lang/Object", null);
+
+      classWriter.visitSource("A.java", null);
+
+      classWriter.visitInnerClass("java/lang/invoke/MethodHandles$Lookup", "java/lang/invoke/MethodHandles", "Lookup", ACC_PUBLIC | ACC_FINAL | ACC_STATIC);
+
+      {
+        methodVisitor = classWriter.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
+        methodVisitor.visitCode();
+        Label label0 = new Label();
+        methodVisitor.visitLabel(label0);
+        methodVisitor.visitLineNumber(132, label0);
+        methodVisitor.visitVarInsn(ALOAD, 0);
+        methodVisitor.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
+        methodVisitor.visitInsn(RETURN);
+        Label label1 = new Label();
+        methodVisitor.visitLabel(label1);
+        methodVisitor.visitLocalVariable("this", "LA;", null, label0, label1, 0);
+        methodVisitor.visitMaxs(1, 1);
+        methodVisitor.visitEnd();
+      }
+      {
+        methodVisitor = classWriter.visitMethod(ACC_PUBLIC | ACC_STATIC, "getConstant1", "()Ljava/lang/Object;", null, null);
+        methodVisitor.visitCode();
+        Label label0 = new Label();
+        methodVisitor.visitLabel(label0);
+        methodVisitor.visitLineNumber(135, label0);
+        methodVisitor.visitLdcInsn(new ConstantDynamic("constantName", "Ljava/lang/Object;", new Handle(Opcodes.H_INVOKESTATIC, "A", "myConstant", "(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/Class;)Ljava/lang/Object;", false), new Object[] {}));
+        methodVisitor.visitInsn(ARETURN);
+        methodVisitor.visitMaxs(1, 0);
+        methodVisitor.visitEnd();
+      }
+      {
+        methodVisitor = classWriter.visitMethod(ACC_PUBLIC | ACC_STATIC, "getConstant2", "()Ljava/lang/Object;", null, null);
+        methodVisitor.visitCode();
+        Label label0 = new Label();
+        methodVisitor.visitLabel(label0);
+        methodVisitor.visitLineNumber(139, label0);
+        methodVisitor.visitLdcInsn(new ConstantDynamic("constantName", "Ljava/lang/Object;", new Handle(Opcodes.H_INVOKESTATIC, "A", "myConstant", "(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/Class;)Ljava/lang/Object;", false), new Object[] {}));
+        methodVisitor.visitInsn(ARETURN);
+        methodVisitor.visitMaxs(1, 0);
+        methodVisitor.visitEnd();
+      }
+      {
+        methodVisitor = classWriter.visitMethod(ACC_PRIVATE | ACC_STATIC, "myConstant", "(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/Class;)Ljava/lang/Object;", "(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/Class<*>;)Ljava/lang/Object;", null);
+        methodVisitor.visitCode();
+        Label label0 = new Label();
+        methodVisitor.visitLabel(label0);
+        methodVisitor.visitLineNumber(143, label0);
+        methodVisitor.visitTypeInsn(NEW, "java/lang/Object");
+        methodVisitor.visitInsn(DUP);
+        methodVisitor.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
+        methodVisitor.visitInsn(ARETURN);
+        Label label1 = new Label();
+        methodVisitor.visitLabel(label1);
+        methodVisitor.visitLocalVariable("lookup", "Ljava/lang/invoke/MethodHandles$Lookup;", null, label0, label1, 0);
+        methodVisitor.visitLocalVariable("name", "Ljava/lang/String;", null, label0, label1, 1);
+        methodVisitor.visitLocalVariable("type", "Ljava/lang/Class;", "Ljava/lang/Class<*>;", label0, label1, 2);
+        methodVisitor.visitMaxs(2, 3);
+        methodVisitor.visitEnd();
+      }
+      {
+        methodVisitor = classWriter.visitMethod(ACC_PUBLIC | ACC_STATIC, "main", "([Ljava/lang/String;)V", null, null);
+        methodVisitor.visitCode();
+        Label label0 = new Label();
+        methodVisitor.visitLabel(label0);
+        methodVisitor.visitLineNumber(12, label0);
+        methodVisitor.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
+        methodVisitor.visitMethodInsn(INVOKESTATIC, "A", "getConstant1", "()Ljava/lang/Object;", false);
+        methodVisitor.visitMethodInsn(INVOKESTATIC, "A", "getConstant2", "()Ljava/lang/Object;", false);
+        Label label1 = new Label();
+        methodVisitor.visitJumpInsn(IF_ACMPNE, label1);
+        methodVisitor.visitInsn(ICONST_1);
+        Label label2 = new Label();
+        methodVisitor.visitJumpInsn(GOTO, label2);
+        methodVisitor.visitLabel(label1);
+        methodVisitor.visitFrame(Opcodes.F_SAME1, 0, null, 1, new Object[] {"java/io/PrintStream"});
+        methodVisitor.visitInsn(ICONST_0);
+        methodVisitor.visitLabel(label2);
+        methodVisitor.visitFrame(Opcodes.F_FULL, 1, new Object[] {"[Ljava/lang/String;"}, 2, new Object[] {"java/io/PrintStream", Opcodes.INTEGER});
+        methodVisitor.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Z)V", false);
+        Label label3 = new Label();
+        methodVisitor.visitLabel(label3);
+        methodVisitor.visitLineNumber(13, label3);
+        methodVisitor.visitInsn(RETURN);
+        methodVisitor.visitMaxs(3, 1);
+        methodVisitor.visitEnd();
+      }
+      classWriter.visitEnd();
+
+      return classWriter.toByteArray();
+    }
+
+    public static void main(String[] args) throws Exception {
+      Files.write(Paths.get("A.class"), DC.dump());
+    }
+  }
+
+  diff --git a/asm/src/main/java/org/objectweb/asm/SymbolTable.java b/asm/src/main/java/org/objectweb/asm/SymbolTable.java
+index a2f26f18..999620c5 100644
+--- a/asm/src/main/java/org/objectweb/asm/SymbolTable.java
++++ b/asm/src/main/java/org/objectweb/asm/SymbolTable.java
+@@ -922,17 +922,6 @@ final class SymbolTable {
+   private Symbol addConstantDynamicOrInvokeDynamicReference(
+       final int tag, final String name, final String descriptor, final int bootstrapMethodIndex) {
+     int hashCode = hash(tag, name, descriptor, bootstrapMethodIndex);
+-    Entry entry = get(hashCode);
+-    while (entry != null) {
+-      if (entry.tag == tag
+-          && entry.hashCode == hashCode
+-          && entry.data == bootstrapMethodIndex
+-          && entry.name.equals(name)
+-          && entry.value.equals(descriptor)) {
+-        return entry;
+-      }
+-      entry = entry.next;
+-    }
+     constantPool.put122(tag, bootstrapMethodIndex, addConstantNameAndType(name, descriptor));
+     return put(
+         new Entry(
+@@ -1094,24 +1083,6 @@ final class SymbolTable {
+  private Symbol addBootstrapMethod(final int offset, final int length, final int hashCode) {
+    final byte[] bootstrapMethodsData = bootstrapMethods.data;
+-    Entry entry = get(hashCode);
+-    while (entry != null) {
+-      if (entry.tag == Symbol.BOOTSTRAP_METHOD_TAG && entry.hashCode == hashCode) {
+-        int otherOffset = (int) entry.data;
+-        boolean isSameBootstrapMethod = true;
+-        for (int i = 0; i < length; ++i) {
+-          if (bootstrapMethodsData[offset + i] != bootstrapMethodsData[otherOffset + i]) {
+-            isSameBootstrapMethod = false;
+-            break;
+-          }
+-        }
+-        if (isSameBootstrapMethod) {
+-          bootstrapMethods.length = offset; // Revert to old position.
+-          return entry;
+-        }
+-      }
+-      entry = entry.next;
+-    }
+    return put(new Entry(bootstrapMethodCount++, Symbol.BOOTSTRAP_METHOD_TAG, offset, hashCode));
+  }
+
+*/
+
+}
diff --git a/src/test/java/com/android/tools/r8/desugar/constantdynamic/ConstantDynamicRegress272346803Test.java b/src/test/java/com/android/tools/r8/desugar/constantdynamic/ConstantDynamicRegress272346803Test.java
new file mode 100644
index 0000000..bec0148
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/constantdynamic/ConstantDynamicRegress272346803Test.java
@@ -0,0 +1,159 @@
+// Copyright (c) 2023, 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.desugar.constantdynamic;
+
+import static com.android.tools.r8.DiagnosticsMatcher.diagnosticMessage;
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.junit.Assert.assertThrows;
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.DesugarTestConfiguration;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.TestRuntime.CfVm;
+import com.android.tools.r8.cf.CfVersion;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.StringUtils;
+import java.io.IOException;
+import java.lang.invoke.MethodHandles;
+import java.util.Arrays;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class ConstantDynamicRegress272346803Test extends TestBase {
+
+  @Parameter() public TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimes().withAllApiLevelsAlsoForCf().build();
+  }
+
+  private static final Class<?> MAIN_CLASS = Main.class;
+  private static final String EXPECTED_OUTPUT = StringUtils.lines("A", "B");
+
+  @Test
+  public void testReference() throws Exception {
+    parameters.assumeJvmTestParameters();
+    assumeTrue(parameters.getRuntime().asCf().isNewerThanOrEqual(CfVm.JDK11));
+    testForJvm(parameters)
+        .addProgramClasses(Main.class)
+        .addProgramClassFileData(getTransformedClasses())
+        .run(parameters.getRuntime(), MAIN_CLASS)
+        .assertSuccessWithOutput(EXPECTED_OUTPUT);
+  }
+
+  @Test
+  public void testDesugaring() throws Exception {
+    testForDesugaring(parameters)
+        .addProgramClasses(Main.class)
+        .addProgramClassFileData(getTransformedClasses())
+        .run(parameters.getRuntime(), MAIN_CLASS)
+        .applyIf(
+            // When not desugaring the CF code requires JDK 11.
+            DesugarTestConfiguration::isNotDesugared,
+            r -> {
+              if (parameters.isCfRuntime()
+                  && parameters.getRuntime().asCf().isNewerThanOrEqual(CfVm.JDK11)) {
+                r.assertSuccessWithOutput(EXPECTED_OUTPUT);
+              } else {
+                r.assertFailureWithErrorThatThrows(UnsupportedClassVersionError.class);
+              }
+            })
+        .applyIf(
+            DesugarTestConfiguration::isDesugared, r -> r.assertSuccessWithOutput(EXPECTED_OUTPUT));
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    parameters.assumeR8TestParameters();
+    testForR8(parameters.getBackend())
+        .addProgramClasses(Main.class)
+        .addProgramClassFileData(getTransformedClasses())
+        .setMinApi(parameters)
+        .addKeepMainRule(MAIN_CLASS)
+        // Access modification is required for inlining the get method from the desugared constant
+        // dynamic class.
+        .allowAccessModification()
+        // TODO(b/198142613): There should not be a warnings on class references which are
+        //  desugared away.
+        .applyIf(
+            parameters.getApiLevel().isLessThan(AndroidApiLevel.O),
+            b -> b.addDontWarn("java.lang.invoke.MethodHandles$Lookup"))
+        // TODO(b/198142625): Support CONSTANT_Dynamic output for class files.
+        .applyIf(
+            parameters.isCfRuntime(),
+            r -> {
+              assertThrows(
+                  CompilationFailedException.class,
+                  () ->
+                      r.compileWithExpectedDiagnostics(
+                          diagnostics -> {
+                            diagnostics.assertOnlyErrors();
+                            diagnostics.assertErrorsMatch(
+                                diagnosticMessage(
+                                    containsString(
+                                        "Unsupported dynamic constant (not desugaring)")));
+                          }));
+            },
+            r ->
+                r.run(parameters.getRuntime(), MAIN_CLASS)
+                    .assertSuccessWithOutput(EXPECTED_OUTPUT));
+  }
+
+  private List<byte[]> getTransformedClasses() throws IOException {
+    return Arrays.asList(
+        transformer(A.class)
+            .setVersion(CfVersion.V11)
+            .transformConstStringToConstantDynamic(
+                "condy1", A.class, "myConstant", false, "constantName", String.class)
+            .transform(),
+        transformer(B.class)
+            .setVersion(CfVersion.V11)
+            .transformConstStringToConstantDynamic(
+                "condy1", B.class, "myConstant", false, "constantName", String.class)
+            .transform());
+  }
+
+  // When R8 optimize this code the getter for the two constant dynamics will be inlined into
+  // Main.main. This leaves the synthetic constant dynamic classes with just two static fields.
+  // The synthetic sharing cannot share these two synthetics as that would leave only one constant.
+  // See b/272346803 for details.
+  public static class Main {
+
+    public static void main(String[] args) {
+      System.out.println(A.getConstant());
+      System.out.println(B.getConstant());
+    }
+  }
+
+  public static class A {
+
+    public static String getConstant() {
+      return "condy1"; // Will be transformed to Constant_DYNAMIC.
+    }
+
+    private static Object myConstant(MethodHandles.Lookup lookup, String name, Class<?> type) {
+      return "A";
+    }
+  }
+
+  public static class B {
+
+    public static String getConstant() {
+      return "condy1"; // Will be transformed to Constant_DYNAMIC.
+    }
+
+    private static Object myConstant(MethodHandles.Lookup lookup, String name, Class<?> type) {
+      return "B";
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/IrInjectionTestBase.java b/src/test/java/com/android/tools/r8/ir/IrInjectionTestBase.java
index d11c68e..0d23c77 100644
--- a/src/test/java/com/android/tools/r8/ir/IrInjectionTestBase.java
+++ b/src/test/java/com/android/tools/r8/ir/IrInjectionTestBase.java
@@ -101,7 +101,7 @@
 
     public String run() throws IOException {
       Timing timing = Timing.empty();
-      IRConverter converter = new IRConverter(appView, timing);
+      IRConverter converter = new IRConverter(appView);
       converter.replaceCodeForTesting(code);
       AndroidApp app = writeDex();
       return runOnArtRaw(app, DEFAULT_MAIN_CLASS_NAME).stdout;
diff --git a/tools/compiledump.py b/tools/compiledump.py
index c313cb9..39315c9 100755
--- a/tools/compiledump.py
+++ b/tools/compiledump.py
@@ -415,6 +415,8 @@
         f.write(line)
 
 def clean_config_line(line, minify, optimize, shrink):
+  if line.lstrip().startswith('#'):
+    return False
   if ('-injars' in line or '-libraryjars' in line or
       '-print' in line or '-applymapping' in line):
     return True