Merge commit '06a3766c088acb54072bd661e9e5421cc4926a15' into dev-release
diff --git a/src/main/java/com/android/tools/r8/D8.java b/src/main/java/com/android/tools/r8/D8.java
index 7335dbb..998b838 100644
--- a/src/main/java/com/android/tools/r8/D8.java
+++ b/src/main/java/com/android/tools/r8/D8.java
@@ -186,7 +186,9 @@
 
       if (!options.mainDexKeepRules.isEmpty()) {
         MainDexInfo mainDexInfo =
-            new GenerateMainDexList(options).traceMainDex(executor, appView.appInfo().app());
+            new GenerateMainDexList(options)
+                .traceMainDex(
+                    executor, appView.appInfo().app(), appView.appInfo().getMainDexInfo());
         appView.setAppInfo(appView.appInfo().rebuildWithMainDexInfo(mainDexInfo));
       }
 
diff --git a/src/main/java/com/android/tools/r8/GenerateMainDexList.java b/src/main/java/com/android/tools/r8/GenerateMainDexList.java
index 2ebde33..07783b3 100644
--- a/src/main/java/com/android/tools/r8/GenerateMainDexList.java
+++ b/src/main/java/com/android/tools/r8/GenerateMainDexList.java
@@ -49,7 +49,7 @@
       //  consumer.
       DexApplication application = new ApplicationReader(app, options, timing).read(executor);
       List<String> result = new ArrayList<>();
-      traceMainDex(executor, application)
+      traceMainDex(executor, application, MainDexInfo.none())
           .forEach(type -> result.add(type.toBinaryName() + ".class"));
       Collections.sort(result);
       if (options.mainDexListConsumer != null) {
@@ -62,10 +62,11 @@
     }
   }
 
-  public MainDexInfo traceMainDex(ExecutorService executor, DexApplication application)
+  public MainDexInfo traceMainDex(
+      ExecutorService executor, DexApplication application, MainDexInfo existingMainDexInfo)
       throws ExecutionException {
     AppView<? extends AppInfoWithClassHierarchy> appView =
-        AppView.createForR8(application.toDirect());
+        AppView.createForR8(application.toDirect(), existingMainDexInfo);
     appView.setAppServices(AppServices.builder(appView).build());
 
     MainDexListBuilder.checkForAssumedLibraryTypes(appView.appInfo());
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfInvoke.java b/src/main/java/com/android/tools/r8/cf/code/CfInvoke.java
index 00d4c93..70fe54d 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfInvoke.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfInvoke.java
@@ -5,6 +5,7 @@
 
 import com.android.tools.r8.cf.CfPrinter;
 import com.android.tools.r8.dex.Constants;
+import com.android.tools.r8.errors.CompilationError;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.CfCompareHelper;
@@ -164,12 +165,20 @@
     }
   }
 
+  public boolean isInvokeConstructor(DexItemFactory dexItemFactory) {
+    return getMethod().isInstanceInitializer(dexItemFactory);
+  }
+
   public boolean isInvokeSuper(DexType clazz) {
     return opcode == Opcodes.INVOKESPECIAL &&
         method.holder != clazz &&
         !method.name.toString().equals(Constants.INSTANCE_INITIALIZER_NAME);
   }
 
+  public boolean isInvokeSpecial() {
+    return opcode == Opcodes.INVOKESPECIAL;
+  }
+
   public boolean isInvokeStatic() {
     return opcode == Opcodes.INVOKESTATIC;
   }
@@ -225,11 +234,7 @@
           if (method.name.toString().equals(Constants.INSTANCE_INITIALIZER_NAME)) {
             type = Type.DIRECT;
           } else if (code.getOriginalHolder() == method.holder) {
-            MethodAndInvokeType methodAndInvokeType =
-                transformInvokeSpecialToNonInitMethodOnHolder(
-                    builder.appView, code, builder.getProgramMethod());
-            type = methodAndInvokeType.getInvokeType();
-            canonicalMethod = methodAndInvokeType.getMethod();
+            type = invokeTypeForInvokeSpecialToNonInitMethodOnHolder(builder.appView, code);
           } else {
             type = Type.SUPER;
           }
@@ -364,26 +369,8 @@
     return true;
   }
 
-  private static class MethodAndInvokeType {
-    private final DexMethod method;
-    private final Invoke.Type invokeType;
-
-    private MethodAndInvokeType(DexMethod method, Type invokeType) {
-      this.method = method;
-      this.invokeType = invokeType;
-    }
-
-    public DexMethod getMethod() {
-      return method;
-    }
-
-    public Type getInvokeType() {
-      return invokeType;
-    }
-  }
-
-  private MethodAndInvokeType transformInvokeSpecialToNonInitMethodOnHolder(
-      AppView<?> appView, CfSourceCode code, ProgramMethod context) {
+  private Type invokeTypeForInvokeSpecialToNonInitMethodOnHolder(
+      AppView<?> appView, CfSourceCode code) {
     boolean desugaringEnabled = appView.options().isInterfaceMethodDesugaringEnabled();
     MethodLookupResult lookupResult = appView.graphLens().lookupMethod(method, method, Type.DIRECT);
     if (lookupResult.getType() == Type.VIRTUAL) {
@@ -391,38 +378,30 @@
       // publicized to be final. For example, if a private method A.m() is publicized, and A is
       // subsequently merged with a class B, with declares a public non-final method B.m(), then the
       // horizontal class merger will merge A.m() and B.m() into a new non-final public method.
-      return new MethodAndInvokeType(method, Type.VIRTUAL);
+      return Type.VIRTUAL;
     }
     DexMethod rewrittenMethod = lookupResult.getReference();
-    DexEncodedMethod encodedMethod = lookupMethodOnHolder(appView, rewrittenMethod);
-    if (encodedMethod == null) {
+    DexEncodedMethod definition = lookupMethodOnHolder(appView, rewrittenMethod);
+    if (definition == null) {
       // The method is not defined on the class, we can use super to target. When desugaring
       // default interface methods, it is expected they are targeted with invoke-direct.
-      return new MethodAndInvokeType(
-          method, this.itf && desugaringEnabled ? Type.DIRECT : Type.SUPER);
+      return this.itf && desugaringEnabled ? Type.DIRECT : Type.SUPER;
     }
-    if (encodedMethod.isPrivateMethod() || !encodedMethod.isVirtualMethod()) {
-      return new MethodAndInvokeType(method, Type.DIRECT);
+    if (definition.isPrivateMethod() || !definition.isVirtualMethod()) {
+      return Type.DIRECT;
     }
-    if (encodedMethod.accessFlags.isFinal()) {
+    if (definition.isFinal()) {
       // This method is final which indicates no subtype will overwrite it, we can use
       // invoke-virtual.
-      return new MethodAndInvokeType(method, Type.VIRTUAL);
+      return Type.VIRTUAL;
     }
-    if (this.itf && encodedMethod.isDefaultMethod()) {
-      return new MethodAndInvokeType(method, desugaringEnabled ? Type.DIRECT : Type.SUPER);
+    if (itf && definition.isDefaultMethod()) {
+      return desugaringEnabled ? Type.DIRECT : Type.SUPER;
     }
-    assert encodedMethod.isNonPrivateVirtualMethod();
-    assert context.getHolderType() == method.holder;
-    // This is an invoke-special to a virtual method on invoke-special method holder.
-    // The invoke should be rewritten with a bridge.
-    DexMethod directMethod =
-        appView.getInvokeSpecialBridgeSynthesizer().registerBridgeForMethod(encodedMethod);
-    // In R8 the target should have been inserted in the enqueuer,
-    // while in D8, the target is inserted at the end of the compilation.
-    assert appView.enableWholeProgramOptimizations()
-        == (context.getHolder().lookupDirectMethod(directMethod) != null);
-    return new MethodAndInvokeType(directMethod, Type.DIRECT);
+    // We cannot emulate the semantics of invoke-special in this case and should throw a compilation
+    // error.
+    throw new CompilationError(
+        "Failed to compile unsupported use of invokespecial", code.getOrigin());
   }
 
   private DexEncodedMethod lookupMethodOnHolder(AppView<?> appView, DexMethod method) {
diff --git a/src/main/java/com/android/tools/r8/contexts/CompilationContext.java b/src/main/java/com/android/tools/r8/contexts/CompilationContext.java
new file mode 100644
index 0000000..aa3247c
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/contexts/CompilationContext.java
@@ -0,0 +1,191 @@
+// Copyright (c) 2021, 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.contexts;
+
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.utils.InternalOptions;
+import com.google.common.hash.Hasher;
+import com.google.common.hash.Hashing;
+import java.util.HashSet;
+import java.util.Set;
+import java.util.function.Consumer;
+
+public class CompilationContext {
+
+  // Internal contract to compute a unique suffix for synthetics.
+  private abstract static class ContextDescriptorProvider {
+
+    // Method to construct a fully qualified description of the context.
+    // This is used to ensure that all contexts are unique during compilation.
+    abstract StringBuilder buildContextDescriptorForTesting(StringBuilder builder);
+
+    // Build a suffix to append to synthetic definitions.
+    abstract StringBuilder buildSyntheticSuffix(StringBuilder builder);
+  }
+
+  /**
+   * Create the initial compilation context.
+   *
+   * <p>This context should be a singleton for a given compilation allocated by AppView.
+   */
+  public static CompilationContext createInitialContext(InternalOptions options) {
+    return new CompilationContext(options);
+  }
+
+  private final Consumer<String> testingConsumer;
+  private final Set<String> seenSetForTesting = new HashSet<>();
+  private int nextProcessorId = 0;
+
+  private CompilationContext(InternalOptions options) {
+    testingConsumer = options.testing.processingContextsConsumer;
+  }
+
+  private boolean verifyContext(ContextDescriptorProvider context) {
+    String descriptor = context.buildContextDescriptorForTesting(new StringBuilder()).toString();
+    String suffix = context.buildSyntheticSuffix(new StringBuilder()).toString();
+    assert descriptor.endsWith(suffix);
+    if (testingConsumer != null) {
+      testingConsumer.accept(descriptor);
+    }
+    assert seenSetForTesting.add(descriptor)
+        : "Duplicated use of context descriptor: " + descriptor;
+    return true;
+  }
+
+  /**
+   * Creates the context for a "processor".
+   *
+   * <p>A "processor" is just a compilation task but which is deterministically ordered as part of
+   * the full compilation pipeline. Thus, this method should only be called on the main-thread
+   * ensuring that the assigned ids are deterministic. The id itself has not particular meaning.
+   */
+  public ProcessorContext createProcessorContext() {
+    ProcessorContext processorContext = new ProcessorContext(this, nextProcessorId++);
+    assert verifyContext(processorContext);
+    return processorContext;
+  }
+
+  public static class ProcessorContext extends ContextDescriptorProvider {
+    private final CompilationContext parent;
+    private final int processorId;
+
+    private ProcessorContext(CompilationContext parent, int processorId) {
+      this.parent = parent;
+      this.processorId = processorId;
+    }
+
+    private boolean verifyContext(ContextDescriptorProvider context) {
+      assert parent.verifyContext(context);
+      return true;
+    }
+
+    /**
+     * Create processing context for a single method.
+     *
+     * <p>There should only ever be a single allocation of the particular method-processing context.
+     * This is generally ensured by, eg, a MethodProcessor, having private access to the processing
+     * context and ensuring a safe single allocation of the individual method-processing contexts.
+     */
+    public MethodProcessingContext createMethodProcessingContext(ProgramMethod method) {
+      MethodProcessingContext methodProcessingContext = new MethodProcessingContext(this, method);
+      assert verifyContext(methodProcessingContext);
+      return methodProcessingContext;
+    }
+
+    private StringBuilder buildSuffix(StringBuilder builder) {
+      return builder.append('$').append(processorId);
+    }
+
+    @Override
+    StringBuilder buildContextDescriptorForTesting(StringBuilder builder) {
+      return buildSuffix(builder);
+    }
+
+    @Override
+    StringBuilder buildSyntheticSuffix(StringBuilder builder) {
+      return buildSuffix(builder);
+    }
+  }
+
+  /** Description of the method context from which to synthesize. */
+  public static class MethodProcessingContext extends ContextDescriptorProvider {
+    private final ProcessorContext parent;
+    private final ProgramMethod method;
+    private int nextId = 0;
+
+    private MethodProcessingContext(ProcessorContext parent, ProgramMethod method) {
+      this.parent = parent;
+      this.method = method;
+    }
+
+    /**
+     * Create a unique processing context.
+     *
+     * <p>The uniqueness of the context requires that the parent, eg, method-context, is unique and
+     * that the processing of that entity is such that the calls to this method happen in a
+     * deterministic order, eg, by the processing of method instructions being single threaded.
+     */
+    public UniqueContext createUniqueContext() {
+      UniqueContext uniqueContext = new UniqueContext(this, nextId++);
+      assert parent.verifyContext(uniqueContext);
+      return uniqueContext;
+    }
+
+    DexProgramClass getClassContext() {
+      return method.getHolder();
+    }
+
+    private StringBuilder buildSuffix(StringBuilder builder) {
+      // TODO(b/172194101): Sanitize the method descriptor instead of hashing.
+      Hasher hasher = Hashing.sha256().newHasher();
+      method.getReference().hash(hasher);
+      return builder.append('$').append(hasher.hash().toString());
+    }
+
+    @Override
+    StringBuilder buildContextDescriptorForTesting(StringBuilder builder) {
+      // Put the type first in the context descriptor.
+      builder.append(getClassContext().getType().toDescriptorString());
+      return buildSuffix(parent.buildContextDescriptorForTesting(builder));
+    }
+
+    @Override
+    StringBuilder buildSyntheticSuffix(StringBuilder builder) {
+      return buildSuffix(parent.buildSyntheticSuffix(builder));
+    }
+  }
+
+  public static class UniqueContext extends ContextDescriptorProvider {
+    private final MethodProcessingContext parent;
+    private final int positionId;
+
+    private UniqueContext(MethodProcessingContext parent, int positionId) {
+      this.parent = parent;
+      this.positionId = positionId;
+    }
+
+    private StringBuilder buildSuffix(StringBuilder builder) {
+      return builder.append('$').append(positionId);
+    }
+
+    @Override
+    StringBuilder buildContextDescriptorForTesting(StringBuilder builder) {
+      return buildSuffix(parent.buildContextDescriptorForTesting(builder));
+    }
+
+    @Override
+    StringBuilder buildSyntheticSuffix(StringBuilder builder) {
+      return buildSuffix(parent.buildSyntheticSuffix(builder));
+    }
+
+    public DexProgramClass getClassContext() {
+      return parent.getClassContext();
+    }
+
+    public String getSyntheticSuffix() {
+      return buildSyntheticSuffix(new StringBuilder()).toString();
+    }
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/graph/AppInfo.java b/src/main/java/com/android/tools/r8/graph/AppInfo.java
index de3d976..f2064b3 100644
--- a/src/main/java/com/android/tools/r8/graph/AppInfo.java
+++ b/src/main/java/com/android/tools/r8/graph/AppInfo.java
@@ -12,7 +12,6 @@
 import com.android.tools.r8.synthesis.SyntheticItems;
 import com.android.tools.r8.utils.BooleanBox;
 import com.android.tools.r8.utils.InternalOptions;
-import java.util.Collection;
 import java.util.List;
 import java.util.function.Consumer;
 
@@ -137,7 +136,7 @@
     }
   }
 
-  public Collection<DexProgramClass> classes() {
+  public List<DexProgramClass> classes() {
     assert checkIfObsolete();
     return app.classes();
   }
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 b56d682..8b09674 100644
--- a/src/main/java/com/android/tools/r8/graph/AppView.java
+++ b/src/main/java/com/android/tools/r8/graph/AppView.java
@@ -4,6 +4,8 @@
 
 package com.android.tools.r8.graph;
 
+import com.android.tools.r8.contexts.CompilationContext;
+import com.android.tools.r8.contexts.CompilationContext.ProcessorContext;
 import com.android.tools.r8.errors.dontwarn.DontWarnConfiguration;
 import com.android.tools.r8.features.ClassToFeatureSplitMap;
 import com.android.tools.r8.graph.DexValue.DexValueString;
@@ -19,8 +21,6 @@
 import com.android.tools.r8.ir.analysis.proto.GeneratedMessageLiteShrinker;
 import com.android.tools.r8.ir.analysis.proto.ProtoShrinker;
 import com.android.tools.r8.ir.analysis.value.AbstractValueFactory;
-import com.android.tools.r8.ir.conversion.MethodProcessingId;
-import com.android.tools.r8.ir.desugar.InvokeSpecialBridgeSynthesizer;
 import com.android.tools.r8.ir.desugar.PrefixRewritingMapper;
 import com.android.tools.r8.ir.optimize.CallSiteOptimizationInfoPropagator;
 import com.android.tools.r8.ir.optimize.enums.EnumDataMap;
@@ -72,13 +72,11 @@
   private final AbstractValueFactory abstractValueFactory = new AbstractValueFactory();
   private final InstanceFieldInitializationInfoFactory instanceFieldInitializationInfoFactory =
       new InstanceFieldInitializationInfoFactory();
-  private final MethodProcessingId.Factory methodProcessingIdFactory;
   private final SimpleInliningConstraintFactory simpleInliningConstraintFactory =
       new SimpleInliningConstraintFactory();
 
   // Desugaring.
   public final PrefixRewritingMapper rewritePrefix;
-  private final InvokeSpecialBridgeSynthesizer invokeSpecialBridgeSynthesizer;
 
   // Modeling.
   private final LibraryMethodSideEffectModelCollection libraryMethodSideEffectModelCollection;
@@ -103,20 +101,22 @@
   // desugared. This information is populated in the IR converter.
   private Set<DexType> alreadyLibraryDesugared = null;
 
+  private final CompilationContext context;
+
+  private final Thread mainThread = Thread.currentThread();
+
   private AppView(
       T appInfo,
       WholeProgramOptimizations wholeProgramOptimizations,
       PrefixRewritingMapper mapper) {
     assert appInfo != null;
+    this.context = CompilationContext.createInitialContext(appInfo.options());
     this.appInfo = appInfo;
     this.dontWarnConfiguration = DontWarnConfiguration.create(options().getProguardConfiguration());
     this.wholeProgramOptimizations = wholeProgramOptimizations;
     this.graphLens = GraphLens.getIdentityLens();
     this.initClassLens = InitClassLens.getDefault();
-    this.methodProcessingIdFactory =
-        new MethodProcessingId.Factory(options().testing.methodProcessingIdConsumer);
     this.rewritePrefix = mapper;
-    this.invokeSpecialBridgeSynthesizer = new InvokeSpecialBridgeSynthesizer(this);
 
     if (enableWholeProgramOptimizations() && options().callSiteOptimizationOptions().isEnabled()) {
       this.callSiteOptimizationInfoPropagator =
@@ -135,6 +135,11 @@
     }
   }
 
+  public boolean verifyMainThread() {
+    assert mainThread == Thread.currentThread();
+    return true;
+  }
+
   @Override
   public boolean isModeled(DexType type) {
     return libraryMemberOptimizer.isModeled(type);
@@ -188,10 +193,6 @@
     return instanceFieldInitializationInfoFactory;
   }
 
-  public MethodProcessingId.Factory methodProcessingIdFactory() {
-    return methodProcessingIdFactory;
-  }
-
   public SimpleInliningConstraintFactory simpleInliningConstraintFactory() {
     return simpleInliningConstraintFactory;
   }
@@ -306,6 +307,17 @@
     return wholeProgramOptimizations == WholeProgramOptimizations.ON;
   }
 
+  /**
+   * Create a new processor context.
+   *
+   * <p>The order of processor contexts for a compilation must be deterministic so this is required
+   * to be called on the main thread only.
+   */
+  public ProcessorContext createProcessorContext() {
+    assert verifyMainThread();
+    return context.createProcessorContext();
+  }
+
   public SyntheticItems getSyntheticItems() {
     return appInfo.getSyntheticItems();
   }
@@ -314,10 +326,6 @@
     return callSiteOptimizationInfoPropagator;
   }
 
-  public InvokeSpecialBridgeSynthesizer getInvokeSpecialBridgeSynthesizer() {
-    return invokeSpecialBridgeSynthesizer;
-  }
-
   public LibraryMemberOptimizer libraryMethodOptimizer() {
     return libraryMemberOptimizer;
   }
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 7b1fe7c..1c30bbf 100644
--- a/src/main/java/com/android/tools/r8/graph/AssemblyWriter.java
+++ b/src/main/java/com/android/tools/r8/graph/AssemblyWriter.java
@@ -6,8 +6,8 @@
 import static com.android.tools.r8.utils.StringUtils.LINE_SEPARATOR;
 
 import com.android.tools.r8.ClassFileConsumer;
+import com.android.tools.r8.contexts.CompilationContext;
 import com.android.tools.r8.ir.conversion.IRConverter;
-import com.android.tools.r8.ir.conversion.MethodProcessingId;
 import com.android.tools.r8.ir.conversion.OneTimeMethodProcessor;
 import com.android.tools.r8.ir.optimize.info.OptimizationFeedbackIgnore;
 import com.android.tools.r8.kotlin.Kotlin;
@@ -24,8 +24,6 @@
 
 public class AssemblyWriter extends DexByteCodeWriter {
 
-  private final MethodProcessingId.Factory methodProcessingIdFactory =
-      new MethodProcessingId.Factory();
   private final boolean writeAllClassInfo;
   private final boolean writeFields;
   private final boolean writeAnnotations;
@@ -34,6 +32,7 @@
   private final AppInfo appInfo;
   private final Kotlin kotlin;
   private final Timing timing = new Timing("AssemblyWriter");
+  private final CompilationContext compilationContext;
 
   public AssemblyWriter(
       DexApplication application,
@@ -42,6 +41,7 @@
       boolean writeIR,
       boolean writeCode) {
     super(application, options);
+    this.compilationContext = CompilationContext.createInitialContext(options);
     this.writeAllClassInfo = allInfo;
     this.writeFields = allInfo;
     this.writeAnnotations = allInfo;
@@ -177,14 +177,14 @@
     CfgPrinter printer = new CfgPrinter();
     IRConverter converter = new IRConverter(appInfo, timing, printer);
     OneTimeMethodProcessor methodProcessor =
-        OneTimeMethodProcessor.create(method, methodProcessingIdFactory);
+        OneTimeMethodProcessor.create(method, compilationContext.createProcessorContext());
     methodProcessor.forEachWaveWithExtension(
-        (ignore, methodProcesingId) ->
-            converter.processMethod(
+        (ignore, methodProcessingContext) ->
+            converter.processDesugaredMethod(
                 method,
                 OptimizationFeedbackIgnore.getInstance(),
                 methodProcessor,
-                methodProcesingId));
+                methodProcessingContext));
     ps.println(printer.toString());
   }
 
diff --git a/src/main/java/com/android/tools/r8/graph/DexClass.java b/src/main/java/com/android/tools/r8/graph/DexClass.java
index df80a80..08d2880 100644
--- a/src/main/java/com/android/tools/r8/graph/DexClass.java
+++ b/src/main/java/com/android/tools/r8/graph/DexClass.java
@@ -63,7 +63,12 @@
   /** InnerClasses table. If this class is an inner class, it will have an entry here. */
   private List<InnerClassAttribute> innerClasses;
 
+  /**
+   * Nest attributes. If this class was compiled in JDK 11 and higher, and is in a nest, one of the
+   * two attributes will be set.
+   */
   private NestHostClassAttribute nestHost;
+
   private final List<NestMemberClassAttribute> nestMembers;
 
   /** Generic signature information if the attribute is present in the input */
@@ -587,6 +592,10 @@
     return accessFlags.isEnum();
   }
 
+  public boolean isRecord() {
+    return accessFlags.isRecord();
+  }
+
   public abstract void addDependencies(MixedSectionCollection collector);
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
index c7a6e70..afc04c7 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
@@ -11,6 +11,7 @@
 import static com.android.tools.r8.graph.DexEncodedMethod.CompilationState.PROCESSED_NOT_INLINING_CANDIDATE;
 import static com.android.tools.r8.graph.DexProgramClass.asProgramClassOrNull;
 import static com.android.tools.r8.kotlin.KotlinMetadataUtils.NO_KOTLIN_INFO;
+import static com.android.tools.r8.utils.ConsumerUtils.emptyConsumer;
 
 import com.android.tools.r8.cf.CfVersion;
 import com.android.tools.r8.cf.code.CfConstNull;
@@ -1287,14 +1288,10 @@
             .setCode(
                 ForwardMethodBuilder.builder(dexItemFactory)
                     .setStaticSource(newMethod)
-                    .apply(
-                        builder -> {
-                          if (isStatic()) {
-                            builder.setStaticTarget(getReference(), holder.isInterface());
-                          } else {
-                            builder.setDirectTarget(getReference(), holder.isInterface());
-                          }
-                        })
+                    .applyIf(
+                        isStatic(),
+                        builder -> builder.setStaticTarget(getReference(), holder.isInterface()),
+                        builder -> builder.setDirectTarget(getReference(), holder.isInterface()))
                     .build())
             .setAccessFlags(
                 MethodAccessFlags.builder()
@@ -1306,61 +1303,66 @@
             .build());
   }
 
-  public DexEncodedMethod toPrivateSyntheticMethod(DexMethod method) {
-    assert !accessFlags.isStatic();
-    assert !accessFlags.isPrivate();
-    assert getHolderType() == method.holder;
+  public ProgramMethod toPrivateSyntheticMethod(DexProgramClass holder, DexMethod method) {
+    assert !isStatic();
+    assert !isPrivate();
+    assert getHolderType() == method.getHolderType();
     checkIfObsolete();
-    Builder builder = syntheticBuilder(this);
-    builder.setMethod(method);
-    builder.accessFlags.setSynthetic();
-    builder.accessFlags.unsetProtected();
-    builder.accessFlags.unsetPublic();
-    builder.accessFlags.setPrivate();
-    return builder.build();
+    return new ProgramMethod(
+        holder,
+        syntheticBuilder(this)
+            .setMethod(method)
+            .modifyAccessFlags(
+                accessFlags -> {
+                  accessFlags.setSynthetic();
+                  accessFlags.unsetProtected();
+                  accessFlags.unsetPublic();
+                  accessFlags.setPrivate();
+                })
+            .build());
   }
 
-  public DexEncodedMethod toForwardingMethod(DexClass holder, DexDefinitionSupplier definitions) {
-    DexMethod newMethod = method.withHolder(holder.type, definitions.dexItemFactory());
+  public DexEncodedMethod toForwardingMethod(
+      DexClass newHolder, DexDefinitionSupplier definitions) {
+    DexMethod newMethod = method.withHolder(newHolder, definitions.dexItemFactory());
     checkIfObsolete();
+
     // Clear the final flag, as this method is now overwritten. Do this before creating the builder
     // for the forwarding method, as the forwarding method will copy the access flags from this,
     // and if different forwarding methods are created in different subclasses the first could be
     // final.
     accessFlags.demoteFromFinal();
-    Builder builder = syntheticBuilder(this);
-    builder.setMethod(newMethod);
-    if (accessFlags.isAbstract()) {
-      // If the forwarding target is abstract, we can just create an abstract method. While it
-      // will not actually forward, it will create the same exception when hit at runtime.
-      builder.accessFlags.setAbstract();
-    } else {
-      // Create code that forwards the call to the target.
-      DexClass target = definitions.definitionFor(method.holder);
-      ForwardMethodSourceCode.Builder forwardSourceCodeBuilder =
-          ForwardMethodSourceCode.builder(newMethod);
-      forwardSourceCodeBuilder
-          .setReceiver(accessFlags.isStatic() ? null : newMethod.getHolderType())
-          .setTargetReceiver(accessFlags.isStatic() ? null : method.holder)
-          .setTarget(method)
-          .setInvokeType(accessFlags.isStatic() ? Invoke.Type.STATIC : Invoke.Type.SUPER)
-          .setIsInterface(target.isInterface());
-      builder.setCode(
-          new SynthesizedCode(
-              forwardSourceCodeBuilder::build,
-              registry -> {
-                if (accessFlags.isStatic()) {
-                  registry.registerInvokeStatic(method);
-                } else {
-                  registry.registerInvokeSuper(method);
-                }
-              }));
-      builder.accessFlags.setBridge();
-    }
-    builder.accessFlags.setSynthetic();
-    // Note that we are not marking this instance obsolete, since it is not: the newly synthesized
-    // forwarding method has a separate code that literally forwards to the current method.
-    return builder.build();
+
+    return syntheticBuilder(this)
+        .setMethod(newMethod)
+        .modifyAccessFlags(MethodAccessFlags::setSynthetic)
+        // If the forwarding target is abstract, we can just create an abstract method. While it
+        // will not actually forward, it will create the same exception when hit at runtime.
+        // Otherwise, we need to create code that forwards the call to the target.
+        .applyIf(
+            !isAbstract(),
+            builder ->
+                builder
+                    .setCode(
+                        ForwardMethodBuilder.builder(definitions.dexItemFactory())
+                            .setStaticSource(newMethod)
+                            .applyIf(
+                                isStatic(),
+                                codeBuilder ->
+                                    codeBuilder
+                                        .setStaticSource(newMethod)
+                                        .setStaticTarget(
+                                            getReference(),
+                                            method.getHolderType().isInterface(definitions)),
+                                codeBuilder ->
+                                    codeBuilder
+                                        .setNonStaticSource(newMethod)
+                                        .setSuperTarget(
+                                            getReference(),
+                                            method.getHolderType().isInterface(definitions)))
+                            .build())
+                    .modifyAccessFlags(MethodAccessFlags::setBridge))
+        .build();
   }
 
   public static DexEncodedMethod createDesugaringForwardingMethod(
@@ -1571,6 +1573,20 @@
       }
     }
 
+    public Builder applyIf(boolean condition, Consumer<Builder> thenConsumer) {
+      return applyIf(condition, thenConsumer, emptyConsumer());
+    }
+
+    public Builder applyIf(
+        boolean condition, Consumer<Builder> thenConsumer, Consumer<Builder> elseConsumer) {
+      if (condition) {
+        thenConsumer.accept(this);
+      } else {
+        elseConsumer.accept(this);
+      }
+      return this;
+    }
+
     public Builder setSimpleInliningConstraint(
         DexProgramClass holder, SimpleInliningConstraint simpleInliningConstraint) {
       return addBuildConsumer(
diff --git a/src/main/java/com/android/tools/r8/graph/DexType.java b/src/main/java/com/android/tools/r8/graph/DexType.java
index f806886..aaeb156 100644
--- a/src/main/java/com/android/tools/r8/graph/DexType.java
+++ b/src/main/java/com/android/tools/r8/graph/DexType.java
@@ -319,6 +319,10 @@
     return false;
   }
 
+  public boolean isInterface(DexDefinitionSupplier definitionSupplier) {
+    return definitionSupplier.definitionFor(this).isInterface();
+  }
+
   public boolean isProgramType(DexDefinitionSupplier definitions) {
     DexClass clazz = definitions.definitionFor(this);
     return clazz != null && clazz.isProgramClass();
diff --git a/src/main/java/com/android/tools/r8/graph/JarClassFileReader.java b/src/main/java/com/android/tools/r8/graph/JarClassFileReader.java
index 1f67c4d..35b4574 100644
--- a/src/main/java/com/android/tools/r8/graph/JarClassFileReader.java
+++ b/src/main/java/com/android/tools/r8/graph/JarClassFileReader.java
@@ -47,6 +47,7 @@
 import com.android.tools.r8.utils.StringUtils;
 import com.google.common.base.Equivalence.Wrapper;
 import com.google.common.collect.Iterables;
+import com.google.common.collect.Sets;
 import java.nio.ByteBuffer;
 import java.util.ArrayList;
 import java.util.HashSet;
@@ -205,6 +206,7 @@
     private DexString sourceFile;
     private NestHostClassAttribute nestHost = null;
     private final List<NestMemberClassAttribute> nestMembers = new ArrayList<>();
+    private final Set<DexField> recordComponents = Sets.newIdentityHashSet();
     private EnclosingMethodAttribute enclosingMember = null;
     private final List<InnerClassAttribute> innerClasses = new ArrayList<>();
     private ClassSignature classSignature = ClassSignature.noSignature();
@@ -301,7 +303,16 @@
     @Override
     public RecordComponentVisitor visitRecordComponent(
         String name, String descriptor, String signature) {
-      // TODO(b/169645628): Support Records.
+      assert name != null;
+      assert descriptor != null;
+      // Javac generated record components are only the instance fields, so we just reuse the field
+      // to avoid duplicating the field and field signature rewriting logic.
+      DexField field =
+          application
+              .getFactory()
+              .createField(
+                  type, application.getTypeFromDescriptor(descriptor), application.getString(name));
+      recordComponents.add(field);
       return super.visitRecordComponent(name, descriptor, signature);
     }
 
@@ -327,12 +338,6 @@
       }
       this.deprecated = AsmUtils.isDeprecated(access);
       accessFlags = ClassAccessFlags.fromCfAccessFlags(cleanAccessFlags(access));
-      if (accessFlags.isRecord()) {
-        // TODO(b/169645628): Support records in all compilation.
-        if (!application.options.canUseRecords()) {
-          throw new CompilationError("Records are not supported", origin);
-        }
-      }
       type = application.getTypeFromName(name);
       // Check if constraints from
       // https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-4.html#jvms-4.1 are met.
@@ -426,6 +431,7 @@
             type, defaultAnnotations, application.getFactory()));
       }
       checkReachabilitySensitivity();
+      checkRecord();
       T clazz =
           classKind.create(
               type,
@@ -489,6 +495,28 @@
       classConsumer.accept(clazz);
     }
 
+    private void checkRecord() {
+      if (!accessFlags.isRecord()) {
+        return;
+      }
+      // TODO(b/169645628): Support records in all compilation.
+      if (!application.options.canUseRecords()) {
+        throw new CompilationError("Records are not supported", origin);
+      }
+      // TODO(b/169645628): Change this logic if we start stripping the record components.
+      // Another approach would be to mark a bit in fields that are record components instead.
+      String message = "Records are expected to have one record component per instance field.";
+      if (recordComponents.size() != instanceFields.size()) {
+        throw new CompilationError(message, origin);
+      }
+      for (DexEncodedField instanceField : instanceFields) {
+        if (!recordComponents.contains(instanceField.field)) {
+          throw new CompilationError(
+              message + " Unmatched field " + instanceField.field + ".", origin);
+        }
+      }
+    }
+
     private ChecksumSupplier getChecksumSupplier(ClassKind<T> classKind) {
       if (application.options.encodeChecksums && classKind == ClassKind.PROGRAM) {
         CRC32 crc = new CRC32();
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
index e9b454a..f15ad69 100644
--- 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
@@ -157,12 +157,12 @@
         SortedProgramMethodSet.create(this::forEachFindLiteExtensionByNumberMethod);
     OneTimeMethodProcessor methodProcessor = OneTimeMethodProcessor.create(wave, appView);
     methodProcessor.forEachWaveWithExtension(
-        (method, methodProcessingId) ->
-            converter.processMethod(
+        (method, methodProcessingContext) ->
+            converter.processDesugaredMethod(
                 method,
                 OptimizationFeedbackIgnore.getInstance(),
                 methodProcessor,
-                methodProcessingId),
+                methodProcessingContext),
         executorService);
     timing.end();
   }
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/proto/GeneratedMessageLiteShrinker.java b/src/main/java/com/android/tools/r8/ir/analysis/proto/GeneratedMessageLiteShrinker.java
index 45f5083..ab3673e 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/proto/GeneratedMessageLiteShrinker.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/proto/GeneratedMessageLiteShrinker.java
@@ -81,12 +81,12 @@
     SortedProgramMethodSet wave = SortedProgramMethodSet.create(this::forEachDynamicMethod);
     OneTimeMethodProcessor methodProcessor = OneTimeMethodProcessor.create(wave, appView);
     methodProcessor.forEachWaveWithExtension(
-        (method, methodProcessingId) ->
-            converter.processMethod(
+        (method, methodProcessingContext) ->
+            converter.processDesugaredMethod(
                 method,
                 OptimizationFeedbackIgnore.getInstance(),
                 methodProcessor,
-                methodProcessingId),
+                methodProcessingContext),
         executorService);
     timing.end();
   }
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/proto/ProtoEnumSwitchMapRemover.java b/src/main/java/com/android/tools/r8/ir/analysis/proto/ProtoEnumSwitchMapRemover.java
index 00e824f..5c3692c 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/proto/ProtoEnumSwitchMapRemover.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/proto/ProtoEnumSwitchMapRemover.java
@@ -4,7 +4,6 @@
 
 package com.android.tools.r8.ir.analysis.proto;
 
-import com.android.tools.r8.errors.CompilationError;
 import com.android.tools.r8.graph.DexEncodedField;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexType;
@@ -12,6 +11,7 @@
 import com.android.tools.r8.ir.analysis.fieldvalueanalysis.StaticFieldValues.EnumStaticFieldValues;
 import com.android.tools.r8.ir.analysis.value.ObjectState;
 import com.android.tools.r8.ir.analysis.value.SingleNumberValue;
+import java.util.IdentityHashMap;
 import java.util.Map;
 import java.util.concurrent.ConcurrentHashMap;
 
@@ -20,7 +20,8 @@
 
   private final ProtoReferences references;
 
-  private final Map<DexType, EnumStaticFieldValues> staticFieldValuesMap =
+  private final Map<DexType, EnumStaticFieldValues> staticFieldValuesMap = new IdentityHashMap<>();
+  private final Map<DexType, EnumStaticFieldValues> staticFieldValuesMapDelayed =
       new ConcurrentHashMap<>();
 
   public ProtoEnumSwitchMapRemover(ProtoReferences references) {
@@ -34,10 +35,15 @@
     assert clazz.isEnum();
     EnumStaticFieldValues enumStaticFieldValues = staticFieldValues.asEnumStaticFieldValues();
     if (isProtoEnum(clazz)) {
-      staticFieldValuesMap.put(clazz.type, enumStaticFieldValues);
+      staticFieldValuesMapDelayed.put(clazz.type, enumStaticFieldValues);
     }
   }
 
+  public void updateVisibleStaticFieldValues() {
+    staticFieldValuesMap.putAll(staticFieldValuesMapDelayed);
+    staticFieldValuesMapDelayed.clear();
+  }
+
   private boolean isProtoEnum(DexProgramClass clazz) {
     assert clazz.isEnum();
     if (clazz.type == references.methodToInvokeType) {
@@ -53,9 +59,8 @@
     }
     EnumStaticFieldValues enumStaticFieldValues = staticFieldValuesMap.get(enumClass.type);
     if (enumStaticFieldValues == null) {
-      if (enumClass.type == references.methodToInvokeType) {
-        throw new CompilationError("Proto optimizations: missing information for MethodToInvoke.");
-      }
+      // If the switch map is found in a wave previous to the wave containing the enum clinit,
+      // then bail out. This can happen but is extremely uncommon.
       return null;
     }
     ObjectState state =
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/ClassConverter.java b/src/main/java/com/android/tools/r8/ir/conversion/ClassConverter.java
index 7d32802..1a25cb1 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/ClassConverter.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/ClassConverter.java
@@ -5,9 +5,12 @@
 package com.android.tools.r8.ir.conversion;
 
 import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.DexApplication;
+import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.ir.desugar.CfInstructionDesugaringEventConsumer;
+import com.android.tools.r8.ir.desugar.CfInstructionDesugaringEventConsumer.D8CfInstructionDesugaringEventConsumer;
 import com.android.tools.r8.utils.ThreadUtils;
 import com.google.common.collect.Sets;
 import java.util.ArrayList;
@@ -18,10 +21,12 @@
 
 public abstract class ClassConverter {
 
+  protected final AppView<?> appView;
   private final IRConverter converter;
   private final D8MethodProcessor methodProcessor;
 
-  ClassConverter(IRConverter converter, D8MethodProcessor methodProcessor) {
+  ClassConverter(AppView<?> appView, IRConverter converter, D8MethodProcessor methodProcessor) {
+    this.appView = appView;
     this.converter = converter;
     this.methodProcessor = methodProcessor;
   }
@@ -30,18 +35,16 @@
       AppView<?> appView, IRConverter converter, D8MethodProcessor methodProcessor) {
     return appView.options().desugarSpecificOptions().allowAllDesugaredInput
         ? new LibraryDesugaredClassConverter(appView, converter, methodProcessor)
-        : new DefaultClassConverter(converter, methodProcessor);
+        : new DefaultClassConverter(appView, converter, methodProcessor);
   }
 
-  public void convertClasses(DexApplication application, ExecutorService executorService)
-      throws ExecutionException {
-    internalConvertClasses(application, executorService);
+  public void convertClasses(ExecutorService executorService) throws ExecutionException {
+    internalConvertClasses(executorService);
     notifyAllClassesConverted();
   }
 
-  private void internalConvertClasses(DexApplication application, ExecutorService executorService)
-      throws ExecutionException {
-    List<DexProgramClass> classes = application.classes();
+  private void internalConvertClasses(ExecutorService executorService) throws ExecutionException {
+    List<DexProgramClass> classes = appView.appInfo().classes();
     while (!classes.isEmpty()) {
       Set<DexType> seenNestHosts = Sets.newIdentityHashSet();
       List<DexProgramClass> deferred = new ArrayList<>(classes.size() / 2);
@@ -51,31 +54,70 @@
           deferred.add(clazz);
         } else {
           wave.add(clazz);
+
+          // TODO(b/179755192): Avoid marking classes as scheduled by building up waves of methods.
+          methodProcessor.addScheduled(clazz);
         }
       }
-      ThreadUtils.processItems(wave, this::convertClass, executorService);
+
+      // Process the wave and wait for all IR processing to complete.
+      D8CfInstructionDesugaringEventConsumer desugaringEventConsumer =
+          CfInstructionDesugaringEventConsumer.createForD8();
+      methodProcessor.newWave();
+      ThreadUtils.processItems(
+          wave, clazz -> convertClass(clazz, desugaringEventConsumer), executorService);
       methodProcessor.awaitMethodProcessing();
+
+      // Finalize the desugaring of the processed classes. This may require reprocessing of some
+      // methods, because nest-based access desugaring changes the body of virtual methods.
+      List<ProgramMethod> needsReprocessing = desugaringEventConsumer.finalizeDesugaring(appView);
+      if (!needsReprocessing.isEmpty()) {
+        // Create a new processor context to ensure unique method processing contexts.
+        methodProcessor.newWave();
+
+        // Process the methods that require reprocessing. These are all simple bridge methods and
+        // should therefore not lead to additional desugaring.
+        ThreadUtils.processItems(
+            needsReprocessing,
+            method -> {
+              DexEncodedMethod definition = method.getDefinition();
+              assert definition.isProcessed();
+              definition.markNotProcessed();
+              methodProcessor.processMethod(method, desugaringEventConsumer);
+            },
+            executorService);
+
+        // Verify that there is no more desugaring to do, and that all IR processing has been
+        // completed.
+        assert desugaringEventConsumer.verifyNothingToFinalize();
+        assert methodProcessor.verifyNoPendingMethodProcessing();
+      }
+
       classes = deferred;
     }
   }
 
-  abstract void convertClass(DexProgramClass clazz);
+  abstract void convertClass(
+      DexProgramClass clazz, D8CfInstructionDesugaringEventConsumer desugaringEventConsumer);
 
-  void convertMethods(DexProgramClass clazz) {
-    converter.convertMethods(clazz, methodProcessor);
+  void convertMethods(
+      DexProgramClass clazz, D8CfInstructionDesugaringEventConsumer desugaringEventConsumer) {
+    converter.convertMethods(clazz, desugaringEventConsumer, methodProcessor);
   }
 
   abstract void notifyAllClassesConverted();
 
   static class DefaultClassConverter extends ClassConverter {
 
-    DefaultClassConverter(IRConverter converter, D8MethodProcessor methodProcessor) {
-      super(converter, methodProcessor);
+    DefaultClassConverter(
+        AppView<?> appView, IRConverter converter, D8MethodProcessor methodProcessor) {
+      super(appView, converter, methodProcessor);
     }
 
     @Override
-    void convertClass(DexProgramClass clazz) {
-      convertMethods(clazz);
+    void convertClass(
+        DexProgramClass clazz, D8CfInstructionDesugaringEventConsumer desugaringEventConsumer) {
+      convertMethods(clazz, desugaringEventConsumer);
     }
 
     @Override
@@ -86,24 +128,23 @@
 
   static class LibraryDesugaredClassConverter extends ClassConverter {
 
-    private final AppView<?> appView;
     private final Set<DexType> alreadyLibraryDesugared = Sets.newConcurrentHashSet();
 
     LibraryDesugaredClassConverter(
         AppView<?> appView, IRConverter converter, D8MethodProcessor methodProcessor) {
-      super(converter, methodProcessor);
-      this.appView = appView;
+      super(appView, converter, methodProcessor);
     }
 
     @Override
-    void convertClass(DexProgramClass clazz) {
+    void convertClass(
+        DexProgramClass clazz, D8CfInstructionDesugaringEventConsumer desugaringEventConsumer) {
       // Classes which has already been through library desugaring will not go through IR
       // processing again.
       LibraryDesugaredChecker libraryDesugaredChecker = new LibraryDesugaredChecker(appView);
       if (libraryDesugaredChecker.isClassLibraryDesugared(clazz)) {
         alreadyLibraryDesugared.add(clazz.getType());
       } else {
-        convertMethods(clazz);
+        convertMethods(clazz, desugaringEventConsumer);
       }
     }
 
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/CodeOptimization.java b/src/main/java/com/android/tools/r8/ir/conversion/CodeOptimization.java
index a10c562..86b7f73 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/CodeOptimization.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/CodeOptimization.java
@@ -3,6 +3,7 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.ir.conversion;
 
+import com.android.tools.r8.contexts.CompilationContext.MethodProcessingContext;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.optimize.info.OptimizationFeedback;
@@ -25,10 +26,10 @@
       IRCode code,
       OptimizationFeedback feedback,
       MethodProcessor methodProcessor,
-      MethodProcessingId methodProcessingId);
+      MethodProcessingContext methodProcessingContext);
 
   static CodeOptimization from(Consumer<IRCode> consumer) {
-    return (code, feedback, methodProcessor, methodProcessingId) -> {
+    return (code, feedback, methodProcessor, methodProcessingContext) -> {
       consumer.accept(code);
     };
   }
@@ -38,9 +39,9 @@
   }
 
   static CodeOptimization sequence(Collection<CodeOptimization> codeOptimizations) {
-    return (code, feedback, methodProcessor, methodProcessingId) -> {
+    return (code, feedback, methodProcessor, methodProcessingContext) -> {
       for (CodeOptimization codeOptimization : codeOptimizations) {
-        codeOptimization.optimize(code, feedback, methodProcessor, methodProcessingId);
+        codeOptimization.optimize(code, feedback, methodProcessor, methodProcessingContext);
       }
     };
   }
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/D8MethodProcessor.java b/src/main/java/com/android/tools/r8/ir/conversion/D8MethodProcessor.java
index d8fb60c..2d3247e 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/D8MethodProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/D8MethodProcessor.java
@@ -3,13 +3,19 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.ir.conversion;
 
+import com.android.tools.r8.contexts.CompilationContext.ProcessorContext;
 import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.ir.desugar.CfInstructionDesugaringEventConsumer.D8CfInstructionDesugaringEventConsumer;
 import com.android.tools.r8.ir.optimize.info.OptimizationFeedbackIgnore;
 import com.android.tools.r8.utils.ThreadUtils;
+import com.google.common.collect.Sets;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
+import java.util.Set;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Future;
@@ -19,10 +25,23 @@
   private final IRConverter converter;
   private final ExecutorService executorService;
   private final List<Future<?>> futures = Collections.synchronizedList(new ArrayList<>());
+  private final Set<DexType> scheduled = Sets.newIdentityHashSet();
+
+  private ProcessorContext processorContext;
 
   public D8MethodProcessor(IRConverter converter, ExecutorService executorService) {
     this.converter = converter;
     this.executorService = executorService;
+    this.processorContext = converter.appView.createProcessorContext();
+  }
+
+  public void addScheduled(DexProgramClass clazz) {
+    boolean added = scheduled.add(clazz.getType());
+    assert added;
+  }
+
+  public void newWave() {
+    this.processorContext = converter.appView.createProcessorContext();
   }
 
   @Override
@@ -37,11 +56,22 @@
   }
 
   @Override
-  public void scheduleMethodForProcessingAfterCurrentWave(ProgramMethod method) {
+  public void scheduleDesugaredMethodForProcessing(ProgramMethod method) {
+    // TODO(b/179755192): By building up waves of methods in the class converter, we can avoid the
+    //  following check and always process the method asynchronously.
+    if (!scheduled.contains(method.getHolderType())
+        && !converter.appView.getSyntheticItems().isNonLegacySynthetic(method.getHolder())) {
+      // The non-synthetic holder is not scheduled. It will be processed once holder is scheduled.
+      return;
+    }
     futures.add(
         ThreadUtils.processAsynchronously(
             () ->
-                converter.rewriteCode(method, OptimizationFeedbackIgnore.getInstance(), this, null),
+                converter.rewriteDesugaredCode(
+                    method,
+                    OptimizationFeedbackIgnore.getInstance(),
+                    this,
+                    processorContext.createMethodProcessingContext(method)),
             executorService));
   }
 
@@ -54,4 +84,18 @@
     ThreadUtils.awaitFutures(futures);
     futures.clear();
   }
+
+  public void processMethod(
+      ProgramMethod method, D8CfInstructionDesugaringEventConsumer desugaringEventConsumer) {
+    converter.convertMethod(
+        method,
+        desugaringEventConsumer,
+        this,
+        processorContext.createMethodProcessingContext(method));
+  }
+
+  public boolean verifyNoPendingMethodProcessing() {
+    assert futures.isEmpty();
+    return true;
+  }
 }
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 b1e68ace..7a45467 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
@@ -6,6 +6,7 @@
 import static com.android.tools.r8.ir.desugar.InterfaceMethodRewriter.Flavor.ExcludeDexResources;
 import static com.android.tools.r8.ir.desugar.InterfaceMethodRewriter.Flavor.IncludeAllResources;
 
+import com.android.tools.r8.contexts.CompilationContext.MethodProcessingContext;
 import com.android.tools.r8.errors.CompilationError;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.AppInfo;
@@ -42,6 +43,8 @@
 import com.android.tools.r8.ir.code.NumericType;
 import com.android.tools.r8.ir.code.Value;
 import com.android.tools.r8.ir.desugar.BackportedMethodRewriter;
+import com.android.tools.r8.ir.desugar.CfInstructionDesugaringEventConsumer;
+import com.android.tools.r8.ir.desugar.CfInstructionDesugaringEventConsumer.D8CfInstructionDesugaringEventConsumer;
 import com.android.tools.r8.ir.desugar.CovariantReturnTypeAnnotationTransformer;
 import com.android.tools.r8.ir.desugar.DesugaredLibraryAPIConverter;
 import com.android.tools.r8.ir.desugar.DesugaredLibraryAPIConverter.Mode;
@@ -51,6 +54,7 @@
 import com.android.tools.r8.ir.desugar.LambdaRewriter;
 import com.android.tools.r8.ir.desugar.StringConcatRewriter;
 import com.android.tools.r8.ir.desugar.TwrCloseResourceRewriter;
+import com.android.tools.r8.ir.desugar.invokespecial.InvokeSpecialToSelfDesugaring;
 import com.android.tools.r8.ir.desugar.nest.D8NestBasedAccessDesugaring;
 import com.android.tools.r8.ir.desugar.nest.NestBridgeConsumer;
 import com.android.tools.r8.ir.optimize.AssertionsRewriter;
@@ -97,7 +101,9 @@
 import com.android.tools.r8.utils.ExceptionUtils;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.InternalOptions.DesugarState;
+import com.android.tools.r8.utils.ListUtils;
 import com.android.tools.r8.utils.StringDiagnostic;
+import com.android.tools.r8.utils.SupplierUtils;
 import com.android.tools.r8.utils.ThreadUtils;
 import com.android.tools.r8.utils.Timing;
 import com.android.tools.r8.utils.collections.ProgramMethodSet;
@@ -131,6 +137,7 @@
   private final StringBuilderOptimizer stringBuilderOptimizer;
   private final IdempotentFunctionCallCanonicalizer idempotentFunctionCallCanonicalizer;
   private final LambdaRewriter lambdaRewriter;
+  private final InvokeSpecialToSelfDesugaring invokeSpecialToSelfDesugaring;
   private final D8NestBasedAccessDesugaring d8NestBasedAccessDesugaring;
   private final InterfaceMethodRewriter interfaceMethodRewriter;
   private final TwrCloseResourceRewriter twrCloseResourceRewriter;
@@ -233,6 +240,7 @@
           TwrCloseResourceRewriter.enableTwrCloseResourceDesugaring(appView.options())
               ? new TwrCloseResourceRewriter(appView)
               : null;
+      this.invokeSpecialToSelfDesugaring = new InvokeSpecialToSelfDesugaring(appView);
       this.d8NestBasedAccessDesugaring =
           options.shouldDesugarNests() ? new D8NestBasedAccessDesugaring(appView) : null;
       this.covariantReturnTypeAnnotationTransformer = null;
@@ -313,6 +321,7 @@
       this.devirtualizer =
           options.enableDevirtualization ? new Devirtualizer(appViewWithLiveness) : null;
       this.typeChecker = new TypeChecker(appViewWithLiveness, VerifyTypesHelper.create(appView));
+      this.invokeSpecialToSelfDesugaring = null;
       this.d8NestBasedAccessDesugaring = null;
       this.serviceLoaderRewriter =
           options.enableServiceLoaderRewriting
@@ -339,6 +348,7 @@
       this.identifierNameStringMarker = null;
       this.devirtualizer = null;
       this.typeChecker = null;
+      this.invokeSpecialToSelfDesugaring = new InvokeSpecialToSelfDesugaring(appView);
       this.d8NestBasedAccessDesugaring =
           options.shouldDesugarNests() ? new D8NestBasedAccessDesugaring(appView) : null;
       this.desugaredLibraryAPIConverter =
@@ -437,12 +447,6 @@
     }
   }
 
-  private void synthesizeInvokeSpecialBridges(ExecutorService executorService)
-      throws ExecutionException {
-    assert !appView.enableWholeProgramOptimizations();
-    appView.getInvokeSpecialBridgeSynthesizer().insertBridgesForD8(this, executorService);
-  }
-
   private void synthesizeEnumUnboxingUtilityMethods(ExecutorService executorService)
       throws ExecutionException {
     if (enumUnboxer != null) {
@@ -464,7 +468,7 @@
     DexApplication application = appView.appInfo().app();
     timing.begin("IR conversion");
 
-    convertClasses(application, executor);
+    convertClasses(executor);
 
     reportNestDesugarDependencies();
 
@@ -485,7 +489,6 @@
     desugarInterfaceMethods(builder, ExcludeDexResources, executor);
     processSynthesizedJava8UtilityClasses(executor);
     synthesizeRetargetClass(builder, executor);
-    synthesizeInvokeSpecialBridges(executor);
     processCovariantReturnTypeAnnotations(builder);
     generateDesugaredLibraryAPIWrappers(builder, executor);
 
@@ -498,33 +501,45 @@
             appView.appInfo().getMainDexInfo()));
   }
 
-  private void convertClasses(DexApplication application, ExecutorService executorService)
-      throws ExecutionException {
+  private void convertClasses(ExecutorService executorService) throws ExecutionException {
     D8MethodProcessor methodProcessor = new D8MethodProcessor(this, executorService);
     ClassConverter classConverter = ClassConverter.create(appView, this, methodProcessor);
-    classConverter.convertClasses(application, executorService);
+    classConverter.convertClasses(executorService);
 
     synthesizeBridgesForNestBasedAccessesOnClasspath(methodProcessor, executorService);
     methodProcessor.awaitMethodProcessing();
   }
 
-  void convertMethods(DexProgramClass clazz, MethodProcessor methodProcessor) {
+  void convertMethods(
+      DexProgramClass clazz,
+      D8CfInstructionDesugaringEventConsumer desugaringEventConsumer,
+      D8MethodProcessor methodProcessor) {
     boolean isReachabilitySensitive = clazz.hasReachabilitySensitiveAnnotation(options.itemFactory);
     // When converting all methods on a class always convert <clinit> first.
-    DexEncodedMethod classInitializer = clazz.getClassInitializer();
+    ProgramMethod classInitializer = clazz.getProgramClassInitializer();
+
+    // TODO(b/179755192): We currently need to copy the class' methods, to avoid a
+    //  ConcurrentModificationException from the insertion of methods due to invoke-special
+    //  desugaring. By building up waves of methods in the class converter, we would not need to
+    //  iterate the methods of a class during while its methods are being processed, which avoids
+    //  the need to copy the method list.
+    List<ProgramMethod> methods = ListUtils.newArrayList(clazz::forEachProgramMethod);
     if (classInitializer != null) {
       classInitializer
+          .getDefinition()
           .getMutableOptimizationInfo()
           .setReachabilitySensitive(isReachabilitySensitive);
-      convertMethod(new ProgramMethod(clazz, classInitializer), methodProcessor);
+      methodProcessor.processMethod(classInitializer, desugaringEventConsumer);
     }
-    clazz.forEachProgramMethodMatching(
-        definition -> !definition.isClassInitializer(),
-        method -> {
-          DexEncodedMethod definition = method.getDefinition();
-          definition.getMutableOptimizationInfo().setReachabilitySensitive(isReachabilitySensitive);
-          convertMethod(method, methodProcessor);
-        });
+
+    for (ProgramMethod method : methods) {
+      if (!method.getDefinition().isClassInitializer()) {
+        DexEncodedMethod definition = method.getDefinition();
+        definition.getMutableOptimizationInfo().setReachabilitySensitive(isReachabilitySensitive);
+        methodProcessor.processMethod(method, desugaringEventConsumer);
+      }
+    }
+
     // The class file version is downgraded after compilation. Some of the desugaring might need
     // the initial class file version to determine how far a method can be downgraded.
     if (clazz.hasClassFileVersion()) {
@@ -533,7 +548,11 @@
     }
   }
 
-  private void convertMethod(ProgramMethod method, MethodProcessor methodProcessor) {
+  void convertMethod(
+      ProgramMethod method,
+      D8CfInstructionDesugaringEventConsumer desugaringEventConsumer,
+      MethodProcessor methodProcessor,
+      MethodProcessingContext methodProcessingContext) {
     DexEncodedMethod definition = method.getDefinition();
     if (definition.hasClassFileVersion()) {
       definition.downgradeClassFileVersion(
@@ -552,7 +571,12 @@
     if (options.isGeneratingClassFiles()
         || !(options.passthroughDexCode && definition.getCode().isDexCode())) {
       // We do not process in call graph order, so anything could be a leaf.
-      rewriteCode(method, simpleOptimizationFeedback, methodProcessor, null);
+      rewriteCode(
+          method,
+          desugaringEventConsumer,
+          simpleOptimizationFeedback,
+          methodProcessor,
+          methodProcessingContext);
     } else {
       assert definition.getCode().isDexCode();
     }
@@ -687,8 +711,9 @@
         outliner.createOutlineMethodIdentifierGenerator();
       }
       primaryMethodProcessor.forEachMethod(
-          (method, methodProcessingId) ->
-              processMethod(method, feedback, primaryMethodProcessor, methodProcessingId),
+          (method, methodProcessingContext) ->
+              processDesugaredMethod(
+                  method, feedback, primaryMethodProcessor, methodProcessingContext),
           this::waveStart,
           this::waveDone,
           timing,
@@ -872,6 +897,9 @@
     if (options.enableFieldAssignmentTracker) {
       fieldAccessAnalysis.fieldAssignmentTracker().waveDone(wave, delayedOptimizationFeedback);
     }
+    if (appView.options().protoShrinking().enableRemoveProtoEnumSwitchMap()) {
+      appView.protoShrinker().protoEnumSwitchMapRemover.updateVisibleStaticFieldValues();
+    }
     assert delayedOptimizationFeedback.noUpdatesLeft();
     onWaveDoneActions.forEach(com.android.tools.r8.utils.Action::execute);
     onWaveDoneActions = null;
@@ -1003,9 +1031,9 @@
       OneTimeMethodProcessor methodProcessor =
           OneTimeMethodProcessor.create(synthesizedMethod, appView);
       methodProcessor.forEachWaveWithExtension(
-          (method, methodProcessingId) ->
-              processMethod(
-                  method, delayedOptimizationFeedback, methodProcessor, methodProcessingId));
+          (method, methodProcessingContext) ->
+              processDesugaredMethod(
+                  method, delayedOptimizationFeedback, methodProcessor, methodProcessingContext));
     }
   }
 
@@ -1014,9 +1042,9 @@
     if (!wave.isEmpty()) {
       OneTimeMethodProcessor methodProcessor = OneTimeMethodProcessor.create(wave, appView);
       methodProcessor.forEachWaveWithExtension(
-          (method, methodProcessingId) ->
-              processMethod(
-                  method, delayedOptimizationFeedback, methodProcessor, methodProcessingId),
+          (method, methodProcessingContext) ->
+              processDesugaredMethod(
+                  method, delayedOptimizationFeedback, methodProcessor, methodProcessingContext),
           executorService);
     }
   }
@@ -1036,16 +1064,16 @@
   }
 
   // TODO(b/140766440): Make this receive a list of CodeOptimizations to conduct.
-  public Timing processMethod(
+  public Timing processDesugaredMethod(
       ProgramMethod method,
       OptimizationFeedback feedback,
       MethodProcessor methodProcessor,
-      MethodProcessingId methodProcessingId) {
+      MethodProcessingContext methodProcessingContext) {
     DexEncodedMethod definition = method.getDefinition();
     Code code = definition.getCode();
     boolean matchesMethodFilter = options.methodMatchesFilter(definition);
     if (code != null && matchesMethodFilter) {
-      return rewriteCode(method, feedback, methodProcessor, methodProcessingId);
+      return rewriteDesugaredCode(method, feedback, methodProcessor, methodProcessingContext);
     } else {
       // Mark abstract methods as processed as well.
       definition.markProcessed(ConstraintWithTarget.NEVER);
@@ -1063,20 +1091,41 @@
 
   Timing rewriteCode(
       ProgramMethod method,
+      CfInstructionDesugaringEventConsumer desugaringEventConsumer,
       OptimizationFeedback feedback,
       MethodProcessor methodProcessor,
-      MethodProcessingId methodProcessingId) {
+      MethodProcessingContext methodProcessingContext) {
     return ExceptionUtils.withOriginAndPositionAttachmentHandler(
         method.getOrigin(),
         new MethodPosition(method.getReference().asMethodReference()),
-        () -> rewriteCodeInternal(method, feedback, methodProcessor, methodProcessingId));
+        () ->
+            rewriteCodeInternal(
+                method,
+                desugaringEventConsumer,
+                feedback,
+                methodProcessor,
+                methodProcessingContext));
+  }
+
+  Timing rewriteDesugaredCode(
+      ProgramMethod method,
+      OptimizationFeedback feedback,
+      MethodProcessor methodProcessor,
+      MethodProcessingContext methodProcessingContext) {
+    return rewriteCode(
+        method,
+        CfInstructionDesugaringEventConsumer.createForDesugaredCode(),
+        feedback,
+        methodProcessor,
+        methodProcessingContext);
   }
 
   private Timing rewriteCodeInternal(
       ProgramMethod method,
+      CfInstructionDesugaringEventConsumer desugaringEventConsumer,
       OptimizationFeedback feedback,
       MethodProcessor methodProcessor,
-      MethodProcessingId methodProcessingId) {
+      MethodProcessingContext methodProcessingContext) {
     if (options.verbose) {
       options.reporter.info(
           new StringDiagnostic("Processing: " + method.toSourceString()));
@@ -1088,7 +1137,8 @@
           method.toSourceString(),
           logCode(options, method.getDefinition()));
     }
-    boolean didDesugar = desugar(method, methodProcessor);
+    boolean didDesugar =
+        desugar(method, desugaringEventConsumer, methodProcessor, methodProcessingContext);
     if (Log.ENABLED && didDesugar) {
       Log.debug(
           getClass(),
@@ -1108,29 +1158,37 @@
       feedback.markProcessed(method.getDefinition(), ConstraintWithTarget.NEVER);
       return Timing.empty();
     }
-    return optimize(code, feedback, methodProcessor, methodProcessingId);
+    return optimize(code, feedback, methodProcessor, methodProcessingContext);
   }
 
-  private boolean desugar(ProgramMethod method, MethodProcessor methodProcessor) {
-    if (options.desugarState != DesugarState.ON) {
+  private boolean desugar(
+      ProgramMethod method,
+      CfInstructionDesugaringEventConsumer desugaringEventConsumer,
+      MethodProcessor methodProcessor,
+      MethodProcessingContext methodProcessingContext) {
+    if (options.desugarState.isOff() || !method.getDefinition().getCode().isCfCode()) {
       return false;
     }
-    if (!method.getDefinition().getCode().isCfCode()) {
-      return false;
-    }
+
     boolean didDesugar = false;
     Supplier<AppInfoWithClassHierarchy> lazyAppInfo =
-        Suppliers.memoize(appView::appInfoForDesugaring);
+        SupplierUtils.nonThreadSafeMemoize(appView::appInfoForDesugaring);
     if (lambdaRewriter != null) {
-      didDesugar |= lambdaRewriter.desugarLambdas(method, lazyAppInfo.get()) > 0;
+      didDesugar |=
+          lambdaRewriter.desugarLambdas(method, lazyAppInfo.get(), methodProcessingContext) > 0;
     }
     if (backportedMethodRewriter != null) {
-      didDesugar |= backportedMethodRewriter.desugar(method, lazyAppInfo.get());
+      didDesugar |=
+          backportedMethodRewriter.desugar(method, lazyAppInfo.get(), methodProcessingContext);
     }
     if (d8NestBasedAccessDesugaring != null) {
       NestBridgeConsumer bridgeConsumer = NestBridgeConsumer.createForD8(methodProcessor);
       didDesugar |= d8NestBasedAccessDesugaring.desugar(method, bridgeConsumer);
     }
+    if (invokeSpecialToSelfDesugaring != null) {
+      didDesugar |= invokeSpecialToSelfDesugaring.desugar(method, desugaringEventConsumer);
+    }
+
     return didDesugar;
   }
 
@@ -1139,7 +1197,7 @@
       IRCode code,
       OptimizationFeedback feedback,
       MethodProcessor methodProcessor,
-      MethodProcessingId methodProcessingId) {
+      MethodProcessingContext methodProcessingContext) {
     ProgramMethod context = code.context();
     DexEncodedMethod method = context.getDefinition();
     DexProgramClass holder = context.getHolder();
@@ -1222,7 +1280,7 @@
     if (serviceLoaderRewriter != null) {
       assert appView.appInfo().hasLiveness();
       timing.begin("Rewrite service loaders");
-      serviceLoaderRewriter.rewrite(code, methodProcessingId);
+      serviceLoaderRewriter.rewrite(code, methodProcessingContext);
       timing.end();
     }
 
@@ -1296,7 +1354,7 @@
       timing.begin("Optimize library methods");
       appView
           .libraryMethodOptimizer()
-          .optimize(code, feedback, methodProcessor, methodProcessingId);
+          .optimize(code, feedback, methodProcessor, methodProcessingContext);
       timing.end();
       assert code.isConsistentSSA();
     }
@@ -1314,7 +1372,7 @@
 
     timing.begin("Remove trivial type checks/casts");
     codeRewriter.removeTrivialCheckCastAndInstanceOfInstructions(
-        code, context, methodProcessor, methodProcessingId);
+        code, context, methodProcessor, methodProcessingContext);
     timing.end();
 
     if (enumValueOptimizer != null) {
@@ -1361,7 +1419,7 @@
     if (codeRewriter.simplifyControlFlow(code)) {
       timing.begin("Remove trivial type checks/casts");
       codeRewriter.removeTrivialCheckCastAndInstanceOfInstructions(
-          code, context, methodProcessor, methodProcessingId);
+          code, context, methodProcessor, methodProcessingContext);
       timing.end();
     }
     timing.end();
@@ -1439,7 +1497,7 @@
           code,
           feedback,
           methodProcessor,
-          methodProcessingId,
+          methodProcessingContext,
           inliner,
           Suppliers.memoize(
               () ->
@@ -1459,9 +1517,9 @@
 
     if (interfaceMethodRewriter != null) {
       timing.begin("Rewrite interface methods");
-      interfaceMethodRewriter.rewriteMethodReferences(code, methodProcessor, methodProcessingId);
+      interfaceMethodRewriter.rewriteMethodReferences(
+          code, methodProcessor, methodProcessingContext);
       timing.end();
-      assert code.isConsistentSSA();
     }
 
     previous = printMethod(code, "IR after interface method rewriting (SSA)", previous);
@@ -1480,7 +1538,7 @@
 
     if (twrCloseResourceRewriter != null) {
       timing.begin("Rewrite TWR close");
-      twrCloseResourceRewriter.rewriteIR(code);
+      twrCloseResourceRewriter.rewriteIR(code, methodProcessingContext);
       timing.end();
     }
 
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/MethodProcessingId.java b/src/main/java/com/android/tools/r8/ir/conversion/MethodProcessingId.java
deleted file mode 100644
index 143d6ff..0000000
--- a/src/main/java/com/android/tools/r8/ir/conversion/MethodProcessingId.java
+++ /dev/null
@@ -1,93 +0,0 @@
-// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
-
-package com.android.tools.r8.ir.conversion;
-
-import com.android.tools.r8.graph.ProgramMethod;
-import com.android.tools.r8.utils.InternalOptions;
-import com.android.tools.r8.utils.collections.ProgramMethodSet;
-import com.android.tools.r8.utils.collections.SortedProgramMethodSet;
-import java.util.function.BiConsumer;
-
-public class MethodProcessingId {
-
-  private final int primaryId;
-  private int secondaryId = 1;
-
-  private MethodProcessingId(int primaryId) {
-    this.primaryId = primaryId;
-  }
-
-  public String getAndIncrementId() {
-    String id = getId();
-    secondaryId++;
-    return id;
-  }
-
-  public String getFullyQualifiedIdAndIncrement() {
-    String id = getFullyQualifiedId();
-    secondaryId++;
-    return id;
-  }
-
-  public String getId() {
-    if (secondaryId == 1) {
-      return Integer.toString(primaryId);
-    }
-    return getFullyQualifiedId();
-  }
-
-  public String getFullyQualifiedId() {
-    return primaryId + "$" + secondaryId;
-  }
-
-  public int getPrimaryId() {
-    return primaryId;
-  }
-
-  public static class Factory {
-
-    private final BiConsumer<ProgramMethod, MethodProcessingId> consumer;
-    private int nextId = 1;
-
-    public Factory() {
-      this(null);
-    }
-
-    public Factory(BiConsumer<ProgramMethod, MethodProcessingId> consumer) {
-      this.consumer = consumer;
-    }
-
-    public ReservedMethodProcessingIds reserveIds(SortedProgramMethodSet wave) {
-      ReservedMethodProcessingIds result = new ReservedMethodProcessingIds(nextId, wave.size());
-      nextId += wave.size();
-      return result;
-    }
-
-    public class ReservedMethodProcessingIds {
-
-      private final int firstReservedId;
-      private final int numberOfReservedIds;
-
-      private final ProgramMethodSet seen =
-          InternalOptions.assertionsEnabled() ? ProgramMethodSet.createConcurrent() : null;
-
-      public ReservedMethodProcessingIds(int firstReservedId, int numberOfReservedIds) {
-        this.firstReservedId = firstReservedId;
-        this.numberOfReservedIds = numberOfReservedIds;
-      }
-
-      public MethodProcessingId get(ProgramMethod method, int index) {
-        assert index >= 0;
-        assert index < numberOfReservedIds;
-        assert seen.add(method);
-        MethodProcessingId result = new MethodProcessingId(firstReservedId + index);
-        if (consumer != null) {
-          consumer.accept(method, result);
-        }
-        return result;
-      }
-    }
-  }
-}
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/MethodProcessor.java b/src/main/java/com/android/tools/r8/ir/conversion/MethodProcessor.java
index 3be22fa..140e0ce 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/MethodProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/MethodProcessor.java
@@ -15,7 +15,7 @@
 
   public abstract boolean shouldApplyCodeRewritings(ProgramMethod method);
 
-  public abstract void scheduleMethodForProcessingAfterCurrentWave(ProgramMethod method);
+  public abstract void scheduleDesugaredMethodForProcessing(ProgramMethod method);
 
   public abstract CallSiteInformation getCallSiteInformation();
 }
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/MethodProcessorWithWave.java b/src/main/java/com/android/tools/r8/ir/conversion/MethodProcessorWithWave.java
index c50e8e3..d0f2413 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/MethodProcessorWithWave.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/MethodProcessorWithWave.java
@@ -22,7 +22,7 @@
   }
 
   @Override
-  public void scheduleMethodForProcessingAfterCurrentWave(ProgramMethod method) {
+  public void scheduleDesugaredMethodForProcessing(ProgramMethod method) {
     waveExtension.add(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 be427aa..f637225 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
@@ -3,11 +3,11 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.ir.conversion;
 
+import com.android.tools.r8.contexts.CompilationContext.MethodProcessingContext;
+import com.android.tools.r8.contexts.CompilationContext.ProcessorContext;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.ProgramMethod;
-import com.android.tools.r8.ir.conversion.MethodProcessingId.Factory.ReservedMethodProcessingIds;
 import com.android.tools.r8.utils.ThreadUtils;
-import com.android.tools.r8.utils.ThrowingBiConsumer;
 import com.android.tools.r8.utils.collections.SortedProgramMethodSet;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.ExecutorService;
@@ -18,33 +18,30 @@
  */
 public class OneTimeMethodProcessor extends MethodProcessorWithWave {
 
-  private final MethodProcessingId.Factory methodProcessingIdFactory;
+  private final ProcessorContext processorContext;
 
-  private OneTimeMethodProcessor(
-      MethodProcessingId.Factory methodProcessingIdFactory, SortedProgramMethodSet wave) {
-    this.methodProcessingIdFactory = methodProcessingIdFactory;
+  private OneTimeMethodProcessor(ProcessorContext processorContext, SortedProgramMethodSet wave) {
+    this.processorContext = processorContext;
     this.wave = wave;
   }
 
   public static OneTimeMethodProcessor create(ProgramMethod methodToProcess, AppView<?> appView) {
-    return create(methodToProcess, appView.methodProcessingIdFactory());
+    return create(SortedProgramMethodSet.create(methodToProcess), appView);
   }
 
   public static OneTimeMethodProcessor create(
-      ProgramMethod methodToProcess, MethodProcessingId.Factory methodProcessingIdFactory) {
-    return new OneTimeMethodProcessor(
-        methodProcessingIdFactory, SortedProgramMethodSet.create(methodToProcess));
+      ProgramMethod methodToProcess, ProcessorContext processorContext) {
+    return create(SortedProgramMethodSet.create(methodToProcess), processorContext);
   }
 
   public static OneTimeMethodProcessor create(
       SortedProgramMethodSet methodsToProcess, AppView<?> appView) {
-    return create(methodsToProcess, appView.methodProcessingIdFactory());
+    return create(methodsToProcess, appView.createProcessorContext());
   }
 
   public static OneTimeMethodProcessor create(
-      SortedProgramMethodSet methodsToProcess,
-      MethodProcessingId.Factory methodProcessingIdFactory) {
-    return new OneTimeMethodProcessor(methodProcessingIdFactory, methodsToProcess);
+      SortedProgramMethodSet methodsToProcess, ProcessorContext processorContext) {
+    return new OneTimeMethodProcessor(processorContext, methodsToProcess);
   }
 
   @Override
@@ -52,27 +49,26 @@
     return true;
   }
 
-  public <E extends Exception> void forEachWaveWithExtension(
-      ThrowingBiConsumer<ProgramMethod, MethodProcessingId, E> consumer) throws E {
+  @FunctionalInterface
+  public interface MethodAction<E extends Exception> {
+    void accept(ProgramMethod method, MethodProcessingContext methodProcessingContext) throws E;
+  }
+
+  public <E extends Exception> void forEachWaveWithExtension(MethodAction<E> consumer) throws E {
     while (!wave.isEmpty()) {
-      ReservedMethodProcessingIds methodProcessingIds = methodProcessingIdFactory.reserveIds(wave);
-      int i = 0;
       for (ProgramMethod method : wave) {
-        consumer.accept(method, methodProcessingIds.get(method, i++));
+        consumer.accept(method, processorContext.createMethodProcessingContext(method));
       }
       prepareForWaveExtensionProcessing();
     }
   }
 
   public <E extends Exception> void forEachWaveWithExtension(
-      ThrowingBiConsumer<ProgramMethod, MethodProcessingId, E> consumer,
-      ExecutorService executorService)
-      throws ExecutionException {
+      MethodAction<E> consumer, ExecutorService executorService) throws ExecutionException {
     while (!wave.isEmpty()) {
-      ReservedMethodProcessingIds methodProcessingIds = methodProcessingIdFactory.reserveIds(wave);
       ThreadUtils.processItems(
           wave,
-          (method, index) -> consumer.accept(method, methodProcessingIds.get(method, index)),
+          method -> consumer.accept(method, processorContext.createMethodProcessingContext(method)),
           executorService);
       prepareForWaveExtensionProcessing();
     }
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/PostMethodProcessor.java b/src/main/java/com/android/tools/r8/ir/conversion/PostMethodProcessor.java
index ba7fa7f..06ddf34 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/PostMethodProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/PostMethodProcessor.java
@@ -6,6 +6,7 @@
 
 import static com.android.tools.r8.graph.DexProgramClass.asProgramClassOrNull;
 
+import com.android.tools.r8.contexts.CompilationContext.ProcessorContext;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexMethod;
@@ -13,7 +14,6 @@
 import com.android.tools.r8.graph.GraphLens;
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.code.IRCode;
-import com.android.tools.r8.ir.conversion.MethodProcessingId.Factory.ReservedMethodProcessingIds;
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
 import com.android.tools.r8.ir.optimize.info.OptimizationFeedback;
 import com.android.tools.r8.logging.Log;
@@ -35,6 +35,7 @@
 
 public class PostMethodProcessor extends MethodProcessorWithWave {
 
+  private final ProcessorContext processorContext;
   private final AppView<AppInfoWithLiveness> appView;
   private final Collection<CodeOptimization> defaultCodeOptimizations;
   private final Map<DexMethod, Collection<CodeOptimization>> methodsMap;
@@ -46,6 +47,7 @@
       Collection<CodeOptimization> defaultCodeOptimizations,
       Map<DexMethod, Collection<CodeOptimization>> methodsMap,
       CallGraph callGraph) {
+    this.processorContext = appView.createProcessorContext();
     this.appView = appView;
     this.defaultCodeOptimizations = defaultCodeOptimizations;
     this.methodsMap = methodsMap;
@@ -173,16 +175,13 @@
       assert !wave.isEmpty();
       assert waveExtension.isEmpty();
       do {
-        ReservedMethodProcessingIds methodProcessingIds =
-            appView.methodProcessingIdFactory().reserveIds(wave);
         ThreadUtils.processItems(
             wave,
-            (method, index) -> {
+            method -> {
               Collection<CodeOptimization> codeOptimizations =
                   methodsMap.get(method.getReference());
               assert codeOptimizations != null && !codeOptimizations.isEmpty();
-              forEachMethod(
-                  method, codeOptimizations, feedback, methodProcessingIds.get(method, index));
+              forEachMethod(method, codeOptimizations, feedback);
             },
             executorService);
         processed.addAll(wave);
@@ -194,8 +193,7 @@
   private void forEachMethod(
       ProgramMethod method,
       Collection<CodeOptimization> codeOptimizations,
-      OptimizationFeedback feedback,
-      MethodProcessingId methodProcessingId) {
+      OptimizationFeedback feedback) {
     // TODO(b/140766440): Make IRConverter#process receive a list of CodeOptimization to conduct.
     //   Then, we can share IRCode creation there.
     if (appView.options().skipIR) {
@@ -209,7 +207,8 @@
     }
     // TODO(b/140768815): Reprocessing may trigger more methods to revisit. Update waves on-the-fly.
     for (CodeOptimization codeOptimization : codeOptimizations) {
-      codeOptimization.optimize(code, feedback, this, methodProcessingId);
+      codeOptimization.optimize(
+          code, feedback, this, processorContext.createMethodProcessingContext(method));
     }
   }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/PrimaryMethodProcessor.java b/src/main/java/com/android/tools/r8/ir/conversion/PrimaryMethodProcessor.java
index 065d00b..8969cba 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/PrimaryMethodProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/PrimaryMethodProcessor.java
@@ -4,15 +4,15 @@
 
 package com.android.tools.r8.ir.conversion;
 
+import com.android.tools.r8.contexts.CompilationContext.MethodProcessingContext;
+import com.android.tools.r8.contexts.CompilationContext.ProcessorContext;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.conversion.CallGraph.Node;
-import com.android.tools.r8.ir.conversion.MethodProcessingId.Factory.ReservedMethodProcessingIds;
 import com.android.tools.r8.logging.Log;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.ThreadUtils;
-import com.android.tools.r8.utils.ThrowingBiFunction;
 import com.android.tools.r8.utils.Timing;
 import com.android.tools.r8.utils.Timing.TimingMerger;
 import com.android.tools.r8.utils.collections.ProgramMethodSet;
@@ -36,8 +36,8 @@
     void notifyWaveStart(ProgramMethodSet wave);
   }
 
+  private final AppView<?> appView;
   private final CallSiteInformation callSiteInformation;
-  private final MethodProcessingId.Factory methodProcessingIdFactory;
   private final PostMethodProcessor.Builder postMethodProcessorBuilder;
   private final Deque<SortedProgramMethodSet> waves;
 
@@ -45,8 +45,8 @@
       AppView<AppInfoWithLiveness> appView,
       PostMethodProcessor.Builder postMethodProcessorBuilder,
       CallGraph callGraph) {
+    this.appView = appView;
     this.callSiteInformation = callGraph.createCallSiteInformation(appView);
-    this.methodProcessingIdFactory = appView.methodProcessingIdFactory();
     this.postMethodProcessorBuilder = postMethodProcessorBuilder;
     this.waves = createWaves(appView, callGraph, callSiteInformation);
   }
@@ -104,6 +104,11 @@
     return waves;
   }
 
+  @FunctionalInterface
+  public interface MethodAction<E extends Exception> {
+    Timing apply(ProgramMethod method, MethodProcessingContext methodProcessingContext) throws E;
+  }
+
   /**
    * Applies the given method to all leaf nodes of the graph.
    *
@@ -111,7 +116,7 @@
    * processed at the same time is passed. This can be used to avoid races in concurrent processing.
    */
   <E extends Exception> void forEachMethod(
-      ThrowingBiFunction<ProgramMethod, MethodProcessingId, Timing, E> consumer,
+      MethodAction<E> consumer,
       WaveStartAction waveStartAction,
       Consumer<ProgramMethodSet> waveDone,
       Timing timing,
@@ -120,18 +125,19 @@
     TimingMerger merger =
         timing.beginMerger("primary-processor", ThreadUtils.getNumberOfThreads(executorService));
     while (!waves.isEmpty()) {
+      ProcessorContext processorContext = appView.createProcessorContext();
       wave = waves.removeFirst();
       assert !wave.isEmpty();
       assert waveExtension.isEmpty();
       do {
         waveStartAction.notifyWaveStart(wave);
-        ReservedMethodProcessingIds methodProcessingIds =
-            methodProcessingIdFactory.reserveIds(wave);
         Collection<Timing> timings =
             ThreadUtils.processItemsWithResults(
                 wave,
-                (method, index) -> {
-                  Timing time = consumer.apply(method, methodProcessingIds.get(method, index));
+                method -> {
+                  Timing time =
+                      consumer.apply(
+                          method, processorContext.createMethodProcessingContext(method));
                   time.end();
                   return time;
                 },
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/BackportedMethodRewriter.java b/src/main/java/com/android/tools/r8/ir/desugar/BackportedMethodRewriter.java
index 09fd687..704ed92 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/BackportedMethodRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/BackportedMethodRewriter.java
@@ -6,6 +6,7 @@
 
 import com.android.tools.r8.cf.code.CfInstruction;
 import com.android.tools.r8.cf.code.CfInvoke;
+import com.android.tools.r8.contexts.CompilationContext.MethodProcessingContext;
 import com.android.tools.r8.dex.ApplicationReader;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.AppInfo;
@@ -34,13 +35,10 @@
 import com.android.tools.r8.synthesis.SyntheticNaming;
 import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.AndroidApp;
-import com.android.tools.r8.utils.IntBox;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.InternalOptions.DesugarState;
 import com.android.tools.r8.utils.StringDiagnostic;
 import com.android.tools.r8.utils.Timing;
-import com.google.common.hash.Hasher;
-import com.google.common.hash.Hashing;
 import java.io.IOException;
 import java.util.ArrayList;
 import java.util.Collections;
@@ -53,7 +51,6 @@
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.ExecutorService;
 import java.util.function.Consumer;
-import java.util.function.Supplier;
 import org.objectweb.asm.Opcodes;
 
 public final class BackportedMethodRewriter {
@@ -109,12 +106,18 @@
     BackportedMethods.registerSynthesizedCodeReferences(options.itemFactory);
   }
 
-  public boolean desugar(ProgramMethod method, AppInfoWithClassHierarchy appInfo) {
-    return desugar(method, appInfo, synthesizedMethods::add);
+  public boolean desugar(
+      ProgramMethod method,
+      AppInfoWithClassHierarchy appInfo,
+      MethodProcessingContext methodProcessingContext) {
+    return desugar(method, appInfo, methodProcessingContext, synthesizedMethods::add);
   }
 
   public boolean desugar(
-      ProgramMethod method, AppInfoWithClassHierarchy appInfo, Consumer<ProgramMethod> consumer) {
+      ProgramMethod method,
+      AppInfoWithClassHierarchy appInfo,
+      MethodProcessingContext methodProcessingContext,
+      Consumer<ProgramMethod> consumer) {
     if (!enabled) {
       return false;
     }
@@ -131,14 +134,6 @@
     }
     CfCode code = method.getDefinition().getCode().asCfCode();
     ListIterator<CfInstruction> iterator = code.getInstructions().listIterator();
-    // TODO(b/172194101): Make this part of a unique context construction.
-    IntBox nextBackportId = new IntBox();
-    Supplier<String> methodIdSupplier =
-        () -> {
-          Hasher hasher = Hashing.sha256().newHasher();
-          method.getReference().hash(hasher);
-          return "$" + hasher.hash().toString() + "$" + nextBackportId.getAndIncrement();
-        };
     boolean replaced = false;
     while (iterator.hasNext()) {
       CfInvoke invoke = iterator.next().asInvoke();
@@ -155,7 +150,7 @@
           iterator = mutableInstructions.listIterator(iterator.previousIndex());
           iterator.next();
         }
-        provider.rewriteInvoke(invoke, iterator, method, appInfo, consumer, methodIdSupplier);
+        provider.rewriteInvoke(invoke, iterator, appInfo, consumer, methodProcessingContext);
         replaced = true;
       }
     }
@@ -1357,10 +1352,9 @@
     public abstract void rewriteInvoke(
         CfInvoke invoke,
         ListIterator<CfInstruction> iterator,
-        ProgramMethod context,
         AppInfoWithClassHierarchy appInfo,
         Consumer<ProgramMethod> registerSynthesizedMethod,
-        Supplier<String> methodIdProvider);
+        MethodProcessingContext methodProcessingContext);
   }
 
   private static final class InvokeRewriter extends MethodProvider {
@@ -1376,10 +1370,9 @@
     public void rewriteInvoke(
         CfInvoke invoke,
         ListIterator<CfInstruction> iterator,
-        ProgramMethod context,
         AppInfoWithClassHierarchy appInfo,
         Consumer<ProgramMethod> registerSynthesizedMethod,
-        Supplier<String> methodIdProvider) {
+        MethodProcessingContext methodProcessingContext) {
       rewriter.rewrite(invoke, iterator, appInfo.dexItemFactory());
     }
   }
@@ -1403,33 +1396,29 @@
     public void rewriteInvoke(
         CfInvoke invoke,
         ListIterator<CfInstruction> iterator,
-        ProgramMethod context,
         AppInfoWithClassHierarchy appInfo,
         Consumer<ProgramMethod> registerSynthesizedMethod,
-        Supplier<String> methodIdProvider) {
-      ProgramMethod method = getSyntheticMethod(context, methodIdProvider, appInfo);
+        MethodProcessingContext methodProcessingContext) {
+      ProgramMethod method = getSyntheticMethod(appInfo, methodProcessingContext);
       registerSynthesizedMethod.accept(method);
       iterator.remove();
       iterator.add(new CfInvoke(Opcodes.INVOKESTATIC, method.getReference(), false));
     }
 
     private ProgramMethod getSyntheticMethod(
-        ProgramMethod context,
-        Supplier<String> methodIdProvider,
-        AppInfoWithClassHierarchy appInfo) {
+        AppInfoWithClassHierarchy appInfo, MethodProcessingContext methodProcessingContext) {
       return appInfo
           .getSyntheticItems()
           .createMethod(
               SyntheticNaming.SyntheticKind.BACKPORT,
-              context,
+              methodProcessingContext.createUniqueContext(),
               appInfo.dexItemFactory(),
               builder ->
                   builder
                       .setProto(getProto(appInfo.dexItemFactory()))
                       .setAccessFlags(MethodAccessFlags.createPublicStaticSynthetic())
                       .setCode(
-                          methodSig -> generateTemplateMethod(appInfo.app().options, methodSig)),
-              methodIdProvider);
+                          methodSig -> generateTemplateMethod(appInfo.app().options, methodSig)));
     }
 
     public DexProto getProto(DexItemFactory itemFactory) {
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/CfInstructionDesugaring.java b/src/main/java/com/android/tools/r8/ir/desugar/CfInstructionDesugaring.java
new file mode 100644
index 0000000..08a7934
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/desugar/CfInstructionDesugaring.java
@@ -0,0 +1,29 @@
+// Copyright (c) 2021, 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.desugar;
+
+import com.android.tools.r8.cf.code.CfInstruction;
+import com.android.tools.r8.graph.ProgramMethod;
+import java.util.List;
+
+/** Interface for desugaring a single class-file instruction. */
+public interface CfInstructionDesugaring {
+
+  /**
+   * Given an instruction, returns the list of instructions that the instruction should be desugared
+   * to. If no desugaring is needed, {@code null} should be returned (for efficiency).
+   */
+  List<CfInstruction> desugarInstruction(
+      CfInstruction instruction,
+      CfInstructionDesugaringEventConsumer consumer,
+      ProgramMethod context);
+
+  /**
+   * Returns true if the given instruction needs desugaring.
+   *
+   * <p>This should return true if-and-only-if {@link #desugarInstruction} returns non-null.
+   */
+  boolean needsDesugaring(CfInstruction instruction, ProgramMethod context);
+}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/CfInstructionDesugaringCollection.java b/src/main/java/com/android/tools/r8/ir/desugar/CfInstructionDesugaringCollection.java
new file mode 100644
index 0000000..bf63ace
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/desugar/CfInstructionDesugaringCollection.java
@@ -0,0 +1,26 @@
+// Copyright (c) 2021, 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.desugar;
+
+import com.android.tools.r8.graph.ProgramMethod;
+
+/**
+ * Abstracts a collection of low-level desugarings (i.e., mappings from class-file instructions to
+ * new class-file instructions).
+ *
+ * <p>The combined set of low-level desugarings provide a way to desugar a method in full
+ */
+public abstract class CfInstructionDesugaringCollection {
+
+  public static CfInstructionDesugaringCollection empty() {
+    return new EmptyCfInstructionDesugaringCollection();
+  }
+
+  /** Desugars the instructions in the given method. */
+  public abstract void desugar(ProgramMethod method, CfInstructionDesugaringEventConsumer consumer);
+
+  /** Returns true if the given method needs desugaring. */
+  public abstract boolean needsDesugaring(ProgramMethod method);
+}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/CfInstructionDesugaringEventConsumer.java b/src/main/java/com/android/tools/r8/ir/desugar/CfInstructionDesugaringEventConsumer.java
new file mode 100644
index 0000000..0b01594
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/desugar/CfInstructionDesugaringEventConsumer.java
@@ -0,0 +1,119 @@
+// Copyright (c) 2021, 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.desugar;
+
+import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexReference;
+import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.ir.desugar.invokespecial.InvokeSpecialBridgeInfo;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.function.Consumer;
+
+/**
+ * Class that gets notified for structural changes made as a result of desugaring (e.g., the
+ * inserting of a new method).
+ */
+public abstract class CfInstructionDesugaringEventConsumer {
+
+  public static D8CfInstructionDesugaringEventConsumer createForD8() {
+    return new D8CfInstructionDesugaringEventConsumer();
+  }
+
+  public static R8CfInstructionDesugaringEventConsumer createForR8() {
+    return new R8CfInstructionDesugaringEventConsumer();
+  }
+
+  public static CfInstructionDesugaringEventConsumer createForDesugaredCode() {
+    return new CfInstructionDesugaringEventConsumer() {
+      @Override
+      public void acceptInvokeSpecialBridgeInfo(InvokeSpecialBridgeInfo info) {
+        assert false;
+      }
+    };
+  }
+
+  public abstract void acceptInvokeSpecialBridgeInfo(InvokeSpecialBridgeInfo info);
+
+  public static class D8CfInstructionDesugaringEventConsumer
+      extends CfInstructionDesugaringEventConsumer {
+
+    private final Map<DexReference, InvokeSpecialBridgeInfo> pendingInvokeSpecialBridges =
+        new LinkedHashMap<>();
+
+    @Override
+    public void acceptInvokeSpecialBridgeInfo(InvokeSpecialBridgeInfo info) {
+      synchronized (pendingInvokeSpecialBridges) {
+        assert !pendingInvokeSpecialBridges.containsKey(info.getNewDirectMethod().getReference());
+        pendingInvokeSpecialBridges.put(info.getNewDirectMethod().getReference(), info);
+      }
+    }
+
+    public List<ProgramMethod> finalizeDesugaring(AppView<?> appView) {
+      List<ProgramMethod> needsReprocessing = new ArrayList<>();
+      finalizeInvokeSpecialDesugaring(appView, needsReprocessing::add);
+      return needsReprocessing;
+    }
+
+    private void finalizeInvokeSpecialDesugaring(
+        AppView<?> appView, Consumer<ProgramMethod> needsReprocessing) {
+      // Fixup the code of the new private methods have that been synthesized.
+      pendingInvokeSpecialBridges
+          .values()
+          .forEach(
+              info -> {
+                ProgramMethod newDirectMethod = info.getNewDirectMethod();
+                newDirectMethod
+                    .getDefinition()
+                    .setCode(info.getVirtualMethod().getDefinition().getCode(), appView);
+              });
+
+      // Reprocess the methods that were subject to invoke-special desugaring (because their body
+      // has been moved to a private method).
+      pendingInvokeSpecialBridges
+          .values()
+          .forEach(
+              info -> {
+                info.getVirtualMethod()
+                    .getDefinition()
+                    .setCode(info.getVirtualMethodCode(), appView);
+                needsReprocessing.accept(info.getVirtualMethod());
+              });
+
+      pendingInvokeSpecialBridges.clear();
+    }
+
+    public boolean verifyNothingToFinalize() {
+      assert pendingInvokeSpecialBridges.isEmpty();
+      return true;
+    }
+  }
+
+  public static class R8CfInstructionDesugaringEventConsumer
+      extends CfInstructionDesugaringEventConsumer {
+
+    private final List<InvokeSpecialBridgeInfo> pendingInvokeSpecialBridges = new ArrayList<>();
+
+    @Override
+    public void acceptInvokeSpecialBridgeInfo(InvokeSpecialBridgeInfo info) {
+      synchronized (pendingInvokeSpecialBridges) {
+        pendingInvokeSpecialBridges.add(info);
+      }
+    }
+
+    public void finalizeDesugaring(AppView<? extends AppInfoWithClassHierarchy> appView) {
+      Collections.sort(pendingInvokeSpecialBridges);
+      pendingInvokeSpecialBridges.forEach(
+          info ->
+              info.getVirtualMethod()
+                  .getDefinition()
+                  .setCode(info.getVirtualMethodCode(), appView));
+    }
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/EmptyCfInstructionDesugaringCollection.java b/src/main/java/com/android/tools/r8/ir/desugar/EmptyCfInstructionDesugaringCollection.java
new file mode 100644
index 0000000..8c56b4d
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/desugar/EmptyCfInstructionDesugaringCollection.java
@@ -0,0 +1,20 @@
+// Copyright (c) 2021, 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.desugar;
+
+import com.android.tools.r8.graph.ProgramMethod;
+
+public class EmptyCfInstructionDesugaringCollection extends CfInstructionDesugaringCollection {
+
+  @Override
+  public void desugar(ProgramMethod method, CfInstructionDesugaringEventConsumer consumer) {
+    // Intentionally empty.
+  }
+
+  @Override
+  public boolean needsDesugaring(ProgramMethod method) {
+    return false;
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/InterfaceMethodRewriter.java b/src/main/java/com/android/tools/r8/ir/desugar/InterfaceMethodRewriter.java
index db127f2..6ffc8e2 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/InterfaceMethodRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/InterfaceMethodRewriter.java
@@ -20,6 +20,7 @@
 
 import com.android.tools.r8.DesugarGraphConsumer;
 import com.android.tools.r8.cf.CfVersion;
+import com.android.tools.r8.contexts.CompilationContext.MethodProcessingContext;
 import com.android.tools.r8.errors.CompilationError;
 import com.android.tools.r8.errors.Unimplemented;
 import com.android.tools.r8.graph.AppInfo;
@@ -63,7 +64,6 @@
 import com.android.tools.r8.ir.code.InvokeSuper;
 import com.android.tools.r8.ir.code.Value;
 import com.android.tools.r8.ir.conversion.IRConverter;
-import com.android.tools.r8.ir.conversion.MethodProcessingId;
 import com.android.tools.r8.ir.conversion.MethodProcessor;
 import com.android.tools.r8.ir.desugar.DefaultMethodsHelper.Collection;
 import com.android.tools.r8.ir.desugar.InterfaceProcessor.InterfaceProcessorNestedGraphLens;
@@ -269,7 +269,9 @@
   // Rewrites the references to static and default interface methods.
   // NOTE: can be called for different methods concurrently.
   public void rewriteMethodReferences(
-      IRCode code, MethodProcessor methodProcessor, MethodProcessingId methodProcessingId) {
+      IRCode code,
+      MethodProcessor methodProcessor,
+      MethodProcessingContext methodProcessingContext) {
     ProgramMethod context = code.context();
     if (synthesizedMethods.contains(context)) {
       return;
@@ -302,7 +304,7 @@
                 affectedValues,
                 blocksToRemove,
                 methodProcessor,
-                methodProcessingId);
+                methodProcessingContext);
             break;
           case INVOKE_SUPER:
             rewriteInvokeSuper(instruction.asInvokeSuper(), instructions, context);
@@ -407,7 +409,7 @@
       Set<Value> affectedValues,
       Set<BasicBlock> blocksToRemove,
       MethodProcessor methodProcessor,
-      MethodProcessingId methodProcessingId) {
+      MethodProcessingContext methodProcessingContext) {
     DexMethod invokedMethod = invoke.getInvokedMethod();
     if (appView.getSyntheticItems().isPendingSynthetic(invokedMethod.holder)) {
       // We did not create this code yet, but it will not require rewriting.
@@ -456,7 +458,7 @@
                 .getSyntheticItems()
                 .createMethod(
                     SyntheticNaming.SyntheticKind.STATIC_INTERFACE_CALL,
-                    context.getHolder(),
+                    methodProcessingContext.createUniqueContext(),
                     factory,
                     syntheticMethodBuilder ->
                         syntheticMethodBuilder
@@ -504,9 +506,9 @@
     UtilityMethodForCodeOptimizations throwMethod =
         resolutionResult == null
             ? UtilityMethodsForCodeOptimizations.synthesizeThrowNoSuchMethodErrorMethod(
-                appView, context, methodProcessingId)
+                appView, methodProcessingContext)
             : UtilityMethodsForCodeOptimizations.synthesizeThrowIncompatibleClassChangeErrorMethod(
-                appView, context, methodProcessingId);
+                appView, methodProcessingContext);
     throwMethod.optimize(methodProcessor);
 
     InvokeStatic throwInvoke =
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/InvokeSpecialBridgeSynthesizer.java b/src/main/java/com/android/tools/r8/ir/desugar/InvokeSpecialBridgeSynthesizer.java
deleted file mode 100644
index 51003cb..0000000
--- a/src/main/java/com/android/tools/r8/ir/desugar/InvokeSpecialBridgeSynthesizer.java
+++ /dev/null
@@ -1,139 +0,0 @@
-// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
-
-package com.android.tools.r8.ir.desugar;
-
-import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.CfCode;
-import com.android.tools.r8.graph.DexEncodedMethod;
-import com.android.tools.r8.graph.DexMethod;
-import com.android.tools.r8.graph.DexProgramClass;
-import com.android.tools.r8.graph.ProgramMethod;
-import com.android.tools.r8.graph.analysis.EnqueuerInvokeAnalysis;
-import com.android.tools.r8.ir.conversion.IRConverter;
-import com.android.tools.r8.ir.synthetic.ForwardMethodBuilder;
-import com.android.tools.r8.utils.collections.SortedProgramMethodSet;
-import com.google.common.collect.Sets;
-import java.util.Map;
-import java.util.Set;
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.ExecutionException;
-import java.util.concurrent.ExecutorService;
-
-/**
- * It is possible in class files to have an invoke-special to a virtual method in the same class
- * than the method holding the invoke-special. Such invoke-special are executed correctly on the
- * JVM, but cannot be expressed in terms of invoke-direct or invoke-super in dex. This class
- * introduces bridges to support the case described: the virtual method code is moved to a private
- * synthetic method, and a bridging virtual method with the initial method name and flags is
- * inserted.
- */
-public class InvokeSpecialBridgeSynthesizer {
-
-  private static final String INVOKE_SPECIAL_BRIDGE_PREFIX = "$invoke$special$";
-
-  private final AppView<?> appView;
-
-  private final Map<DexMethod, DexMethod> bridges = new ConcurrentHashMap<>();
-  private final Set<DexMethod> seenBridges = Sets.newIdentityHashSet();
-
-  public InvokeSpecialBridgeSynthesizer(AppView<?> appView) {
-    this.appView = appView;
-  }
-
-  public DexMethod registerBridgeForMethod(DexEncodedMethod method) {
-    assert method.isVirtualMethod();
-    assert !method.getAccessFlags().isFinal();
-    return bridges.computeIfAbsent(
-        method.getReference(),
-        vMethod ->
-            vMethod.withName(
-                appView
-                    .dexItemFactory()
-                    .createString(INVOKE_SPECIAL_BRIDGE_PREFIX + vMethod.name.toString()),
-                appView.dexItemFactory()));
-  }
-
-  // In R8, insertBridgesForR8 is called multiple times until fixed point.
-  // The bridges are inserted prior to IR conversion.
-  public SortedProgramMethodSet insertBridgesForR8() {
-    SortedProgramMethodSet insertedDirectMethods = SortedProgramMethodSet.create();
-    bridges.forEach(
-        (vMethod, dMethod) -> {
-          if (seenBridges.add(vMethod)) {
-            insertedDirectMethods.add(insertBridge(getVirtualMethod(vMethod), dMethod));
-          }
-        });
-    return insertedDirectMethods;
-  }
-
-  // In D8, insertBridgesForD8 is called once.
-  // The bridges are inserted after IR conversion hence the bridges need to be processed.
-  public void insertBridgesForD8(IRConverter converter, ExecutorService executorService)
-      throws ExecutionException {
-    SortedProgramMethodSet insertedBridges = SortedProgramMethodSet.create();
-    bridges.forEach(
-        (virtualMethod, directMethod) -> {
-          ProgramMethod programVirtualMethod = getVirtualMethod(virtualMethod);
-          insertBridge(programVirtualMethod, directMethod);
-          insertedBridges.add(programVirtualMethod);
-        });
-    converter.processMethodsConcurrently(insertedBridges, executorService);
-  }
-
-  private ProgramMethod getVirtualMethod(DexMethod virtualMethod) {
-    DexProgramClass holder = appView.definitionFor(virtualMethod.holder).asProgramClass();
-    assert holder.lookupVirtualMethod(virtualMethod) != null;
-    DexEncodedMethod encodedVirtualMethod = holder.lookupVirtualMethod(virtualMethod);
-    return new ProgramMethod(holder, encodedVirtualMethod);
-  }
-
-  private ProgramMethod insertBridge(ProgramMethod virtualMethod, DexMethod directMethod) {
-    assert virtualMethod.getHolderType() == directMethod.holder;
-    DexProgramClass holder = virtualMethod.getHolder();
-    assert holder.lookupDirectMethod(directMethod) == null;
-    DexEncodedMethod initialVirtualMethod = virtualMethod.getDefinition();
-    DexEncodedMethod newDirectMethod = initialVirtualMethod.toPrivateSyntheticMethod(directMethod);
-    CfCode forwardingCode =
-        ForwardMethodBuilder.builder(appView.dexItemFactory())
-            .setDirectTarget(directMethod, holder.isInterface())
-            .setNonStaticSource(virtualMethod.getReference())
-            .build();
-    initialVirtualMethod.setCode(forwardingCode, appView);
-    initialVirtualMethod.markNotProcessed();
-    holder.addDirectMethod(newDirectMethod);
-    return new ProgramMethod(holder, newDirectMethod);
-  }
-
-  public EnqueuerInvokeAnalysis getEnqueuerInvokeAnalysis() {
-    return new InvokeSpecialBridgeAnalysis();
-  }
-
-  private class InvokeSpecialBridgeAnalysis implements EnqueuerInvokeAnalysis {
-
-    @Override
-    public void traceInvokeStatic(DexMethod invokedMethod, ProgramMethod context) {}
-
-    @Override
-    public void traceInvokeDirect(DexMethod invokedMethod, ProgramMethod context) {
-      DexEncodedMethod lookup = context.getHolder().lookupMethod(invokedMethod);
-      if (lookup != null
-          && lookup.isNonPrivateVirtualMethod()
-          && context.getHolderType() == invokedMethod.holder
-          && !context.getHolder().isInterface()
-          && !lookup.accessFlags.isFinal()) {
-        registerBridgeForMethod(lookup);
-      }
-    }
-
-    @Override
-    public void traceInvokeInterface(DexMethod invokedMethod, ProgramMethod context) {}
-
-    @Override
-    public void traceInvokeSuper(DexMethod invokedMethod, ProgramMethod context) {}
-
-    @Override
-    public void traceInvokeVirtual(DexMethod invokedMethod, ProgramMethod context) {}
-  }
-}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/LambdaRewriter.java b/src/main/java/com/android/tools/r8/ir/desugar/LambdaRewriter.java
index c8c6a54..b12b2a7 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/LambdaRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/LambdaRewriter.java
@@ -13,6 +13,7 @@
 import com.android.tools.r8.cf.code.CfStackInstruction;
 import com.android.tools.r8.cf.code.CfStackInstruction.Opcode;
 import com.android.tools.r8.cf.code.CfStore;
+import com.android.tools.r8.contexts.CompilationContext.MethodProcessingContext;
 import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.CfCode;
@@ -37,8 +38,6 @@
 import com.google.common.base.Suppliers;
 import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Sets;
-import com.google.common.hash.Hasher;
-import com.google.common.hash.Hashing;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
@@ -114,7 +113,10 @@
    *
    * <p>NOTE: this method can be called concurrently for several different methods.
    */
-  public int desugarLambdas(ProgramMethod method, AppInfoWithClassHierarchy appInfo) {
+  public int desugarLambdas(
+      ProgramMethod method,
+      AppInfoWithClassHierarchy appInfo,
+      MethodProcessingContext methodProcessingContext) {
     return desugarLambdas(
         method,
         callsite -> {
@@ -122,7 +124,7 @@
           if (descriptor == null) {
             return null;
           }
-          return createLambdaClass(descriptor, method);
+          return createLambdaClass(descriptor, method, methodProcessingContext);
         });
   }
 
@@ -211,24 +213,16 @@
   }
 
   // Creates a lambda class corresponding to the lambda descriptor and context.
-  public LambdaClass createLambdaClass(LambdaDescriptor descriptor, ProgramMethod accessedFrom) {
-    int nextId =
-        methodIds.compute(
-            accessedFrom.getReference(), (method, value) -> value == null ? 0 : value + 1);
+  public LambdaClass createLambdaClass(
+      LambdaDescriptor descriptor, ProgramMethod accessedFrom, MethodProcessingContext context) {
     Box<LambdaClass> box = new Box<>();
     DexProgramClass clazz =
         appView
             .getSyntheticItems()
             .createClass(
                 SyntheticNaming.SyntheticKind.LAMBDA,
-                accessedFrom.getHolder(),
+                context.createUniqueContext(),
                 appView.dexItemFactory(),
-                // TODO(b/172194101): Make this part of a unique context construction.
-                () -> {
-                  Hasher hasher = Hashing.sha256().newHasher();
-                  accessedFrom.getReference().hash(hasher);
-                  return "$" + hasher.hash().toString() + "$" + nextId;
-                },
                 builder ->
                     box.set(new LambdaClass(builder, appView, this, accessedFrom, descriptor)));
     // Immediately set the actual program class on the lambda.
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/NonEmptyCfInstructionDesugaringCollection.java b/src/main/java/com/android/tools/r8/ir/desugar/NonEmptyCfInstructionDesugaringCollection.java
new file mode 100644
index 0000000..6a11e86
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/desugar/NonEmptyCfInstructionDesugaringCollection.java
@@ -0,0 +1,130 @@
+// Copyright (c) 2021, 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.desugar;
+
+import com.android.tools.r8.cf.code.CfInstruction;
+import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.CfCode;
+import com.android.tools.r8.graph.Code;
+import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.ir.desugar.invokespecial.InvokeSpecialToSelfDesugaring;
+import com.android.tools.r8.ir.desugar.nest.NestBasedAccessDesugaring;
+import com.android.tools.r8.utils.IteratorUtils;
+import com.android.tools.r8.utils.ListUtils;
+import com.android.tools.r8.utils.StringDiagnostic;
+import com.google.common.collect.Iterables;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+public class NonEmptyCfInstructionDesugaringCollection extends CfInstructionDesugaringCollection {
+
+  private final AppView<?> appView;
+  private final List<CfInstructionDesugaring> desugarings = new ArrayList<>();
+  private final InvokeSpecialToSelfDesugaring invokeSpecialToSelfDesugaring;
+  private final NestBasedAccessDesugaring nestBasedAccessDesugaring;
+
+  public NonEmptyCfInstructionDesugaringCollection(AppView<?> appView) {
+    this.appView = appView;
+    this.invokeSpecialToSelfDesugaring = new InvokeSpecialToSelfDesugaring(appView);
+    this.nestBasedAccessDesugaring =
+        appView.options().shouldDesugarNests() ? new NestBasedAccessDesugaring(appView) : null;
+    registerIfNotNull(invokeSpecialToSelfDesugaring);
+    registerIfNotNull(nestBasedAccessDesugaring);
+  }
+
+  private void registerIfNotNull(CfInstructionDesugaring desugaring) {
+    if (desugaring != null) {
+      desugarings.add(desugaring);
+    }
+  }
+
+  @Override
+  public void desugar(ProgramMethod method, CfInstructionDesugaringEventConsumer consumer) {
+    Code code = method.getDefinition().getCode();
+    if (!code.isCfCode()) {
+      appView
+          .options()
+          .reporter
+          .error(
+              new StringDiagnostic(
+                  "Unsupported attempt to desugar non-CF code",
+                  method.getOrigin(),
+                  method.getPosition()));
+      return;
+    }
+
+    CfCode cfCode = code.asCfCode();
+    List<CfInstruction> desugaredInstructions =
+        ListUtils.flatMap(
+            cfCode.getInstructions(),
+            instruction -> desugarInstruction(instruction, consumer, method),
+            null);
+    if (desugaredInstructions != null) {
+      cfCode.setInstructions(desugaredInstructions);
+    } else {
+      assert false : "Expected code to be desugared";
+    }
+  }
+
+  private List<CfInstruction> desugarInstruction(
+      CfInstruction instruction,
+      CfInstructionDesugaringEventConsumer consumer,
+      ProgramMethod context) {
+    // TODO(b/177810578): Migrate other cf-to-cf based desugaring here.
+    Iterator<CfInstructionDesugaring> iterator = desugarings.iterator();
+    while (iterator.hasNext()) {
+      CfInstructionDesugaring desugaring = iterator.next();
+      List<CfInstruction> replacement =
+          desugaring.desugarInstruction(instruction, consumer, context);
+      if (replacement != null) {
+        assert verifyNoOtherDesugaringNeeded(instruction, context, iterator);
+        return replacement;
+      }
+    }
+    return null;
+  }
+
+  @Override
+  public boolean needsDesugaring(ProgramMethod method) {
+    if (!method.getDefinition().hasCode()) {
+      return false;
+    }
+
+    Code code = method.getDefinition().getCode();
+    if (code.isDexCode()) {
+      return false;
+    }
+
+    if (!code.isCfCode()) {
+      throw new Unreachable("Unexpected attempt to determine if non-CF code needs desugaring");
+    }
+
+    return Iterables.any(
+        code.asCfCode().getInstructions(), instruction -> needsDesugaring(instruction, method));
+  }
+
+  private boolean needsDesugaring(CfInstruction instruction, ProgramMethod context) {
+    return Iterables.any(
+        desugarings, desugaring -> desugaring.needsDesugaring(instruction, context));
+  }
+
+  private static boolean verifyNoOtherDesugaringNeeded(
+      CfInstruction instruction,
+      ProgramMethod context,
+      Iterator<CfInstructionDesugaring> iterator) {
+    assert IteratorUtils.nextUntil(
+            iterator,
+            desugaring ->
+                desugaring.desugarInstruction(
+                        instruction,
+                        CfInstructionDesugaringEventConsumer.createForDesugaredCode(),
+                        context)
+                    != null)
+        == null;
+    return true;
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/TwrCloseResourceRewriter.java b/src/main/java/com/android/tools/r8/ir/desugar/TwrCloseResourceRewriter.java
index 98acb2d..69b4dc2 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/TwrCloseResourceRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/TwrCloseResourceRewriter.java
@@ -6,6 +6,7 @@
 
 import com.android.tools.r8.cf.code.CfInstruction;
 import com.android.tools.r8.cf.code.CfInvoke;
+import com.android.tools.r8.contexts.CompilationContext.MethodProcessingContext;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.CfCode;
 import com.android.tools.r8.graph.DexItemFactory;
@@ -57,7 +58,10 @@
             dexItemFactory.voidType, dexItemFactory.throwableType, dexItemFactory.objectType);
   }
 
-  public int rewriteCf(ProgramMethod method, Consumer<ProgramMethod> newMethodCallback) {
+  public int rewriteCf(
+      ProgramMethod method,
+      Consumer<ProgramMethod> newMethodCallback,
+      MethodProcessingContext methodProcessingContext) {
     CfCode code = method.getDefinition().getCode().asCfCode();
     List<CfInstruction> instructions = code.getInstructions();
     Supplier<List<CfInstruction>> lazyNewInstructions =
@@ -72,7 +76,7 @@
         continue;
       }
       // Synthesize a new method.
-      ProgramMethod closeMethod = createSyntheticCloseResourceMethod(method);
+      ProgramMethod closeMethod = createSyntheticCloseResourceMethod(methodProcessingContext);
       newMethodCallback.accept(closeMethod);
       // Rewrite the invoke to the new synthetic.
       int newInstructionIndex = i + newInstructionDelta;
@@ -90,7 +94,7 @@
   }
 
   // Rewrites calls to $closeResource() method. Can be invoked concurrently.
-  public void rewriteIR(IRCode code) {
+  public void rewriteIR(IRCode code, MethodProcessingContext methodProcessingContext) {
     InstructionListIterator iterator = code.instructionListIterator();
     while (iterator.hasNext()) {
       InvokeStatic invoke = iterator.next().asInvokeStatic();
@@ -102,7 +106,8 @@
       // Replace with a call to a synthetic utility.
       assert invoke.outValue() == null;
       assert invoke.inValues().size() == 2;
-      ProgramMethod closeResourceMethod = createSyntheticCloseResourceMethod(code.context());
+      ProgramMethod closeResourceMethod =
+          createSyntheticCloseResourceMethod(methodProcessingContext);
       InvokeStatic newInvoke =
           new InvokeStatic(closeResourceMethod.getReference(), null, invoke.inValues());
       iterator.replaceCurrentInstruction(newInvoke);
@@ -117,12 +122,13 @@
         && method.proto == factory.twrCloseResourceMethodProto;
   }
 
-  private ProgramMethod createSyntheticCloseResourceMethod(ProgramMethod method) {
+  private ProgramMethod createSyntheticCloseResourceMethod(
+      MethodProcessingContext methodProcessingContext) {
     return appView
         .getSyntheticItems()
         .createMethod(
             SyntheticKind.TWR_CLOSE_RESOURCE,
-            method,
+            methodProcessingContext.createUniqueContext(),
             appView.dexItemFactory(),
             methodBuilder ->
                 methodBuilder
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/invokespecial/InvokeSpecialBridgeInfo.java b/src/main/java/com/android/tools/r8/ir/desugar/invokespecial/InvokeSpecialBridgeInfo.java
new file mode 100644
index 0000000..5ef0bb6
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/desugar/invokespecial/InvokeSpecialBridgeInfo.java
@@ -0,0 +1,39 @@
+// Copyright (c) 2021, 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.desugar.invokespecial;
+
+import com.android.tools.r8.graph.CfCode;
+import com.android.tools.r8.graph.ProgramMethod;
+
+public class InvokeSpecialBridgeInfo implements Comparable<InvokeSpecialBridgeInfo> {
+
+  private final ProgramMethod newDirectMethod;
+  private final ProgramMethod virtualMethod;
+  private final CfCode virtualMethodCode;
+
+  InvokeSpecialBridgeInfo(
+      ProgramMethod newDirectMethod, ProgramMethod virtualMethod, CfCode virtualMethodCode) {
+    this.newDirectMethod = newDirectMethod;
+    this.virtualMethod = virtualMethod;
+    this.virtualMethodCode = virtualMethodCode;
+  }
+
+  public ProgramMethod getNewDirectMethod() {
+    return newDirectMethod;
+  }
+
+  public ProgramMethod getVirtualMethod() {
+    return virtualMethod;
+  }
+
+  public CfCode getVirtualMethodCode() {
+    return virtualMethodCode;
+  }
+
+  @Override
+  public int compareTo(InvokeSpecialBridgeInfo info) {
+    return getNewDirectMethod().getReference().compareTo(info.getNewDirectMethod().getReference());
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/invokespecial/InvokeSpecialToSelfDesugaring.java b/src/main/java/com/android/tools/r8/ir/desugar/invokespecial/InvokeSpecialToSelfDesugaring.java
new file mode 100644
index 0000000..2d99341
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/desugar/invokespecial/InvokeSpecialToSelfDesugaring.java
@@ -0,0 +1,161 @@
+// Copyright (c) 2021, 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.desugar.invokespecial;
+
+import com.android.tools.r8.cf.code.CfInstruction;
+import com.android.tools.r8.cf.code.CfInvoke;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.CfCode;
+import com.android.tools.r8.graph.Code;
+import com.android.tools.r8.graph.DexClassAndMethod;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.ir.desugar.CfInstructionDesugaring;
+import com.android.tools.r8.ir.desugar.CfInstructionDesugaringEventConsumer;
+import com.android.tools.r8.ir.synthetic.ForwardMethodBuilder;
+import com.android.tools.r8.utils.ListUtils;
+import com.android.tools.r8.utils.StringDiagnostic;
+import com.google.common.collect.ImmutableList;
+import java.util.List;
+import org.objectweb.asm.Opcodes;
+
+/** This class defines the desugaring of a single invoke-special instruction. */
+public class InvokeSpecialToSelfDesugaring implements CfInstructionDesugaring {
+
+  private static final String INVOKE_SPECIAL_BRIDGE_PREFIX = "$invoke$special$";
+
+  private final AppView<?> appView;
+  private final DexItemFactory dexItemFactory;
+
+  public InvokeSpecialToSelfDesugaring(AppView<?> appView) {
+    this.appView = appView;
+    this.dexItemFactory = appView.dexItemFactory();
+  }
+
+  @Override
+  public boolean needsDesugaring(CfInstruction instruction, ProgramMethod context) {
+    if (instruction.isInvoke()) {
+      return needsDesugaring(instruction.asInvoke(), context) != null;
+    }
+    return false;
+  }
+
+  /** @return the resolved method if desugaring is needed, otherwise null. */
+  private ProgramMethod needsDesugaring(CfInvoke invoke, ProgramMethod context) {
+    if (!invoke.isInvokeSpecial() || invoke.isInvokeConstructor(dexItemFactory)) {
+      return null;
+    }
+
+    DexMethod invokedMethod = invoke.getMethod();
+    if (invokedMethod.getHolderType() != context.getHolderType()) {
+      return null;
+    }
+
+    ProgramMethod method = context.getHolder().lookupProgramMethod(invokedMethod);
+    if (method == null
+        || method.getAccessFlags().isPrivate()
+        || method.getDefinition().isStatic()
+        || (invoke.isInterface() && method.isDefaultMethod())) {
+      return null;
+    }
+
+    return method;
+  }
+
+  public boolean desugar(ProgramMethod method, CfInstructionDesugaringEventConsumer consumer) {
+    Code code = method.getDefinition().getCode();
+    if (!code.isCfCode()) {
+      appView
+          .options()
+          .reporter
+          .error(
+              new StringDiagnostic(
+                  "Unsupported attempt to desugar non-CF code",
+                  method.getOrigin(),
+                  method.getPosition()));
+      return false;
+    }
+
+    CfCode cfCode = code.asCfCode();
+    List<CfInstruction> desugaredInstructions =
+        ListUtils.flatMap(
+            cfCode.getInstructions(),
+            instruction -> desugarInstruction(instruction, consumer, method),
+            null);
+    if (desugaredInstructions != null) {
+      cfCode.setInstructions(desugaredInstructions);
+      return true;
+    }
+    return false;
+  }
+
+  @Override
+  public List<CfInstruction> desugarInstruction(
+      CfInstruction instruction,
+      CfInstructionDesugaringEventConsumer consumer,
+      ProgramMethod context) {
+    if (instruction.isInvoke()) {
+      return desugarInvokeInstruction(instruction.asInvoke(), consumer, context);
+    }
+    return null;
+  }
+
+  private List<CfInstruction> desugarInvokeInstruction(
+      CfInvoke invoke, CfInstructionDesugaringEventConsumer consumer, ProgramMethod context) {
+    ProgramMethod method = needsDesugaring(invoke, context);
+    if (method == null) {
+      return null;
+    }
+
+    if (method.getAccessFlags().isFinal()) {
+      // This method is final thus we can use invoke-virtual.
+      return ImmutableList.of(
+          new CfInvoke(Opcodes.INVOKEVIRTUAL, invoke.getMethod(), invoke.isInterface()));
+    }
+
+    // This is an invoke-special to a virtual method on invoke-special method holder.
+    // The invoke should be rewritten with a bridge.
+    DexMethod bridgeMethod = ensureInvokeSpecialBridge(method, consumer);
+    return ImmutableList.of(
+        new CfInvoke(Opcodes.INVOKESPECIAL, bridgeMethod, invoke.isInterface()));
+  }
+
+  private DexMethod ensureInvokeSpecialBridge(
+      ProgramMethod method, CfInstructionDesugaringEventConsumer consumer) {
+    DexMethod bridgeReference = getInvokeSpecialBridgeReference(method);
+    DexProgramClass clazz = method.getHolder();
+    synchronized (clazz.getMethodCollection()) {
+      if (clazz.lookupProgramMethod(bridgeReference) == null) {
+        // Create a new private method holding the code of the virtual method.
+        ProgramMethod newDirectMethod =
+            method.getDefinition().toPrivateSyntheticMethod(clazz, bridgeReference);
+
+        // Create the new cf code object for the virtual method.
+        CfCode virtualMethodCode =
+            ForwardMethodBuilder.builder(dexItemFactory)
+                .setDirectTarget(bridgeReference, clazz.isInterface())
+                .setNonStaticSource(method.getReference())
+                .build();
+
+        // Add the newly created direct method to its holder.
+        clazz.addDirectMethod(newDirectMethod.getDefinition());
+
+        consumer.acceptInvokeSpecialBridgeInfo(
+            new InvokeSpecialBridgeInfo(newDirectMethod, method, virtualMethodCode));
+      }
+    }
+    return bridgeReference;
+  }
+
+  private DexMethod getInvokeSpecialBridgeReference(DexClassAndMethod method) {
+    return method
+        .getReference()
+        .withName(
+            dexItemFactory.createString(INVOKE_SPECIAL_BRIDGE_PREFIX + method.getName().toString()),
+            dexItemFactory);
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/nest/D8NestBridgeConsumer.java b/src/main/java/com/android/tools/r8/ir/desugar/nest/D8NestBridgeConsumer.java
index 7468cba..953801f 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/nest/D8NestBridgeConsumer.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/nest/D8NestBridgeConsumer.java
@@ -18,16 +18,16 @@
 
   @Override
   public void acceptFieldGetBridge(ProgramField target, ProgramMethod bridge) {
-    methodProcessor.scheduleMethodForProcessingAfterCurrentWave(bridge);
+    methodProcessor.scheduleDesugaredMethodForProcessing(bridge);
   }
 
   @Override
   public void acceptFieldPutBridge(ProgramField target, ProgramMethod bridge) {
-    methodProcessor.scheduleMethodForProcessingAfterCurrentWave(bridge);
+    methodProcessor.scheduleDesugaredMethodForProcessing(bridge);
   }
 
   @Override
   public void acceptMethodBridge(ProgramMethod target, ProgramMethod bridge) {
-    methodProcessor.scheduleMethodForProcessingAfterCurrentWave(bridge);
+    methodProcessor.scheduleDesugaredMethodForProcessing(bridge);
   }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/nest/NestBasedAccessDesugaring.java b/src/main/java/com/android/tools/r8/ir/desugar/nest/NestBasedAccessDesugaring.java
index c516ce8..86aba1e 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/nest/NestBasedAccessDesugaring.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/nest/NestBasedAccessDesugaring.java
@@ -29,6 +29,8 @@
 import com.android.tools.r8.graph.LibraryMember;
 import com.android.tools.r8.graph.ProgramField;
 import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.ir.desugar.CfInstructionDesugaring;
+import com.android.tools.r8.ir.desugar.CfInstructionDesugaringEventConsumer;
 import com.android.tools.r8.synthesis.SyntheticNaming.SyntheticKind;
 import com.android.tools.r8.utils.BooleanUtils;
 import com.android.tools.r8.utils.ListUtils;
@@ -45,7 +47,7 @@
 
 // NestBasedAccessDesugaring contains common code between the two subclasses
 // which are specialized for d8 and r8
-public class NestBasedAccessDesugaring {
+public class NestBasedAccessDesugaring implements CfInstructionDesugaring {
 
   // Short names to avoid creating long strings
   public static final String NEST_ACCESS_NAME_PREFIX = "-$$Nest$";
@@ -93,7 +95,6 @@
 
     Code code = method.getDefinition().getCode();
     if (code.isDexCode()) {
-      assert appView.testing().allowDexInputForTesting;
       return false;
     }
 
@@ -105,7 +106,8 @@
         code.asCfCode().getInstructions(), instruction -> needsDesugaring(instruction, method));
   }
 
-  private boolean needsDesugaring(CfInstruction instruction, ProgramMethod context) {
+  @Override
+  public boolean needsDesugaring(CfInstruction instruction, ProgramMethod context) {
     if (instruction.isFieldInstruction()) {
       return needsDesugaring(instruction.asFieldInstruction().getField(), context);
     }
@@ -166,6 +168,14 @@
     return false;
   }
 
+  @Override
+  public List<CfInstruction> desugarInstruction(
+      CfInstruction instruction,
+      CfInstructionDesugaringEventConsumer consumer,
+      ProgramMethod context) {
+    return desugarInstruction(instruction, context, null);
+  }
+
   public List<CfInstruction> desugarInstruction(
       CfInstruction instruction, ProgramMethod context, NestBridgeConsumer bridgeConsumer) {
     if (instruction.isFieldInstruction()) {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java b/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
index 3fa8be0..0713d06 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
@@ -9,6 +9,7 @@
 import static com.android.tools.r8.ir.analysis.type.Nullability.maybeNull;
 
 import com.android.tools.r8.algorithms.scc.SCC;
+import com.android.tools.r8.contexts.CompilationContext.MethodProcessingContext;
 import com.android.tools.r8.dex.Constants;
 import com.android.tools.r8.errors.CompilationError;
 import com.android.tools.r8.errors.Unreachable;
@@ -82,7 +83,6 @@
 import com.android.tools.r8.ir.code.ValueType;
 import com.android.tools.r8.ir.code.Xor;
 import com.android.tools.r8.ir.conversion.IRConverter;
-import com.android.tools.r8.ir.conversion.MethodProcessingId;
 import com.android.tools.r8.ir.conversion.MethodProcessor;
 import com.android.tools.r8.ir.optimize.UtilityMethodsForCodeOptimizations.UtilityMethodForCodeOptimizations;
 import com.android.tools.r8.ir.optimize.controlflow.SwitchCaseAnalyzer;
@@ -1371,7 +1371,7 @@
       IRCode code,
       ProgramMethod context,
       MethodProcessor methodProcessor,
-      MethodProcessingId methodProcessingId) {
+      MethodProcessingContext methodProcessingContext) {
     if (!appView.enableWholeProgramOptimizations()) {
       return;
     }
@@ -1422,7 +1422,7 @@
                 context,
                 affectedValues,
                 methodProcessor,
-                methodProcessingId);
+                methodProcessingContext);
         if (removeResult != RemoveCheckCastInstructionIfTrivialResult.NO_REMOVALS) {
           assert removeResult == RemoveCheckCastInstructionIfTrivialResult.REMOVED_CAST_DO_NARROW;
           needToRemoveTrivialPhis |= hasPhiUsers;
@@ -1461,7 +1461,7 @@
       ProgramMethod context,
       Set<Value> affectedValues,
       MethodProcessor methodProcessor,
-      MethodProcessingId methodProcessingId) {
+      MethodProcessingContext methodProcessingContext) {
     Value inValue = checkCast.object();
     Value outValue = checkCast.outValue();
     DexType castType = checkCast.getType();
@@ -1534,7 +1534,7 @@
       // Replace the check-cast instruction by throwClassCastExceptionIfNotNull().
       UtilityMethodForCodeOptimizations throwClassCastExceptionIfNotNullMethod =
           UtilityMethodsForCodeOptimizations.synthesizeThrowClassCastExceptionIfNotNullMethod(
-              appView, context, methodProcessingId);
+              appView, methodProcessingContext);
       throwClassCastExceptionIfNotNullMethod.optimize(methodProcessor);
       InvokeStatic replacement =
           InvokeStatic.builder()
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/Outliner.java b/src/main/java/com/android/tools/r8/ir/optimize/Outliner.java
index dbad5bd..8dc0dd3 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/Outliner.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/Outliner.java
@@ -7,6 +7,8 @@
 import static com.android.tools.r8.ir.analysis.type.Nullability.definitelyNotNull;
 import static com.android.tools.r8.ir.analysis.type.Nullability.maybeNull;
 
+import com.android.tools.r8.contexts.CompilationContext.MethodProcessingContext;
+import com.android.tools.r8.contexts.CompilationContext.ProcessorContext;
 import com.android.tools.r8.dex.Constants;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.AppView;
@@ -68,6 +70,7 @@
 import java.util.ArrayList;
 import java.util.Comparator;
 import java.util.HashMap;
+import java.util.IdentityHashMap;
 import java.util.List;
 import java.util.ListIterator;
 import java.util.Map;
@@ -1340,6 +1343,8 @@
   }
 
   public List<ProgramMethod> buildOutlineMethods() {
+    ProcessorContext outlineProcessorContext = appView.createProcessorContext();
+    Map<DexMethod, MethodProcessingContext> methodProcessingContexts = new IdentityHashMap<>();
     List<ProgramMethod> outlineMethods = new ArrayList<>();
     // By now the candidates are the actual selected outlines. Iterate the outlines in a
     // consistent order, to provide deterministic naming of the internal-synthetics.
@@ -1349,13 +1354,18 @@
     for (Outline outline : outlines) {
       List<ProgramMethod> sites = outlineSites.get(outline);
       assert !sites.isEmpty();
+      // The representative might be shared among multiple outlines.
       ProgramMethod representative = findDeterministicRepresentative(sites);
+      MethodProcessingContext methodProcessingContext =
+          methodProcessingContexts.computeIfAbsent(
+              representative.getReference(),
+              key -> outlineProcessorContext.createMethodProcessingContext(representative));
       ProgramMethod outlineMethod =
           appView
               .getSyntheticItems()
               .createMethod(
                   SyntheticKind.OUTLINE,
-                  representative,
+                  methodProcessingContext.createUniqueContext(),
                   appView.dexItemFactory(),
                   builder -> {
                     builder
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/ServiceLoaderRewriter.java b/src/main/java/com/android/tools/r8/ir/optimize/ServiceLoaderRewriter.java
index efc440b..6758593 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/ServiceLoaderRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/ServiceLoaderRewriter.java
@@ -4,6 +4,7 @@
 
 package com.android.tools.r8.ir.optimize;
 
+import com.android.tools.r8.contexts.CompilationContext.MethodProcessingContext;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexEncodedMethod;
@@ -20,7 +21,6 @@
 import com.android.tools.r8.ir.code.InvokeStatic;
 import com.android.tools.r8.ir.code.InvokeVirtual;
 import com.android.tools.r8.ir.code.Value;
-import com.android.tools.r8.ir.conversion.MethodProcessingId;
 import com.android.tools.r8.ir.desugar.ServiceLoaderSourceCode;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.synthesis.SyntheticNaming.SyntheticKind;
@@ -70,7 +70,7 @@
     return serviceLoadMethods;
   }
 
-  public void rewrite(IRCode code, MethodProcessingId methodProcessingId) {
+  public void rewrite(IRCode code, MethodProcessingContext methodProcessingContext) {
     DexItemFactory factory = appView.dexItemFactory();
     InstructionListIterator instructionIterator = code.instructionListIterator();
     // Create a map from service type to loader methods local to this context since two
@@ -170,7 +170,7 @@
               constClass.getValue(),
               service -> {
                 DexEncodedMethod addedMethod =
-                    createSynthesizedMethod(service, classes, methodProcessingId, code.context());
+                    createSynthesizedMethod(service, classes, methodProcessingContext);
                 if (appView.options().isGeneratingClassFiles()) {
                   addedMethod.upgradeClassFileVersion(code.method().getClassFileVersion());
                 }
@@ -185,15 +185,14 @@
   private DexEncodedMethod createSynthesizedMethod(
       DexType serviceType,
       List<DexClass> classes,
-      MethodProcessingId methodProcessingId,
-      ProgramMethod context) {
+      MethodProcessingContext methodProcessingContext) {
     DexProto proto = appView.dexItemFactory().createProto(appView.dexItemFactory().iteratorType);
     ProgramMethod method =
         appView
             .getSyntheticItems()
             .createMethod(
                 SyntheticKind.SERVICE_LOADER,
-                context,
+                methodProcessingContext.createUniqueContext(),
                 appView.dexItemFactory(),
                 builder ->
                     builder
@@ -202,8 +201,7 @@
                         .setCode(
                             m ->
                                 ServiceLoaderSourceCode.generate(
-                                    serviceType, classes, appView.dexItemFactory())),
-                methodProcessingId);
+                                    serviceType, classes, appView.dexItemFactory())));
     synchronized (serviceLoadMethods) {
       serviceLoadMethods.add(method);
     }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/UtilityMethodsForCodeOptimizations.java b/src/main/java/com/android/tools/r8/ir/optimize/UtilityMethodsForCodeOptimizations.java
index 9e360b4..61b0f30 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/UtilityMethodsForCodeOptimizations.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/UtilityMethodsForCodeOptimizations.java
@@ -5,6 +5,8 @@
 package com.android.tools.r8.ir.optimize;
 
 import com.android.tools.r8.cf.CfVersion;
+import com.android.tools.r8.contexts.CompilationContext.MethodProcessingContext;
+import com.android.tools.r8.contexts.CompilationContext.UniqueContext;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.CfCode;
 import com.android.tools.r8.graph.DexItemFactory;
@@ -12,7 +14,6 @@
 import com.android.tools.r8.graph.DexProto;
 import com.android.tools.r8.graph.MethodAccessFlags;
 import com.android.tools.r8.graph.ProgramMethod;
-import com.android.tools.r8.ir.conversion.MethodProcessingId;
 import com.android.tools.r8.ir.conversion.MethodProcessor;
 import com.android.tools.r8.ir.optimize.templates.CfUtilityMethodsForCodeOptimizations;
 import com.android.tools.r8.synthesis.SyntheticItems;
@@ -22,7 +23,7 @@
 public class UtilityMethodsForCodeOptimizations {
 
   public static UtilityMethodForCodeOptimizations synthesizeToStringIfNotNullMethod(
-      AppView<?> appView, ProgramMethod context, MethodProcessingId methodProcessingId) {
+      AppView<?> appView, MethodProcessingContext methodProcessingContext) {
     InternalOptions options = appView.options();
     DexItemFactory dexItemFactory = appView.dexItemFactory();
     DexProto proto = dexItemFactory.createProto(dexItemFactory.voidType, dexItemFactory.objectType);
@@ -30,15 +31,14 @@
     ProgramMethod syntheticMethod =
         syntheticItems.createMethod(
             SyntheticNaming.SyntheticKind.TO_STRING_IF_NOT_NULL,
-            context,
+            methodProcessingContext.createUniqueContext(),
             dexItemFactory,
             builder ->
                 builder
                     .setAccessFlags(MethodAccessFlags.createPublicStaticSynthetic())
                     .setClassFileVersion(CfVersion.V1_8)
                     .setCode(method -> getToStringIfNotNullCodeTemplate(method, options))
-                    .setProto(proto),
-            methodProcessingId);
+                    .setProto(proto));
     return new UtilityMethodForCodeOptimizations(syntheticMethod);
   }
 
@@ -49,15 +49,16 @@
   }
 
   public static UtilityMethodForCodeOptimizations synthesizeThrowClassCastExceptionIfNotNullMethod(
-      AppView<?> appView, ProgramMethod context, MethodProcessingId methodProcessingId) {
+      AppView<?> appView, MethodProcessingContext methodProcessingContext) {
     InternalOptions options = appView.options();
     DexItemFactory dexItemFactory = appView.dexItemFactory();
     DexProto proto = dexItemFactory.createProto(dexItemFactory.voidType, dexItemFactory.objectType);
     SyntheticItems syntheticItems = appView.getSyntheticItems();
+    UniqueContext positionContext = methodProcessingContext.createUniqueContext();
     ProgramMethod syntheticMethod =
         syntheticItems.createMethod(
             SyntheticNaming.SyntheticKind.THROW_CCE_IF_NOT_NULL,
-            context,
+            positionContext,
             dexItemFactory,
             builder ->
                 builder
@@ -65,8 +66,7 @@
                     .setClassFileVersion(CfVersion.V1_8)
                     .setCode(
                         method -> getThrowClassCastExceptionIfNotNullCodeTemplate(method, options))
-                    .setProto(proto),
-            methodProcessingId);
+                    .setProto(proto));
     return new UtilityMethodForCodeOptimizations(syntheticMethod);
   }
 
@@ -78,7 +78,7 @@
   }
 
   public static UtilityMethodForCodeOptimizations synthesizeThrowIncompatibleClassChangeErrorMethod(
-      AppView<?> appView, ProgramMethod context, MethodProcessingId methodProcessingId) {
+      AppView<?> appView, MethodProcessingContext methodProcessingContext) {
     InternalOptions options = appView.options();
     DexItemFactory dexItemFactory = appView.dexItemFactory();
     DexProto proto = dexItemFactory.createProto(dexItemFactory.icceType);
@@ -86,7 +86,7 @@
     ProgramMethod syntheticMethod =
         syntheticItems.createMethod(
             SyntheticNaming.SyntheticKind.THROW_ICCE,
-            context,
+            methodProcessingContext.createUniqueContext(),
             dexItemFactory,
             builder ->
                 builder
@@ -94,8 +94,7 @@
                     .setClassFileVersion(CfVersion.V1_8)
                     .setCode(
                         method -> getThrowIncompatibleClassChangeErrorCodeTemplate(method, options))
-                    .setProto(proto),
-            methodProcessingId);
+                    .setProto(proto));
     return new UtilityMethodForCodeOptimizations(syntheticMethod);
   }
 
@@ -107,7 +106,7 @@
   }
 
   public static UtilityMethodForCodeOptimizations synthesizeThrowNoSuchMethodErrorMethod(
-      AppView<?> appView, ProgramMethod context, MethodProcessingId methodProcessingId) {
+      AppView<?> appView, MethodProcessingContext methodProcessingContext) {
     InternalOptions options = appView.options();
     DexItemFactory dexItemFactory = appView.dexItemFactory();
     DexProto proto = dexItemFactory.createProto(dexItemFactory.noSuchMethodErrorType);
@@ -115,15 +114,14 @@
     ProgramMethod syntheticMethod =
         syntheticItems.createMethod(
             SyntheticNaming.SyntheticKind.THROW_NSME,
-            context,
+            methodProcessingContext.createUniqueContext(),
             dexItemFactory,
             builder ->
                 builder
                     .setAccessFlags(MethodAccessFlags.createPublicStaticSynthetic())
                     .setClassFileVersion(CfVersion.V1_8)
                     .setCode(method -> getThrowNoSuchMethodErrorCodeTemplate(method, options))
-                    .setProto(proto),
-            methodProcessingId);
+                    .setProto(proto));
     return new UtilityMethodForCodeOptimizations(syntheticMethod);
   }
 
@@ -148,7 +146,7 @@
     }
 
     public void optimize(MethodProcessor methodProcessor) {
-      methodProcessor.scheduleMethodForProcessingAfterCurrentWave(method);
+      methodProcessor.scheduleDesugaredMethodForProcessing(method);
       optimized = true;
     }
   }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInliner.java b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInliner.java
index 13865a0..78dc973 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInliner.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInliner.java
@@ -6,6 +6,7 @@
 
 import static com.android.tools.r8.graph.DexProgramClass.asProgramClassOrNull;
 
+import com.android.tools.r8.contexts.CompilationContext.MethodProcessingContext;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexEncodedMethod;
@@ -18,7 +19,6 @@
 import com.android.tools.r8.ir.code.Instruction;
 import com.android.tools.r8.ir.code.InstructionOrPhi;
 import com.android.tools.r8.ir.code.Value;
-import com.android.tools.r8.ir.conversion.MethodProcessingId;
 import com.android.tools.r8.ir.conversion.MethodProcessor;
 import com.android.tools.r8.ir.optimize.CodeRewriter;
 import com.android.tools.r8.ir.optimize.Inliner;
@@ -133,7 +133,7 @@
       IRCode code,
       OptimizationFeedback feedback,
       MethodProcessor methodProcessor,
-      MethodProcessingId methodProcessingId,
+      MethodProcessingContext methodProcessingContext,
       Inliner inliner,
       Supplier<InliningOracle> defaultOracle) {
 
@@ -253,7 +253,7 @@
       // have more information about the types of the arguments at the call site. This is
       // particularly important for bridge methods.
       codeRewriter.removeTrivialCheckCastAndInstanceOfInstructions(
-          code, method, methodProcessor, methodProcessingId);
+          code, method, methodProcessor, methodProcessingContext);
       // If a method was inlined we may be able to prune additional branches.
       codeRewriter.simplifyControlFlow(code);
       // If a method was inlined we may see more trivial computation/conversion of String.
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/library/LibraryMemberOptimizer.java b/src/main/java/com/android/tools/r8/ir/optimize/library/LibraryMemberOptimizer.java
index 01c4cf7..b7bc845 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/library/LibraryMemberOptimizer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/library/LibraryMemberOptimizer.java
@@ -4,6 +4,7 @@
 
 package com.android.tools.r8.ir.optimize.library;
 
+import com.android.tools.r8.contexts.CompilationContext.MethodProcessingContext;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexClassAndMethod;
 import com.android.tools.r8.graph.DexEncodedField;
@@ -17,7 +18,6 @@
 import com.android.tools.r8.ir.code.InvokeMethod;
 import com.android.tools.r8.ir.code.Value;
 import com.android.tools.r8.ir.conversion.CodeOptimization;
-import com.android.tools.r8.ir.conversion.MethodProcessingId;
 import com.android.tools.r8.ir.conversion.MethodProcessor;
 import com.android.tools.r8.ir.optimize.info.OptimizationFeedback;
 import com.google.common.collect.Sets;
@@ -112,7 +112,7 @@
       IRCode code,
       OptimizationFeedback feedback,
       MethodProcessor methodProcessor,
-      MethodProcessingId methodProcessingId) {
+      MethodProcessingContext methodProcessingContext) {
     Set<Value> affectedValues = Sets.newIdentityHashSet();
     InstructionListIterator instructionIterator = code.instructionListIterator();
     Map<LibraryMethodModelCollection<?>, LibraryMethodModelCollection.State> optimizationStates =
@@ -146,10 +146,15 @@
           optimizationStates.computeIfAbsent(
               optimizer,
               libraryMethodModelCollection ->
-                  libraryMethodModelCollection.createInitialState(
-                      methodProcessor, methodProcessingId));
+                  libraryMethodModelCollection.createInitialState(methodProcessor));
       optimizer.optimize(
-          code, instructionIterator, invoke, singleTarget, affectedValues, optimizationState);
+          code,
+          instructionIterator,
+          invoke,
+          singleTarget,
+          affectedValues,
+          optimizationState,
+          methodProcessingContext);
     }
     if (!affectedValues.isEmpty()) {
       new TypeAnalysis(appView).narrowing(affectedValues);
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/library/LibraryMethodModelCollection.java b/src/main/java/com/android/tools/r8/ir/optimize/library/LibraryMethodModelCollection.java
index 5cb30b3..da38111 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/library/LibraryMethodModelCollection.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/library/LibraryMethodModelCollection.java
@@ -4,13 +4,13 @@
 
 package com.android.tools.r8.ir.optimize.library;
 
+import com.android.tools.r8.contexts.CompilationContext.MethodProcessingContext;
 import com.android.tools.r8.graph.DexClassAndMethod;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.InstructionListIterator;
 import com.android.tools.r8.ir.code.InvokeMethod;
 import com.android.tools.r8.ir.code.Value;
-import com.android.tools.r8.ir.conversion.MethodProcessingId;
 import com.android.tools.r8.ir.conversion.MethodProcessor;
 import com.android.tools.r8.ir.optimize.library.LibraryMethodModelCollection.State;
 import java.util.Set;
@@ -18,8 +18,7 @@
 /** Used to model the behavior of library methods for optimization purposes. */
 public interface LibraryMethodModelCollection<T extends State> {
 
-  default T createInitialState(
-      MethodProcessor methodProcessor, MethodProcessingId methodProcessingId) {
+  default T createInitialState(MethodProcessor methodProcessor) {
     return null;
   }
 
@@ -39,7 +38,8 @@
       InvokeMethod invoke,
       DexClassAndMethod singleTarget,
       Set<Value> affectedValues,
-      T state);
+      T state,
+      MethodProcessingContext methodProcessingContext);
 
   @SuppressWarnings("unchecked")
   default void optimize(
@@ -48,8 +48,16 @@
       InvokeMethod invoke,
       DexClassAndMethod singleTarget,
       Set<Value> affectedValues,
-      Object state) {
-    optimize(code, instructionIterator, invoke, singleTarget, affectedValues, (T) state);
+      Object state,
+      MethodProcessingContext methodProcessingContext) {
+    optimize(
+        code,
+        instructionIterator,
+        invoke,
+        singleTarget,
+        affectedValues,
+        (T) state,
+        methodProcessingContext);
   }
 
   /** Thread local optimization state to allow caching, etc. */
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/library/StatelessLibraryMethodModelCollection.java b/src/main/java/com/android/tools/r8/ir/optimize/library/StatelessLibraryMethodModelCollection.java
index 8b36204..df15197 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/library/StatelessLibraryMethodModelCollection.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/library/StatelessLibraryMethodModelCollection.java
@@ -4,12 +4,12 @@
 
 package com.android.tools.r8.ir.optimize.library;
 
+import com.android.tools.r8.contexts.CompilationContext.MethodProcessingContext;
 import com.android.tools.r8.graph.DexClassAndMethod;
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.InstructionListIterator;
 import com.android.tools.r8.ir.code.InvokeMethod;
 import com.android.tools.r8.ir.code.Value;
-import com.android.tools.r8.ir.conversion.MethodProcessingId;
 import com.android.tools.r8.ir.conversion.MethodProcessor;
 import com.android.tools.r8.ir.optimize.library.StatelessLibraryMethodModelCollection.State;
 import java.util.Set;
@@ -18,8 +18,7 @@
     implements LibraryMethodModelCollection<State> {
 
   @Override
-  public final State createInitialState(
-      MethodProcessor methodProcessor, MethodProcessingId methodProcessingId) {
+  public final State createInitialState(MethodProcessor methodProcessor) {
     return null;
   }
 
@@ -37,7 +36,8 @@
       InvokeMethod invoke,
       DexClassAndMethod singleTarget,
       Set<Value> affectedValues,
-      State state) {
+      State state,
+      MethodProcessingContext methodProcessingContext) {
     assert state == null;
     optimize(code, instructionIterator, invoke, singleTarget, affectedValues);
   }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/library/StringBuilderMethodOptimizer.java b/src/main/java/com/android/tools/r8/ir/optimize/library/StringBuilderMethodOptimizer.java
index d75e060..07ab344 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/library/StringBuilderMethodOptimizer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/library/StringBuilderMethodOptimizer.java
@@ -11,6 +11,7 @@
 import static com.android.tools.r8.ir.code.Opcodes.INVOKE_VIRTUAL;
 import static com.android.tools.r8.ir.code.Opcodes.NEW_INSTANCE;
 
+import com.android.tools.r8.contexts.CompilationContext.MethodProcessingContext;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexClassAndMethod;
 import com.android.tools.r8.graph.DexItemFactory;
@@ -26,7 +27,6 @@
 import com.android.tools.r8.ir.code.InvokeStatic;
 import com.android.tools.r8.ir.code.InvokeVirtual;
 import com.android.tools.r8.ir.code.Value;
-import com.android.tools.r8.ir.conversion.MethodProcessingId;
 import com.android.tools.r8.ir.conversion.MethodProcessor;
 import com.android.tools.r8.ir.optimize.UtilityMethodsForCodeOptimizations;
 import com.android.tools.r8.ir.optimize.UtilityMethodsForCodeOptimizations.UtilityMethodForCodeOptimizations;
@@ -55,9 +55,8 @@
   }
 
   @Override
-  public State createInitialState(
-      MethodProcessor methodProcessor, MethodProcessingId methodProcessingId) {
-    return new State(methodProcessor, methodProcessingId);
+  public State createInitialState(MethodProcessor methodProcessor) {
+    return new State(methodProcessor);
   }
 
   @Override
@@ -72,11 +71,18 @@
       InvokeMethod invoke,
       DexClassAndMethod singleTarget,
       Set<Value> affectedValues,
-      State state) {
+      State state,
+      MethodProcessingContext methodProcessingContext) {
     if (invoke.isInvokeMethodWithReceiver()) {
       InvokeMethodWithReceiver invokeWithReceiver = invoke.asInvokeMethodWithReceiver();
       if (stringBuilderMethods.isAppendMethod(singleTarget.getReference())) {
-        optimizeAppend(code, instructionIterator, invokeWithReceiver, singleTarget, state);
+        optimizeAppend(
+            code,
+            instructionIterator,
+            invokeWithReceiver,
+            singleTarget,
+            state,
+            methodProcessingContext);
       } else if (singleTarget.getReference() == dexItemFactory.stringBuilderMethods.toString) {
         optimizeToString(instructionIterator, invokeWithReceiver);
       }
@@ -88,7 +94,8 @@
       InstructionListIterator instructionIterator,
       InvokeMethodWithReceiver invoke,
       DexClassAndMethod singleTarget,
-      State state) {
+      State state,
+      MethodProcessingContext methodProcessingContext) {
     if (!state.isUnusedBuilder(invoke.getReceiver())) {
       return;
     }
@@ -121,7 +128,7 @@
         // Replace the instruction by toStringIfNotNull().
         UtilityMethodForCodeOptimizations toStringIfNotNullMethod =
             UtilityMethodsForCodeOptimizations.synthesizeToStringIfNotNullMethod(
-                appView, code.context(), state.methodProcessingId);
+                appView, methodProcessingContext);
         toStringIfNotNullMethod.optimize(state.methodProcessor);
         InvokeStatic replacement =
             InvokeStatic.builder()
@@ -146,13 +153,11 @@
   class State implements LibraryMethodModelCollection.State {
 
     final MethodProcessor methodProcessor;
-    final MethodProcessingId methodProcessingId;
 
     final Reference2BooleanMap<Value> unusedBuilders = new Reference2BooleanOpenHashMap<>();
 
-    State(MethodProcessor methodProcessor, MethodProcessingId methodProcessingId) {
+    State(MethodProcessor methodProcessor) {
       this.methodProcessor = methodProcessor;
-      this.methodProcessingId = methodProcessingId;
     }
 
     boolean isUnusedBuilder(Value value) {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/staticizer/StaticizingProcessor.java b/src/main/java/com/android/tools/r8/ir/optimize/staticizer/StaticizingProcessor.java
index bf9f986..5f54ab6 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/staticizer/StaticizingProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/staticizer/StaticizingProcessor.java
@@ -33,7 +33,6 @@
 import com.android.tools.r8.ir.code.StaticPut;
 import com.android.tools.r8.ir.code.Value;
 import com.android.tools.r8.ir.conversion.IRConverter;
-import com.android.tools.r8.ir.conversion.MethodProcessingId;
 import com.android.tools.r8.ir.conversion.MethodProcessor;
 import com.android.tools.r8.ir.conversion.OneTimeMethodProcessor;
 import com.android.tools.r8.ir.optimize.AssumeInserter;
@@ -365,13 +364,12 @@
     OneTimeMethodProcessor methodProcessor =
         OneTimeMethodProcessor.create(methodsToReprocess, appView);
     methodProcessor.forEachWaveWithExtension(
-        (method, methodProcessingId) ->
+        (method, methodProcessingContext) ->
             forEachMethod(
                 method,
                 processingQueue.get(method.getDefinition()).build(),
                 feedback,
-                methodProcessor,
-                methodProcessingId),
+                methodProcessor),
         executorService);
     // TODO(b/140767158): No need to clear if we can do every thing in one go.
     methodsToReprocess.clear();
@@ -383,8 +381,7 @@
       ProgramMethod method,
       Collection<BiConsumer<IRCode, MethodProcessor>> codeOptimizations,
       OptimizationFeedback feedback,
-      OneTimeMethodProcessor methodProcessor,
-      MethodProcessingId methodProcessingId) {
+      OneTimeMethodProcessor methodProcessor) {
     IRCode code = method.buildIR(appView);
     codeOptimizations.forEach(codeOptimization -> codeOptimization.accept(code, methodProcessor));
     CodeRewriter.removeAssumeInstructions(appView, code);
diff --git a/src/main/java/com/android/tools/r8/ir/synthetic/ForwardMethodBuilder.java b/src/main/java/com/android/tools/r8/ir/synthetic/ForwardMethodBuilder.java
index 794fd3e..0b340af 100644
--- a/src/main/java/com/android/tools/r8/ir/synthetic/ForwardMethodBuilder.java
+++ b/src/main/java/com/android/tools/r8/ir/synthetic/ForwardMethodBuilder.java
@@ -3,6 +3,8 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.ir.synthetic;
 
+import static com.android.tools.r8.utils.ConsumerUtils.emptyConsumer;
+
 import com.android.tools.r8.cf.code.CfCheckCast;
 import com.android.tools.r8.cf.code.CfInstruction;
 import com.android.tools.r8.cf.code.CfInvoke;
@@ -61,6 +63,23 @@
     return this;
   }
 
+  public ForwardMethodBuilder applyIf(
+      boolean condition, Consumer<ForwardMethodBuilder> thenConsumer) {
+    return applyIf(condition, thenConsumer, emptyConsumer());
+  }
+
+  public ForwardMethodBuilder applyIf(
+      boolean condition,
+      Consumer<ForwardMethodBuilder> thenConsumer,
+      Consumer<ForwardMethodBuilder> elseConsumer) {
+    if (condition) {
+      thenConsumer.accept(this);
+    } else {
+      elseConsumer.accept(this);
+    }
+    return this;
+  }
+
   public ForwardMethodBuilder setNonStaticSource(DexMethod method) {
     sourceMethod = method;
     staticSource = false;
@@ -87,6 +106,13 @@
     return this;
   }
 
+  public ForwardMethodBuilder setSuperTarget(DexMethod method, boolean isInterface) {
+    targetMethod = method;
+    invokeType = InvokeType.SPECIAL;
+    this.isInterface = isInterface;
+    return this;
+  }
+
   public ForwardMethodBuilder setVirtualTarget(DexMethod method, boolean isInterface) {
     targetMethod = method;
     invokeType = InvokeType.VIRTUAL;
diff --git a/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java b/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java
index cf6a117..1c6adbb 100644
--- a/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java
+++ b/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java
@@ -213,6 +213,18 @@
           : "A nest host cannot also be a nest member.";
     }
 
+    if (clazz.isRecord()) {
+      // TODO(b/169645628): Strip record components if not kept.
+      for (DexEncodedField instanceField : clazz.instanceFields()) {
+        String componentName = namingLens.lookupName(instanceField.field).toString();
+        String componentDescriptor =
+            namingLens.lookupDescriptor(instanceField.field.type).toString();
+        String componentSignature =
+            instanceField.getGenericSignature().toRenamedString(namingLens, isTypeMissing);
+        writer.visitRecordComponent(componentName, componentDescriptor, componentSignature);
+      }
+    }
+
     for (InnerClassAttribute entry : clazz.getInnerClasses()) {
       entry.write(writer, namingLens, options);
     }
diff --git a/src/main/java/com/android/tools/r8/naming/ProguardMapReader.java b/src/main/java/com/android/tools/r8/naming/ProguardMapReader.java
index 7ddb042..0f94efa 100644
--- a/src/main/java/com/android/tools/r8/naming/ProguardMapReader.java
+++ b/src/main/java/com/android/tools/r8/naming/ProguardMapReader.java
@@ -222,6 +222,14 @@
   private void parseClassMappings(ProguardMap.Builder mapBuilder) throws IOException {
     while (hasLine()) {
       skipWhitespace();
+      if (isCommentLineWithJsonBrace()) {
+        // TODO(b/179665169): Parse the mapping information without doing anything with it, since we
+        //  at this point do not have a global context.
+        MappingInformation.fromJsonObject(parseJsonInComment(), diagnosticsHandler, lineNo);
+        // Skip reading the rest of the line.
+        lineOffset = line.length();
+        nextLine();
+      }
       String before = parseType(false);
       skipWhitespace();
       // Workaround for proguard map files that contain entries for package-info.java files.
@@ -528,7 +536,7 @@
     }
   }
 
-  private class ParseException extends RuntimeException {
+  public class ParseException extends RuntimeException {
 
     private final int lineNo;
     private final int lineOffset;
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 9161f4f..e8fed84 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -15,13 +15,13 @@
 import com.android.tools.r8.cf.code.CfInstruction;
 import com.android.tools.r8.cf.code.CfInvoke;
 import com.android.tools.r8.code.CfOrDexInstruction;
+import com.android.tools.r8.contexts.CompilationContext.MethodProcessingContext;
+import com.android.tools.r8.contexts.CompilationContext.ProcessorContext;
 import com.android.tools.r8.dex.IndexedItemCollection;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.experimental.graphinfo.GraphConsumer;
 import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
 import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.CfCode;
-import com.android.tools.r8.graph.Code;
 import com.android.tools.r8.graph.DexAnnotation;
 import com.android.tools.r8.graph.DexAnnotationSet;
 import com.android.tools.r8.graph.DexApplication;
@@ -87,12 +87,15 @@
 import com.android.tools.r8.ir.code.InvokeVirtual;
 import com.android.tools.r8.ir.code.Value;
 import com.android.tools.r8.ir.desugar.BackportedMethodRewriter;
+import com.android.tools.r8.ir.desugar.CfInstructionDesugaringCollection;
+import com.android.tools.r8.ir.desugar.CfInstructionDesugaringEventConsumer;
+import com.android.tools.r8.ir.desugar.CfInstructionDesugaringEventConsumer.R8CfInstructionDesugaringEventConsumer;
 import com.android.tools.r8.ir.desugar.DesugaredLibraryAPIConverter;
 import com.android.tools.r8.ir.desugar.LambdaClass;
 import com.android.tools.r8.ir.desugar.LambdaDescriptor;
 import com.android.tools.r8.ir.desugar.LambdaRewriter;
+import com.android.tools.r8.ir.desugar.NonEmptyCfInstructionDesugaringCollection;
 import com.android.tools.r8.ir.desugar.TwrCloseResourceRewriter;
-import com.android.tools.r8.ir.desugar.nest.NestBasedAccessDesugaring;
 import com.android.tools.r8.kotlin.KotlinMetadataEnqueuerExtension;
 import com.android.tools.r8.logging.Log;
 import com.android.tools.r8.naming.identifiernamestring.IdentifierNameStringLookupResult;
@@ -113,7 +116,6 @@
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.InternalOptions.DesugarState;
 import com.android.tools.r8.utils.IteratorUtils;
-import com.android.tools.r8.utils.ListUtils;
 import com.android.tools.r8.utils.MethodSignatureEquivalence;
 import com.android.tools.r8.utils.OptionalBool;
 import com.android.tools.r8.utils.Pair;
@@ -126,7 +128,6 @@
 import com.android.tools.r8.utils.collections.ProgramFieldSet;
 import com.android.tools.r8.utils.collections.ProgramMethodMap;
 import com.android.tools.r8.utils.collections.ProgramMethodSet;
-import com.android.tools.r8.utils.collections.SortedProgramMethodSet;
 import com.google.common.base.Equivalence.Wrapper;
 import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Iterables;
@@ -384,19 +385,28 @@
 
   private final GraphReporter graphReporter;
 
+  private static final class LambdaInfo {
+    final ProgramMethod context;
+    final DexCallSite callsite;
+    final LambdaDescriptor descriptor;
+
+    public LambdaInfo(ProgramMethod context, DexCallSite callsite, LambdaDescriptor descriptor) {
+      this.context = context;
+      this.callsite = callsite;
+      this.descriptor = descriptor;
+    }
+  }
+
+  private final CfInstructionDesugaringCollection desugaring;
   private final LambdaRewriter lambdaRewriter;
+  private final List<LambdaInfo> lambdasForDesugaring = new ArrayList<>();
+
   private final BackportedMethodRewriter backportRewriter;
-  private final NestBasedAccessDesugaring nestBasedAccessRewriter;
   private final TwrCloseResourceRewriter twrCloseResourceRewriter;
 
   private final DesugaredLibraryConversionWrapperAnalysis desugaredLibraryWrapperAnalysis;
-  private final Map<DexType, Pair<LambdaClass, ProgramMethod>> lambdaClasses =
-      new IdentityHashMap<>();
-  private final ProgramMethodMap<Map<DexCallSite, LambdaClass>> lambdaCallSites =
-      ProgramMethodMap.create();
   private final Map<DexMethod, ProgramMethod> methodsWithBackports = new IdentityHashMap<>();
   private final Map<DexMethod, ProgramMethod> methodsWithTwrCloseResource = new IdentityHashMap<>();
-  private final Set<DexProgramClass> classesWithSerializableLambdas = Sets.newIdentityHashSet();
   private final ProgramMethodSet pendingDesugaring = ProgramMethodSet.create();
 
   Enqueuer(
@@ -439,9 +449,11 @@
     failedFieldResolutionTargets = SetUtils.newIdentityHashSet(0);
     liveMethods = new LiveMethodsSet(graphReporter::registerMethod);
     liveFields = new LiveFieldsSet(graphReporter::registerField);
+    desugaring =
+        mode.isInitialTreeShaking()
+            ? new NonEmptyCfInstructionDesugaringCollection(appView)
+            : CfInstructionDesugaringCollection.empty();
     lambdaRewriter = options.desugarState == DesugarState.ON ? new LambdaRewriter(appView) : null;
-    nestBasedAccessRewriter =
-        options.shouldDesugarNests() ? new NestBasedAccessDesugaring(appView) : null;
     backportRewriter =
         options.desugarState == DesugarState.ON ? new BackportedMethodRewriter(appView) : null;
     twrCloseResourceRewriter =
@@ -968,19 +980,10 @@
       return;
     }
 
-    DexEncodedMethod contextMethod = context.getDefinition();
     if (lambdaRewriter != null) {
-      assert contextMethod.getCode().isCfCode() : "Unexpected input type with lambdas";
-      CfCode code = contextMethod.getCode().asCfCode();
-      if (code != null) {
-        LambdaClass lambdaClass = lambdaRewriter.createLambdaClass(descriptor, context);
-        lambdaClasses.put(lambdaClass.type, new Pair<>(lambdaClass, context));
-        lambdaCallSites
-            .computeIfAbsent(context, k -> new IdentityHashMap<>())
-            .put(callSite, lambdaClass);
-        if (lambdaClass.descriptor.interfaces.contains(appView.dexItemFactory().serializableType)) {
-          classesWithSerializableLambdas.add(context.getHolder());
-        }
+      assert context.getDefinition().getCode().isCfCode() : "Unexpected input type with lambdas";
+      if (context.getDefinition().getCode().isCfCode()) {
+        lambdasForDesugaring.add(new LambdaInfo(context, callSite, descriptor));
       }
     } else {
       markLambdaAsInstantiated(descriptor, context);
@@ -3066,8 +3069,6 @@
       registerAnalysis(new GenericSignatureEnqueuerAnalysis(enqueuerDefinitionSupplier));
     }
     if (mode.isInitialTreeShaking()) {
-      registerInvokeAnalysis(
-          appView.getInvokeSpecialBridgeSynthesizer().getEnqueuerInvokeAnalysis());
       // This is simulating the effect of the "root set" applied rules.
       // This is done only in the initial pass, in subsequent passes the "rules" are reapplied
       // by iterating the instances.
@@ -3167,6 +3168,10 @@
 
   private static class SyntheticAdditions {
 
+    private final ProcessorContext processorContext;
+    private Map<DexMethod, MethodProcessingContext> methodProcessingContexts =
+        new IdentityHashMap<>();
+
     List<ProgramMethod> desugaredMethods = new LinkedList<>();
 
     Map<DexType, Pair<DexProgramClass, ProgramMethod>> syntheticInstantiations =
@@ -3186,6 +3191,15 @@
     // Subset of synthesized classes that need to be added to the main-dex file.
     Set<DexProgramClass> mainDexTypes = Sets.newIdentityHashSet();
 
+    SyntheticAdditions(ProcessorContext processorContext) {
+      this.processorContext = processorContext;
+    }
+
+    MethodProcessingContext getMethodContext(ProgramMethod method) {
+      return methodProcessingContexts.computeIfAbsent(
+          method.getReference(), k -> processorContext.createMethodProcessingContext(method));
+    }
+
     boolean isEmpty() {
       boolean empty =
           desugaredMethods.isEmpty()
@@ -3289,9 +3303,8 @@
     // First part of synthesis is to create and register all reachable synthetic additions.
     // In particular these additions are order independent, i.e., it does not matter which are
     // registered first and no dependencies may exist among them.
-    SyntheticAdditions additions = new SyntheticAdditions();
+    SyntheticAdditions additions = new SyntheticAdditions(appView.createProcessorContext());
     desugar(additions);
-    synthesizeInvokeSpecialBridges(additions);
     synthesizeInterfaceMethodBridges(additions);
     synthesizeLambdas(additions);
     synthesizeLibraryConversionWrappers(additions);
@@ -3319,55 +3332,17 @@
   }
 
   private void desugar(SyntheticAdditions additions) throws ExecutionException {
-    ThreadUtils.processItems(pendingDesugaring, this::desugar, executorService);
+    R8CfInstructionDesugaringEventConsumer desugaringEventConsumer =
+        CfInstructionDesugaringEventConsumer.createForR8();
+    ThreadUtils.processItems(
+        pendingDesugaring,
+        method -> desugaring.desugar(method, desugaringEventConsumer),
+        executorService);
+    desugaringEventConsumer.finalizeDesugaring(appView);
     Iterables.addAll(additions.desugaredMethods, pendingDesugaring);
     pendingDesugaring.clear();
   }
 
-  private void desugar(ProgramMethod method) {
-    Code code = method.getDefinition().getCode();
-    if (!code.isCfCode()) {
-      appView
-          .options()
-          .reporter
-          .error(
-              new StringDiagnostic(
-                  "Unsupported attempt to desugar non-CF code",
-                  method.getOrigin(),
-                  method.getPosition()));
-      return;
-    }
-
-    CfCode cfCode = code.asCfCode();
-    List<CfInstruction> desugaredInstructions =
-        ListUtils.flatMap(
-            cfCode.getInstructions(),
-            instruction -> {
-              // TODO(b/177810578): Migrate other cf-to-cf based desugaring here, and assert that
-              //  that at most one instruction desugarer applies to each instruction.
-              if (nestBasedAccessRewriter != null) {
-                List<CfInstruction> replacement =
-                    nestBasedAccessRewriter.desugarInstruction(instruction, method, null);
-                if (replacement != null) {
-                  return replacement;
-                }
-              }
-              return null;
-            },
-            null);
-    if (desugaredInstructions != null) {
-      cfCode.setInstructions(desugaredInstructions);
-    } else {
-      assert false : "Expected code to be desugared";
-    }
-  }
-
-  private void synthesizeInvokeSpecialBridges(SyntheticAdditions additions) {
-    SortedProgramMethodSet bridges =
-        appView.getInvokeSpecialBridgeSynthesizer().insertBridgesForR8();
-    bridges.forEach(additions::addLiveMethod);
-  }
-
   private void synthesizeInterfaceMethodBridges(SyntheticAdditions additions) {
     for (ProgramMethod bridge : syntheticInterfaceMethodBridges.values()) {
       DexProgramClass holder = bridge.getHolder();
@@ -3380,26 +3355,31 @@
 
   private void synthesizeBackports(SyntheticAdditions additions) {
     for (ProgramMethod method : methodsWithBackports.values()) {
-      backportRewriter.desugar(method, appInfo, additions::addLiveMethod);
+      backportRewriter.desugar(
+          method, appInfo, additions.getMethodContext(method), additions::addLiveMethod);
     }
   }
 
   private void synthesizeTwrCloseResource(SyntheticAdditions additions) {
     for (ProgramMethod method : methodsWithTwrCloseResource.values()) {
-      twrCloseResourceRewriter.rewriteCf(method, additions::addLiveMethod);
+      twrCloseResourceRewriter.rewriteCf(
+          method, additions::addLiveMethod, additions.getMethodContext(method));
     }
   }
 
   private void synthesizeLambdas(SyntheticAdditions additions) {
-    if (lambdaRewriter == null || lambdaClasses.isEmpty()) {
-      assert lambdaCallSites.isEmpty();
-      assert classesWithSerializableLambdas.isEmpty();
+    if (lambdasForDesugaring.isEmpty()) {
       return;
     }
-    for (Pair<LambdaClass, ProgramMethod> lambdaClassAndContext : lambdaClasses.values()) {
+    assert lambdaRewriter != null;
+    ProgramMethodMap<Map<DexCallSite, LambdaClass>> lambdaCallSites = ProgramMethodMap.create();
+    Set<DexProgramClass> classesWithSerializableLambdas = Sets.newIdentityHashSet();
+    for (LambdaInfo lambdaInfo : lambdasForDesugaring) {
       // Add all desugared classes to the application, main-dex list, and mark them instantiated.
-      LambdaClass lambdaClass = lambdaClassAndContext.getFirst();
-      ProgramMethod context = lambdaClassAndContext.getSecond();
+      ProgramMethod context = lambdaInfo.context;
+      LambdaClass lambdaClass =
+          lambdaRewriter.createLambdaClass(
+              lambdaInfo.descriptor, context, additions.getMethodContext(context));
       DexProgramClass programClass = lambdaClass.getLambdaProgramClass();
       additions.addInstantiatedClass(programClass, context);
       // Mark the instance constructor targeted and live.
@@ -3408,6 +3388,14 @@
       ProgramMethod method = new ProgramMethod(programClass, constructor);
       markMethodAsTargeted(method, reason);
       markDirectStaticOrConstructorMethodAsLive(method, reason);
+      // Populate method -> info mapping for method rewriting.
+      lambdaCallSites
+          .computeIfAbsent(context, k -> new IdentityHashMap<>())
+          .put(lambdaInfo.callsite, lambdaClass);
+      // Populate set of types with serialized lambda method for removal.
+      if (lambdaInfo.descriptor.interfaces.contains(appView.dexItemFactory().serializableType)) {
+        classesWithSerializableLambdas.add(context.getHolder());
+      }
     }
 
     // Rewrite all of the invoke-dynamic instructions to lambda class instantiations.
@@ -3420,9 +3408,7 @@
     }
 
     // Clear state before next fixed point iteration.
-    lambdaClasses.clear();
-    lambdaCallSites.clear();
-    classesWithSerializableLambdas.clear();
+    lambdasForDesugaring.clear();
   }
 
   private void finalizeLibraryMethodOverrideInformation() {
@@ -4054,11 +4040,9 @@
   }
 
   private void traceNonDesugaredCode(ProgramMethod method) {
-    if (getMode().isInitialTreeShaking()) {
-      if (nestBasedAccessRewriter != null && nestBasedAccessRewriter.needsDesugaring(method)) {
-        pendingDesugaring.add(method);
-        return;
-      }
+    if (getMode().isInitialTreeShaking() && desugaring.needsDesugaring(method)) {
+      pendingDesugaring.add(method);
+      return;
     }
 
     traceCode(method);
diff --git a/src/main/java/com/android/tools/r8/synthesis/SyntheticItems.java b/src/main/java/com/android/tools/r8/synthesis/SyntheticItems.java
index 14f9cf8..6c07af2 100644
--- a/src/main/java/com/android/tools/r8/synthesis/SyntheticItems.java
+++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticItems.java
@@ -3,6 +3,7 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.synthesis;
 
+import com.android.tools.r8.contexts.CompilationContext.UniqueContext;
 import com.android.tools.r8.errors.InternalCompilerError;
 import com.android.tools.r8.graph.AppInfo;
 import com.android.tools.r8.graph.AppView;
@@ -16,7 +17,6 @@
 import com.android.tools.r8.graph.ProgramDefinition;
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.graph.PrunedItems;
-import com.android.tools.r8.ir.conversion.MethodProcessingId;
 import com.android.tools.r8.synthesis.SyntheticFinalization.Result;
 import com.android.tools.r8.synthesis.SyntheticNaming.SyntheticKind;
 import com.google.common.collect.ImmutableList;
@@ -276,15 +276,15 @@
 
   public DexProgramClass createClass(
       SyntheticKind kind,
-      DexProgramClass context,
+      UniqueContext context,
       DexItemFactory factory,
-      Supplier<String> syntheticIdSupplier,
       Consumer<SyntheticProgramClassBuilder> fn) {
     // Obtain the outer synthesizing context in the case the context itself is synthetic.
     // This is to ensure a flat input-type -> synthetic-item mapping.
-    SynthesizingContext outerContext = getSynthesizingContext(context);
+    SynthesizingContext outerContext = getSynthesizingContext(context.getClassContext());
     DexType type =
-        SyntheticNaming.createInternalType(kind, outerContext, syntheticIdSupplier.get(), factory);
+        SyntheticNaming.createInternalType(
+            kind, outerContext, context.getSyntheticSuffix(), factory);
     SyntheticProgramClassBuilder classBuilder =
         new SyntheticProgramClassBuilder(type, outerContext, factory);
     fn.accept(classBuilder);
@@ -305,6 +305,7 @@
     return clazz;
   }
 
+  // TODO(b/172194101): Make this take a unique context.
   public DexProgramClass createFixedClass(
       SyntheticKind kind,
       DexProgramClass context,
@@ -338,31 +339,13 @@
   /** Create a single synthetic method item. */
   public ProgramMethod createMethod(
       SyntheticKind kind,
-      ProgramDefinition context,
+      UniqueContext context,
       DexItemFactory factory,
       Consumer<SyntheticMethodBuilder> fn) {
-    return createMethod(kind, context, factory, fn, this::getNextSyntheticId);
+    return createMethod(kind, context.getClassContext(), factory, fn, context::getSyntheticSuffix);
   }
 
-  // TODO(b/172194101): Remove this once the uniqueness is a property of the context.
-  public ProgramMethod createMethod(
-      SyntheticKind kind,
-      ProgramDefinition context,
-      DexItemFactory factory,
-      Consumer<SyntheticMethodBuilder> fn,
-      MethodProcessingId methodProcessingId) {
-    return createMethod(
-        kind,
-        context,
-        factory,
-        fn,
-        methodProcessingId != null
-            ? methodProcessingId::getFullyQualifiedIdAndIncrement
-            : this::getNextSyntheticId);
-  }
-
-  // TODO(b/172194101): Remove/private this once the uniqueness is a property of the context.
-  public ProgramMethod createMethod(
+  private ProgramMethod createMethod(
       SyntheticKind kind,
       ProgramDefinition context,
       DexItemFactory factory,
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 118a5b6..afbafe0 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -44,7 +44,6 @@
 import com.android.tools.r8.horizontalclassmerging.HorizontallyMergedClasses;
 import com.android.tools.r8.inspector.internal.InspectorImpl;
 import com.android.tools.r8.ir.code.IRCode;
-import com.android.tools.r8.ir.conversion.MethodProcessingId;
 import com.android.tools.r8.ir.desugar.DesugaredLibraryConfiguration;
 import com.android.tools.r8.ir.desugar.nest.Nest;
 import com.android.tools.r8.ir.optimize.Inliner;
@@ -101,7 +100,15 @@
 
   public enum DesugarState {
     OFF,
-    ON
+    ON;
+
+    public boolean isOff() {
+      return this == OFF;
+    }
+
+    public boolean isOn() {
+      return this == ON;
+    }
   }
 
   public static final CfVersion SUPPORTED_CF_VERSION = CfVersion.V15;
@@ -479,10 +486,7 @@
   }
 
   public boolean shouldDesugarNests() {
-    if (testing.enableForceNestBasedAccessDesugaringForTest) {
-      return true;
-    }
-    return !canUseNestBasedAccess();
+    return testing.enableForceNestBasedAccessDesugaringForTest || !canUseNestBasedAccess();
   }
 
   public boolean canUseRecords() {
@@ -1222,6 +1226,10 @@
       options.testing.enableExperimentalMissingClassesReporting = true;
     }
 
+    public static void allowExperimentClassFileVersion(InternalOptions options) {
+      options.reportedExperimentClassFileVersion.set(true);
+    }
+
     public static int NO_LIMIT = -1;
 
     // Force writing the specified bytes as the DEX version content.
@@ -1234,7 +1242,7 @@
 
     public BiConsumer<AppInfoWithLiveness, Enqueuer.Mode> enqueuerInspector = null;
 
-    public BiConsumer<ProgramMethod, MethodProcessingId> methodProcessingIdConsumer = null;
+    public Consumer<String> processingContextsConsumer = null;
 
     public Function<AppView<AppInfoWithLiveness>, RepackagingConfiguration>
         repackagingConfigurationFactory =
@@ -1269,7 +1277,6 @@
     public boolean addCallEdgesForLibraryInvokes = false;
 
     public boolean allowCheckDiscardedErrors = false;
-    public boolean allowDexInputForTesting = false;
     public boolean allowInjectedAnnotationMethods = false;
     public boolean allowTypeErrors =
         !Version.isDevelopmentVersion()
diff --git a/src/main/java/com/android/tools/r8/utils/ListUtils.java b/src/main/java/com/android/tools/r8/utils/ListUtils.java
index e6b0085..a6f800b 100644
--- a/src/main/java/com/android/tools/r8/utils/ListUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/ListUtils.java
@@ -114,6 +114,12 @@
     return result != null ? result : defaultValue;
   }
 
+  public static <T> ArrayList<T> newArrayList(ForEachable<T> forEachable) {
+    ArrayList<T> list = new ArrayList<>();
+    forEachable.forEach(list::add);
+    return list;
+  }
+
   public static <T> Optional<T> removeFirstMatch(List<T> list, Predicate<T> element) {
     int index = firstIndexMatching(list, element);
     if (index >= 0) {
diff --git a/src/main/java/com/android/tools/r8/utils/SupplierUtils.java b/src/main/java/com/android/tools/r8/utils/SupplierUtils.java
new file mode 100644
index 0000000..13a02cb
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/utils/SupplierUtils.java
@@ -0,0 +1,15 @@
+// Copyright (c) 2021, 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.utils;
+
+import java.util.function.Supplier;
+
+public class SupplierUtils {
+
+  public static <T> Supplier<T> nonThreadSafeMemoize(Supplier<T> supplier) {
+    Box<T> box = new Box<>();
+    return () -> box.computeIfAbsent(supplier);
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/utils/ZipUtils.java b/src/main/java/com/android/tools/r8/utils/ZipUtils.java
index 400ef0c..e7b9b00 100644
--- a/src/main/java/com/android/tools/r8/utils/ZipUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/ZipUtils.java
@@ -122,6 +122,12 @@
     zip(zipFile, basePath, Arrays.asList(filesToZip));
   }
 
+  public static List<Path> unzip(Path zipFile, Path outDirectory) throws IOException {
+    return unzip(zipFile.toString(), outDirectory.toFile(), (entry) -> true).stream()
+        .map(File::toPath)
+        .collect(Collectors.toList());
+  }
+
   public static List<File> unzip(String zipFile, File outDirectory) throws IOException {
     return unzip(zipFile, outDirectory, (entry) -> true);
   }
diff --git a/src/main/java/com/android/tools/r8/utils/structural/HashingVisitorWithTypeEquivalence.java b/src/main/java/com/android/tools/r8/utils/structural/HashingVisitorWithTypeEquivalence.java
index 6c388fb..ff0e4ba 100644
--- a/src/main/java/com/android/tools/r8/utils/structural/HashingVisitorWithTypeEquivalence.java
+++ b/src/main/java/com/android/tools/r8/utils/structural/HashingVisitorWithTypeEquivalence.java
@@ -65,7 +65,7 @@
 
   @Override
   public void visitDexString(DexString string) {
-    visitInt(string.hashCode());
+    hash.putBytes(string.content);
   }
 
   @Override
diff --git a/src/test/java/com/android/tools/r8/AsmTestBase.java b/src/test/java/com/android/tools/r8/AsmTestBase.java
index 4a24331..a8b8a49 100644
--- a/src/test/java/com/android/tools/r8/AsmTestBase.java
+++ b/src/test/java/com/android/tools/r8/AsmTestBase.java
@@ -66,8 +66,9 @@
     ProcessResult d8Result = runOnArtRaw(compileWithD8(app), main);
     ProcessResult r8NonShakenResult =
         runOnArtRaw(compileWithR8(app, "-dontshrink\n-dontobfuscate\n"), main);
-    ProcessResult r8ShakenResult = runOnArtRaw(
-        compileWithR8(app, keepMainProguardConfiguration(main) + "-dontobfuscate\n"), main);
+    ProcessResult r8ShakenResult =
+        runOnArtRaw(
+            compileWithR8(app, keepMainProguardConfiguration(main) + "-dontobfuscate\n"), main);
     Assert.assertEquals(javaResult.stdout, d8Result.stdout);
     Assert.assertEquals(javaResult.stdout, r8NonShakenResult.stdout);
     Assert.assertEquals(javaResult.stdout, r8ShakenResult.stdout);
diff --git a/src/test/java/com/android/tools/r8/JvmTestBuilder.java b/src/test/java/com/android/tools/r8/JvmTestBuilder.java
index 8698d61..9951a04 100644
--- a/src/test/java/com/android/tools/r8/JvmTestBuilder.java
+++ b/src/test/java/com/android/tools/r8/JvmTestBuilder.java
@@ -153,6 +153,11 @@
     return addClasspath(ToolHelper.getClassPathForTests());
   }
 
+  public JvmTestBuilder enablePreview() {
+    addVmArguments("--enable-preview");
+    return self();
+  }
+
   public JvmTestBuilder addVmArguments(Collection<String> arguments) {
     vmArguments.addAll(arguments);
     return self();
diff --git a/src/test/java/com/android/tools/r8/R8RunExamplesAndroidPTest.java b/src/test/java/com/android/tools/r8/R8RunExamplesAndroidPTest.java
index 9ac01a0..a9907fc 100644
--- a/src/test/java/com/android/tools/r8/R8RunExamplesAndroidPTest.java
+++ b/src/test/java/com/android/tools/r8/R8RunExamplesAndroidPTest.java
@@ -53,9 +53,10 @@
   public void invokeCustomWithShrinking() throws Throwable {
     test("invokecustom-with-shrinking", "invokecustom", "InvokeCustom")
         .withMinApiLevel(AndroidApiLevel.P.getLevel())
-        .withBuilderTransformation(builder ->
-            builder.addProguardConfigurationFiles(
-                Paths.get(ToolHelper.EXAMPLES_ANDROID_P_DIR, "invokecustom/keep-rules.txt")))
+        .withBuilderTransformation(
+            builder ->
+                builder.addProguardConfigurationFiles(
+                    Paths.get(ToolHelper.EXAMPLES_ANDROID_P_DIR, "invokecustom/keep-rules.txt")))
         .run();
   }
 
diff --git a/src/test/java/com/android/tools/r8/R8RunExamplesCommon.java b/src/test/java/com/android/tools/r8/R8RunExamplesCommon.java
index 8a004a9..e13ddd2 100644
--- a/src/test/java/com/android/tools/r8/R8RunExamplesCommon.java
+++ b/src/test/java/com/android/tools/r8/R8RunExamplesCommon.java
@@ -19,7 +19,6 @@
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableSet;
 import java.io.IOException;
-import java.nio.file.NoSuchFileException;
 import java.nio.file.Path;
 import java.nio.file.Paths;
 import java.util.Map;
@@ -104,10 +103,10 @@
     }
   }
 
-  private R8Command.Builder addInputFile(R8Command.Builder builder) throws NoSuchFileException {
+  private R8Command.Builder addInputFile(R8Command.Builder builder) {
     if (input == Input.DX) {
       // If input is DEX code, use the tool helper to add the DEX sources as R8 disallows them.
-      ToolHelper.getAppBuilder(builder).addProgramFiles(getInputFile());
+      ToolHelper.getAppBuilder(builder).addProgramFiles(getOriginalDexFile());
     } else {
       builder.addProgramFiles(getInputFile());
     }
@@ -169,7 +168,6 @@
 
   protected void configure(InternalOptions options) {
     options.lineNumberOptimization = LineNumberOptimization.OFF;
-    options.testing.allowDexInputForTesting = input == Input.DX;
   }
 
   private boolean shouldCompileFail() {
diff --git a/src/test/java/com/android/tools/r8/R8RunExamplesKotlinTest.java b/src/test/java/com/android/tools/r8/R8RunExamplesKotlinTest.java
index 9e48fd0..0690fa9 100644
--- a/src/test/java/com/android/tools/r8/R8RunExamplesKotlinTest.java
+++ b/src/test/java/com/android/tools/r8/R8RunExamplesKotlinTest.java
@@ -20,6 +20,7 @@
 
   @Override
   protected void configure(InternalOptions options) {
+    super.configure(options);
     if (output == Output.CF) {
       // Class inliner is not supported with CF backend yet.
       options.enableClassInlining = false;
diff --git a/src/test/java/com/android/tools/r8/R8RunSmaliTestsTest.java b/src/test/java/com/android/tools/r8/R8RunSmaliTestsTest.java
index 0d21319..1d6cb9a 100644
--- a/src/test/java/com/android/tools/r8/R8RunSmaliTestsTest.java
+++ b/src/test/java/com/android/tools/r8/R8RunSmaliTestsTest.java
@@ -7,16 +7,17 @@
 
 import com.android.tools.r8.ToolHelper.DexVm;
 import com.android.tools.r8.ToolHelper.DexVm.Version;
-import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.utils.StringUtils;
 import com.android.tools.r8.utils.TestDescriptionWatcher;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.ImmutableSet;
+import java.nio.file.Files;
 import java.nio.file.Path;
 import java.nio.file.Paths;
 import java.util.Arrays;
 import java.util.Collection;
+import java.util.Collections;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
@@ -29,13 +30,19 @@
 import org.junit.runners.Parameterized.Parameters;
 
 @RunWith(Parameterized.class)
-public class R8RunSmaliTestsTest {
+public class R8RunSmaliTestsTest extends TestBase {
 
   @Rule
   public ExpectedException thrown = ExpectedException.none();
 
   private static final String SMALI_DIR = ToolHelper.SMALI_BUILD_DIR;
 
+  private static Map<String, Set<String>> missingClasses =
+      ImmutableMap.of(
+          "try-catch", ImmutableSet.of("test.X"),
+          "type-confusion-regression5", ImmutableSet.of("jok", "jol"),
+          "bad-codegen", ImmutableSet.of("java.util.LTest"));
+
   // Tests where the original smali code fails on Art, but runs after R8 processing.
   private static Map<DexVm.Version, List<String>> originalFailingOnArtVersions = ImmutableMap.of(
       Version.V5_1_1, ImmutableList.of(
@@ -164,24 +171,25 @@
   @Test
   public void SmaliTest() throws Exception {
     Path originalDexFile = Paths.get(SMALI_DIR, directoryName, dexFileName);
-    String outputPath = temp.getRoot().getCanonicalPath();
-    R8Command.Builder builder = R8Command.builder()
-        .addProguardConfiguration(ImmutableList.of("-keep class * { *; }"), Origin.unknown())
-        .addLibraryFiles(ToolHelper.getDefaultAndroidJar())
-        .setOutput(Paths.get(outputPath), OutputMode.DexIndexed);
-    ToolHelper.getAppBuilder(builder).addProgramFiles(originalDexFile);
+    Path outputPath = temp.getRoot().toPath().resolve("classes.dex");
 
     if (failingOnX8.contains(directoryName)) {
       thrown.expect(CompilationFailedException.class);
     }
-    R8.run(builder.build());
+
+    testForR8(Backend.DEX)
+        .addKeepAllClassesRule()
+        .addProgramDexFileData(Files.readAllBytes(originalDexFile))
+        .addDontWarn(missingClasses.getOrDefault(directoryName, Collections.emptySet()))
+        .compile()
+        .writeToZip(outputPath);
 
     if (!ToolHelper.artSupported()) {
       return;
     }
 
     String mainClass = "Test";
-    String generated = outputPath + "/classes.dex";
+    String generated = outputPath.toString();
     String output = "";
 
     DexVm.Version dexVmVersion = ToolHelper.getDexVm().getVersion();
diff --git a/src/test/java/com/android/tools/r8/TestBase.java b/src/test/java/com/android/tools/r8/TestBase.java
index a80cc32..cbd2755 100644
--- a/src/test/java/com/android/tools/r8/TestBase.java
+++ b/src/test/java/com/android/tools/r8/TestBase.java
@@ -715,18 +715,30 @@
 
   protected static AppView<AppInfoWithClassHierarchy> computeAppViewWithClassHierachy(
       AndroidApp app) throws Exception {
-    return computeAppViewWithClassHierachy(
-        app,
-        factory ->
-            buildConfigForRules(
-                factory,
-                Collections.singletonList(ProguardKeepRule.defaultKeepAllRule(unused -> {}))));
+    return computeAppViewWithClassHierachy(app, null);
   }
 
   private static AppView<AppInfoWithClassHierarchy> computeAppViewWithClassHierachy(
       AndroidApp app, Function<DexItemFactory, ProguardConfiguration> keepConfig) throws Exception {
+    return computeAppViewWithClassHierachy(app, keepConfig, null);
+  }
+
+  private static AppView<AppInfoWithClassHierarchy> computeAppViewWithClassHierachy(
+      AndroidApp app,
+      Function<DexItemFactory, ProguardConfiguration> keepConfig,
+      Consumer<InternalOptions> optionsConsumer)
+      throws Exception {
     DexItemFactory dexItemFactory = new DexItemFactory();
+    if (keepConfig == null) {
+      keepConfig =
+          factory ->
+              buildConfigForRules(
+                  factory, ImmutableList.of(ProguardKeepRule.defaultKeepAllRule(unused -> {})));
+    }
     InternalOptions options = new InternalOptions(keepConfig.apply(dexItemFactory), new Reporter());
+    if (optionsConsumer != null) {
+      optionsConsumer.accept(options);
+    }
     DexApplication dexApplication = readApplicationForDexOutput(app, options);
     AppView<AppInfoWithClassHierarchy> appView = AppView.createForR8(dexApplication.toDirect());
     appView.setAppServices(AppServices.builder(appView).build());
@@ -735,11 +747,7 @@
 
   protected static AppView<AppInfoWithLiveness> computeAppViewWithLiveness(AndroidApp app)
       throws Exception {
-    return computeAppViewWithLiveness(
-        app,
-        factory ->
-            buildConfigForRules(
-                factory, ImmutableList.of(ProguardKeepRule.defaultKeepAllRule(unused -> {}))));
+    return computeAppViewWithLiveness(app, null, null);
   }
 
   protected static AppView<AppInfoWithLiveness> computeAppViewWithLiveness(
@@ -752,7 +760,16 @@
 
   protected static AppView<AppInfoWithLiveness> computeAppViewWithLiveness(
       AndroidApp app, Function<DexItemFactory, ProguardConfiguration> keepConfig) throws Exception {
-    AppView<AppInfoWithClassHierarchy> appView = computeAppViewWithClassHierachy(app, keepConfig);
+    return computeAppViewWithLiveness(app, keepConfig, null);
+  }
+
+  protected static AppView<AppInfoWithLiveness> computeAppViewWithLiveness(
+      AndroidApp app,
+      Function<DexItemFactory, ProguardConfiguration> keepConfig,
+      Consumer<InternalOptions> optionsConsumer)
+      throws Exception {
+    AppView<AppInfoWithClassHierarchy> appView =
+        computeAppViewWithClassHierachy(app, keepConfig, optionsConsumer);
     // Run the tree shaker to compute an instance of AppInfoWithLiveness.
     ExecutorService executor = Executors.newSingleThreadExecutor();
     SubtypingInfo subtypingInfo = new SubtypingInfo(appView);
diff --git a/src/test/java/com/android/tools/r8/TestBaseBuilder.java b/src/test/java/com/android/tools/r8/TestBaseBuilder.java
index 1e75de5..63b1982 100644
--- a/src/test/java/com/android/tools/r8/TestBaseBuilder.java
+++ b/src/test/java/com/android/tools/r8/TestBaseBuilder.java
@@ -96,6 +96,11 @@
     return addMainDexListClassReferences(ListUtils.map(classes, Reference::classFromClass));
   }
 
+  public T addMainDexListFiles(Path... files) {
+    builder.addMainDexListFiles(files);
+    return self();
+  }
+
   public T addMainDexListFiles(Collection<Path> files) {
     builder.addMainDexListFiles(files);
     return self();
diff --git a/src/test/java/com/android/tools/r8/TestCompileResult.java b/src/test/java/com/android/tools/r8/TestCompileResult.java
index 64c7680..80f3234 100644
--- a/src/test/java/com/android/tools/r8/TestCompileResult.java
+++ b/src/test/java/com/android/tools/r8/TestCompileResult.java
@@ -25,6 +25,7 @@
 import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.AndroidApp;
 import com.android.tools.r8.utils.DescriptorUtils;
+import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.ThrowingConsumer;
 import com.android.tools.r8.utils.TriFunction;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
@@ -290,6 +291,11 @@
     return new CodeInspector(app);
   }
 
+  public CodeInspector inspector(Consumer<InternalOptions> debugOptionsConsumer)
+      throws IOException, ExecutionException {
+    return new CodeInspector(app, debugOptionsConsumer);
+  }
+
   public <E extends Throwable> CR inspect(ThrowingConsumer<CodeInspector, E> consumer)
       throws IOException, ExecutionException, E {
     consumer.accept(inspector());
diff --git a/src/test/java/com/android/tools/r8/TestParametersBuilder.java b/src/test/java/com/android/tools/r8/TestParametersBuilder.java
index c5811f9..e3570be 100644
--- a/src/test/java/com/android/tools/r8/TestParametersBuilder.java
+++ b/src/test/java/com/android/tools/r8/TestParametersBuilder.java
@@ -197,6 +197,10 @@
     return withApiFilter(api -> api.getLevel() < endExclusive.getLevel());
   }
 
+  public TestParametersBuilder withApiLevelsWithoutNativeMultiDex() {
+    return withApiLevelsEndingAtExcluding(AndroidApiLevel.L);
+  }
+
   public TestParametersBuilder withCustomRuntime(TestRuntime runtime) {
     assert getUnfilteredAvailableRuntimes().noneMatch(r -> r == runtime);
     customRuntimes.add(runtime);
diff --git a/src/test/java/com/android/tools/r8/TestShrinkerBuilder.java b/src/test/java/com/android/tools/r8/TestShrinkerBuilder.java
index dbac9f1..816e207 100644
--- a/src/test/java/com/android/tools/r8/TestShrinkerBuilder.java
+++ b/src/test/java/com/android/tools/r8/TestShrinkerBuilder.java
@@ -114,13 +114,17 @@
     return addKeepRules("-dontwarn " + className);
   }
 
-  public T addDontWarn(String... classes) {
+  public T addDontWarn(Collection<String> classes) {
     for (String clazz : classes) {
       addKeepRules("-dontwarn " + clazz);
     }
     return self();
   }
 
+  public T addDontWarn(String... classes) {
+    return addDontWarn(Arrays.asList(classes));
+  }
+
   @Deprecated
   public T addDontWarnCompanionClass(Class<?> clazz) {
     return addDontWarn(clazz.getTypeName() + COMPANION_CLASS_NAME_SUFFIX);
@@ -146,12 +150,6 @@
     return addDontWarn("java.nio.file.**");
   }
 
-  // TODO(b/176133676): Investigate why there are missing class references to org.jetbrains
-  @Deprecated
-  public T addDontWarnJetBrains() {
-    return addDontWarn("org.jetbrains.**");
-  }
-
   public T addDontWarnJetBrainsAnnotations() {
     return addDontWarnJetBrainsNotNullAnnotation().addDontWarnJetBrainsNullableAnnotation();
   }
@@ -164,18 +162,6 @@
     return addDontWarn("org.jetbrains.annotations.Nullable");
   }
 
-  // TODO(b/176133676): Should not report missing classes for Kotlin classes.
-  @Deprecated
-  public T addDontWarnKotlin() {
-    return addDontWarn("kotlin.**");
-  }
-
-  // TODO(b/176133676): Should not report missing classes for Kotlin metadata.
-  @Deprecated
-  public T addDontWarnKotlinMetadata() {
-    return addDontWarn("kotlin.Metadata");
-  }
-
   public T addIgnoreWarnings() {
     return addKeepRules("-ignorewarnings");
   }
diff --git a/src/test/java/com/android/tools/r8/ToolHelper.java b/src/test/java/com/android/tools/r8/ToolHelper.java
index 2ef51ed..c906281 100644
--- a/src/test/java/com/android/tools/r8/ToolHelper.java
+++ b/src/test/java/com/android/tools/r8/ToolHelper.java
@@ -860,6 +860,12 @@
     return reflectJar;
   }
 
+  public static Path getKotlinAnnotationJar(KotlinCompiler kotlinc) {
+    Path annotationJar = kotlinc.getFolder().resolve("annotations-13.0.jar");
+    assert Files.exists(annotationJar) : "Expected annotation jar";
+    return annotationJar;
+  }
+
   public static Path getJdwpTestsCfJarPath(AndroidApiLevel minSdk) {
     if (minSdk.getLevel() >= AndroidApiLevel.N.getLevel()) {
       return Paths.get("third_party", "jdwp-tests", "apache-harmony-jdwp-tests-host.jar");
@@ -889,8 +895,8 @@
       super(parentFolder);
     }
 
-    protected void after() {
-    } // instead of remove, do nothing
+    @Override
+    protected void after() {} // instead of remove, do nothing
   }
 
   // For non-Linux platforms create the temporary directory in the repository root to simplify
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/kotlin/KotlinMetadataTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/kotlin/KotlinMetadataTest.java
index 55d4763..4ac6ddf 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/kotlin/KotlinMetadataTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/kotlin/KotlinMetadataTest.java
@@ -124,12 +124,12 @@
             .addProgramFiles(compiledJars.getForConfiguration(kotlinParameters))
             .addProgramFiles(ToolHelper.getKotlinStdlibJar(kotlinParameters.getCompiler()))
             .addProgramFiles(ToolHelper.getKotlinReflectJar(kotlinParameters.getCompiler()))
+            .addProgramFiles(ToolHelper.getKotlinAnnotationJar(kotlinParameters.getCompiler()))
             .addKeepMainRule(PKG + ".MainKt")
             .addKeepAllClassesRule()
             .addKeepAttributes(ProguardKeepAttributes.RUNTIME_VISIBLE_ANNOTATIONS)
             .setMinApi(parameters.getApiLevel())
-            .allowDiagnosticWarningMessages()
-            .addDontWarnJetBrains();
+            .allowDiagnosticWarningMessages();
     KeepRuleConsumer keepRuleConsumer = null;
     if (desugarLibrary) {
       keepRuleConsumer = createKeepRuleConsumer(parameters);
diff --git a/src/test/java/com/android/tools/r8/desugar/records/EmptyRecordTest.java b/src/test/java/com/android/tools/r8/desugar/records/EmptyRecordTest.java
index 92183a7..ccd5d40 100644
--- a/src/test/java/com/android/tools/r8/desugar/records/EmptyRecordTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/records/EmptyRecordTest.java
@@ -4,10 +4,14 @@
 
 package com.android.tools.r8.desugar.records;
 
+import static com.android.tools.r8.desugar.records.RecordTestUtils.RECORD_KEEP_RULE;
+import static com.android.tools.r8.utils.InternalOptions.TestingOptions;
+
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestRuntime.CfRuntime;
 import com.android.tools.r8.utils.StringUtils;
+import java.nio.file.Path;
 import java.util.List;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -38,7 +42,28 @@
   public void testJvm() throws Exception {
     testForJvm()
         .addProgramClassFileData(PROGRAM_DATA)
-        .addVmArguments("--enable-preview")
+        .enablePreview()
+        .run(parameters.getRuntime(), MAIN_TYPE)
+        .assertSuccessWithOutput(EXPECTED_RESULT);
+  }
+
+  @Test
+  public void testR8Cf() throws Exception {
+    Path output =
+        testForR8(parameters.getBackend())
+            .addProgramClassFileData(PROGRAM_DATA)
+            .setMinApi(parameters.getApiLevel())
+            .addKeepRules(RECORD_KEEP_RULE)
+            .addKeepMainRule(MAIN_TYPE)
+            .apply(builder -> RecordTestUtils.setJdk15Library(builder, temp))
+            .addOptionsModification(TestingOptions::allowExperimentClassFileVersion)
+            .addOptionsModification(opt -> opt.testing.canUseRecords = true)
+            .compile()
+            .writeToZip();
+    RecordTestUtils.assertRecordsAreRecords(output);
+    testForJvm()
+        .addRunClasspathFiles(output)
+        .enablePreview()
         .run(parameters.getRuntime(), MAIN_TYPE)
         .assertSuccessWithOutput(EXPECTED_RESULT);
   }
diff --git a/src/test/java/com/android/tools/r8/desugar/records/RecordInstanceOfTest.java b/src/test/java/com/android/tools/r8/desugar/records/RecordInstanceOfTest.java
index 33a3b57..b2fb0f5 100644
--- a/src/test/java/com/android/tools/r8/desugar/records/RecordInstanceOfTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/records/RecordInstanceOfTest.java
@@ -4,10 +4,14 @@
 
 package com.android.tools.r8.desugar.records;
 
+import static com.android.tools.r8.desugar.records.RecordTestUtils.RECORD_KEEP_RULE;
+
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestRuntime.CfRuntime;
+import com.android.tools.r8.utils.InternalOptions.TestingOptions;
 import com.android.tools.r8.utils.StringUtils;
+import java.nio.file.Path;
 import java.util.List;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -38,7 +42,28 @@
   public void testJvm() throws Exception {
     testForJvm()
         .addProgramClassFileData(PROGRAM_DATA)
-        .addVmArguments("--enable-preview")
+        .enablePreview()
+        .run(parameters.getRuntime(), MAIN_TYPE)
+        .assertSuccessWithOutput(EXPECTED_RESULT);
+  }
+
+  @Test
+  public void testR8Cf() throws Exception {
+    Path output =
+        testForR8(parameters.getBackend())
+            .addProgramClassFileData(PROGRAM_DATA)
+            .setMinApi(parameters.getApiLevel())
+            .addKeepRules(RECORD_KEEP_RULE)
+            .addKeepMainRule(MAIN_TYPE)
+            .apply(builder -> RecordTestUtils.setJdk15Library(builder, temp))
+            .addOptionsModification(TestingOptions::allowExperimentClassFileVersion)
+            .addOptionsModification(opt -> opt.testing.canUseRecords = true)
+            .compile()
+            .writeToZip();
+    RecordTestUtils.assertRecordsAreRecords(output);
+    testForJvm()
+        .addRunClasspathFiles(output)
+        .enablePreview()
         .run(parameters.getRuntime(), MAIN_TYPE)
         .assertSuccessWithOutput(EXPECTED_RESULT);
   }
diff --git a/src/test/java/com/android/tools/r8/desugar/records/RecordInvokeCustomTest.java b/src/test/java/com/android/tools/r8/desugar/records/RecordInvokeCustomTest.java
index 1d1e785..02e75d3 100644
--- a/src/test/java/com/android/tools/r8/desugar/records/RecordInvokeCustomTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/records/RecordInvokeCustomTest.java
@@ -4,10 +4,14 @@
 
 package com.android.tools.r8.desugar.records;
 
+import static com.android.tools.r8.desugar.records.RecordTestUtils.RECORD_KEEP_RULE;
+
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestRuntime.CfRuntime;
+import com.android.tools.r8.utils.InternalOptions.TestingOptions;
 import com.android.tools.r8.utils.StringUtils;
+import java.nio.file.Path;
 import java.util.List;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -51,7 +55,28 @@
   public void testJvm() throws Exception {
     testForJvm()
         .addProgramClassFileData(PROGRAM_DATA)
-        .addVmArguments("--enable-preview")
+        .enablePreview()
+        .run(parameters.getRuntime(), MAIN_TYPE)
+        .assertSuccessWithOutput(EXPECTED_RESULT);
+  }
+
+  @Test
+  public void testR8Cf() throws Exception {
+    Path output =
+        testForR8(parameters.getBackend())
+            .addProgramClassFileData(PROGRAM_DATA)
+            .setMinApi(parameters.getApiLevel())
+            .addKeepRules(RECORD_KEEP_RULE)
+            .addKeepMainRule(MAIN_TYPE)
+            .apply(builder -> RecordTestUtils.setJdk15Library(builder, temp))
+            .addOptionsModification(TestingOptions::allowExperimentClassFileVersion)
+            .addOptionsModification(opt -> opt.testing.canUseRecords = true)
+            .compile()
+            .writeToZip();
+    RecordTestUtils.assertRecordsAreRecords(output);
+    testForJvm()
+        .addRunClasspathFiles(output)
+        .enablePreview()
         .run(parameters.getRuntime(), MAIN_TYPE)
         .assertSuccessWithOutput(EXPECTED_RESULT);
   }
diff --git a/src/test/java/com/android/tools/r8/desugar/records/RecordReflectionTest.java b/src/test/java/com/android/tools/r8/desugar/records/RecordReflectionTest.java
index 1bb0a1a..d567625 100644
--- a/src/test/java/com/android/tools/r8/desugar/records/RecordReflectionTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/records/RecordReflectionTest.java
@@ -4,10 +4,14 @@
 
 package com.android.tools.r8.desugar.records;
 
+import static com.android.tools.r8.desugar.records.RecordTestUtils.RECORD_KEEP_RULE;
+
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestRuntime.CfRuntime;
+import com.android.tools.r8.utils.InternalOptions.TestingOptions;
 import com.android.tools.r8.utils.StringUtils;
+import java.nio.file.Path;
 import java.util.List;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -48,7 +52,28 @@
   public void testJvm() throws Exception {
     testForJvm()
         .addProgramClassFileData(PROGRAM_DATA)
-        .addVmArguments("--enable-preview")
+        .enablePreview()
+        .run(parameters.getRuntime(), MAIN_TYPE)
+        .assertSuccessWithOutput(EXPECTED_RESULT);
+  }
+
+  @Test
+  public void testR8Cf() throws Exception {
+    Path output =
+        testForR8(parameters.getBackend())
+            .addProgramClassFileData(PROGRAM_DATA)
+            .setMinApi(parameters.getApiLevel())
+            .addKeepRules(RECORD_KEEP_RULE)
+            .addKeepMainRule(MAIN_TYPE)
+            .apply(builder -> RecordTestUtils.setJdk15Library(builder, temp))
+            .addOptionsModification(TestingOptions::allowExperimentClassFileVersion)
+            .addOptionsModification(opt -> opt.testing.canUseRecords = true)
+            .compile()
+            .writeToZip();
+    RecordTestUtils.assertRecordsAreRecords(output);
+    testForJvm()
+        .addRunClasspathFiles(output)
+        .enablePreview()
         .run(parameters.getRuntime(), MAIN_TYPE)
         .assertSuccessWithOutput(EXPECTED_RESULT);
   }
diff --git a/src/test/java/com/android/tools/r8/desugar/records/RecordTestUtils.java b/src/test/java/com/android/tools/r8/desugar/records/RecordTestUtils.java
index d4a804b..6e5cc6b 100644
--- a/src/test/java/com/android/tools/r8/desugar/records/RecordTestUtils.java
+++ b/src/test/java/com/android/tools/r8/desugar/records/RecordTestUtils.java
@@ -4,8 +4,15 @@
 
 package com.android.tools.r8.desugar.records;
 
+import static com.android.tools.r8.TestRuntime.getCheckedInJdk8;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.JavaCompilerTool;
+import com.android.tools.r8.R8FullTestBuilder;
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.FoundClassSubject;
 import com.google.common.io.ByteStreams;
 import java.io.IOException;
 import java.nio.file.Files;
@@ -16,6 +23,8 @@
 import java.util.List;
 import java.util.zip.ZipEntry;
 import java.util.zip.ZipFile;
+import org.junit.Assume;
+import org.junit.rules.TemporaryFolder;
 
 /**
  * Records are compiled using: third_party/openjdk/jdk-15/linux/bin/javac --release 15
@@ -30,6 +39,27 @@
     return Paths.get(ToolHelper.TESTS_BUILD_DIR, EXAMPLE_FOLDER, RECORD_FOLDER + ".jar");
   }
 
+  // TODO(b/169645628): Consider if that keep rule should be required or not.
+  public static final String RECORD_KEEP_RULE =
+      "-keepattributes *\n" + "-keep class * extends java.lang.Record { private final <fields>; }";
+
+  public static void setJdk15Library(R8FullTestBuilder builder, TemporaryFolder temp)
+      throws IOException {
+    Assume.assumeFalse(ToolHelper.isWindows());
+    // TODO(b/169645628): Add JDK-15 runtime jar instead. As a temporary solution we use the jdk 8
+    // runtime with additional stubs.
+    // We use jdk-8 for compilation because in jdk-9 and higher we would need to deal with the
+    // module patching logic.
+    Path recordStubs =
+        JavaCompilerTool.create(getCheckedInJdk8(), temp)
+            .addSourceFiles(Paths.get("src", "test", "javaStubs", "Record.java"))
+            .addSourceFiles(Paths.get("src", "test", "javaStubs", "ObjectMethods.java"))
+            .addSourceFiles(Paths.get("src", "test", "javaStubs", "TypeDescriptor.java"))
+            .addSourceFiles(Paths.get("src", "test", "javaStubs", "RecordComponent.java"))
+            .compile();
+    builder.addLibraryFiles(ToolHelper.getJava8RuntimeJar()).addLibraryFiles(recordStubs);
+  }
+
   public static byte[][] getProgramData(String mainClassSimpleName) {
     byte[][] bytes = classDataFromPrefix(RECORD_FOLDER + "/" + mainClassSimpleName);
     assert bytes.length > 0 : "Did not find any program data for " + mainClassSimpleName;
@@ -68,4 +98,13 @@
     }
     return result.toArray(new byte[0][0]);
   }
+
+  public static void assertRecordsAreRecords(Path output) throws IOException {
+    CodeInspector inspector = new CodeInspector(output, opt -> opt.testing.canUseRecords = true);
+    for (FoundClassSubject clazz : inspector.allClasses()) {
+      if (clazz.getDexProgramClass().superType.toString().equals("java.lang.Record")) {
+        assertTrue(clazz.getDexProgramClass().isRecord());
+      }
+    }
+  }
 }
diff --git a/src/test/java/com/android/tools/r8/desugar/records/RecordWithMembersTest.java b/src/test/java/com/android/tools/r8/desugar/records/RecordWithMembersTest.java
index c0ca829..e8ef3a4 100644
--- a/src/test/java/com/android/tools/r8/desugar/records/RecordWithMembersTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/records/RecordWithMembersTest.java
@@ -4,10 +4,14 @@
 
 package com.android.tools.r8.desugar.records;
 
+import static com.android.tools.r8.desugar.records.RecordTestUtils.RECORD_KEEP_RULE;
+
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestRuntime.CfRuntime;
+import com.android.tools.r8.utils.InternalOptions.TestingOptions;
 import com.android.tools.r8.utils.StringUtils;
+import java.nio.file.Path;
 import java.util.List;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -40,7 +44,28 @@
   public void testJvm() throws Exception {
     testForJvm()
         .addProgramClassFileData(PROGRAM_DATA)
-        .addVmArguments("--enable-preview")
+        .enablePreview()
+        .run(parameters.getRuntime(), MAIN_TYPE)
+        .assertSuccessWithOutput(EXPECTED_RESULT);
+  }
+
+  @Test
+  public void testR8Cf() throws Exception {
+    Path output =
+        testForR8(parameters.getBackend())
+            .addProgramClassFileData(PROGRAM_DATA)
+            .setMinApi(parameters.getApiLevel())
+            .addKeepRules(RECORD_KEEP_RULE)
+            .addKeepMainRule(MAIN_TYPE)
+            .apply(builder -> RecordTestUtils.setJdk15Library(builder, temp))
+            .addOptionsModification(TestingOptions::allowExperimentClassFileVersion)
+            .addOptionsModification(opt -> opt.testing.canUseRecords = true)
+            .compile()
+            .writeToZip();
+    RecordTestUtils.assertRecordsAreRecords(output);
+    testForJvm()
+        .addRunClasspathFiles(output)
+        .enablePreview()
         .run(parameters.getRuntime(), MAIN_TYPE)
         .assertSuccessWithOutput(EXPECTED_RESULT);
   }
diff --git a/src/test/java/com/android/tools/r8/desugar/records/SimpleRecordTest.java b/src/test/java/com/android/tools/r8/desugar/records/SimpleRecordTest.java
index 35ce924..39e503c 100644
--- a/src/test/java/com/android/tools/r8/desugar/records/SimpleRecordTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/records/SimpleRecordTest.java
@@ -4,10 +4,14 @@
 
 package com.android.tools.r8.desugar.records;
 
+import static com.android.tools.r8.desugar.records.RecordTestUtils.RECORD_KEEP_RULE;
+
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestRuntime.CfRuntime;
+import com.android.tools.r8.utils.InternalOptions.TestingOptions;
 import com.android.tools.r8.utils.StringUtils;
+import java.nio.file.Path;
 import java.util.List;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -39,7 +43,28 @@
   public void testJvm() throws Exception {
     testForJvm()
         .addProgramClassFileData(PROGRAM_DATA)
-        .addVmArguments("--enable-preview")
+        .enablePreview()
+        .run(parameters.getRuntime(), MAIN_TYPE)
+        .assertSuccessWithOutput(EXPECTED_RESULT);
+  }
+
+  @Test
+  public void testR8Cf() throws Exception {
+    Path output =
+        testForR8(parameters.getBackend())
+            .addProgramClassFileData(PROGRAM_DATA)
+            .setMinApi(parameters.getApiLevel())
+            .addKeepRules(RECORD_KEEP_RULE)
+            .addKeepMainRule(MAIN_TYPE)
+            .apply(builder -> RecordTestUtils.setJdk15Library(builder, temp))
+            .addOptionsModification(TestingOptions::allowExperimentClassFileVersion)
+            .addOptionsModification(opt -> opt.testing.canUseRecords = true)
+            .compile()
+            .writeToZip();
+    RecordTestUtils.assertRecordsAreRecords(output);
+    testForJvm()
+        .addRunClasspathFiles(output)
+        .enablePreview()
         .run(parameters.getRuntime(), MAIN_TYPE)
         .assertSuccessWithOutput(EXPECTED_RESULT);
   }
diff --git a/src/test/java/com/android/tools/r8/desugar/sealed/SealedAttributeTest.java b/src/test/java/com/android/tools/r8/desugar/sealed/SealedAttributeTest.java
index 48b29e7..ee2e4e1 100644
--- a/src/test/java/com/android/tools/r8/desugar/sealed/SealedAttributeTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/sealed/SealedAttributeTest.java
@@ -43,7 +43,7 @@
     assumeTrue(backend == Backend.CF);
     testForJvm()
         .addRunClasspathFiles(Sealed.jar())
-        .addVmArguments("--enable-preview")
+        .enablePreview()
         .run(TestRuntime.getCheckedInJdk15(), Sealed.Main.typeName())
         .assertSuccessWithOutputLines("R8 compiler", "D8 compiler");
   }
diff --git a/src/test/java/com/android/tools/r8/graph/TargetLookupTest.java b/src/test/java/com/android/tools/r8/graph/TargetLookupTest.java
index 603a0b1..b1d9990 100644
--- a/src/test/java/com/android/tools/r8/graph/TargetLookupTest.java
+++ b/src/test/java/com/android/tools/r8/graph/TargetLookupTest.java
@@ -288,7 +288,7 @@
     assertEquals(0, result.exitCode);
 
     // Process the application and expect the same result on Art.
-    AndroidApp processedApp = processApplication(application);
+    AndroidApp processedApp = ToolHelper.runR8(application, null);
     assertEquals(result.stdout, runArt(processedApp, Main.class.getCanonicalName()));
   }
 }
diff --git a/src/test/java/com/android/tools/r8/internal/R8GMSCoreLatestTreeShakeJarVerificationTest.java b/src/test/java/com/android/tools/r8/internal/R8GMSCoreLatestTreeShakeJarVerificationTest.java
index d0242b4..d4e1e43 100644
--- a/src/test/java/com/android/tools/r8/internal/R8GMSCoreLatestTreeShakeJarVerificationTest.java
+++ b/src/test/java/com/android/tools/r8/internal/R8GMSCoreLatestTreeShakeJarVerificationTest.java
@@ -5,14 +5,14 @@
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.assertNull;
 
 import com.android.tools.r8.CompilationMode;
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.utils.AndroidApp;
 import com.google.common.collect.ImmutableList;
-import it.unimi.dsi.fastutil.ints.IntOpenHashSet;
-import it.unimi.dsi.fastutil.ints.IntSet;
+import com.google.common.collect.Sets;
+import java.util.Collections;
 import java.util.List;
 import java.util.Map;
 import java.util.concurrent.ConcurrentHashMap;
@@ -29,7 +29,7 @@
     List<String> additionalProguardConfiguration =
         ImmutableList.of(
             ToolHelper.PROGUARD_SETTINGS_FOR_INTERNAL_APPS + "GmsCore_proguard.config");
-    Map<String, IntSet> methodProcessingIds = new ConcurrentHashMap<>();
+    Map<String, String> idsRoundOne = new ConcurrentHashMap<>();
     AndroidApp app1 =
         buildAndTreeShakeFromDeployJar(
             CompilationMode.RELEASE,
@@ -38,17 +38,12 @@
             GMSCORE_LATEST_MAX_SIZE,
             additionalProguardConfiguration,
             options -> {
-              options.testing.methodProcessingIdConsumer =
-                  (method, methodProcessingId) ->
-                      assertTrue(
-                          methodProcessingIds
-                              .computeIfAbsent(
-                                  method.toSourceString(), ignore -> new IntOpenHashSet(4))
-                              .add(methodProcessingId.getPrimaryId()));
+              options.testing.processingContextsConsumer =
+                  id -> assertNull(idsRoundOne.put(id, id));
               options.proguardMapConsumer =
                   ToolHelper.consumeString(proguardMap -> this.proguardMap1 = proguardMap);
             });
-
+    Map<String, String> idsRoundTwo = new ConcurrentHashMap<>();
     AndroidApp app2 =
         buildAndTreeShakeFromDeployJar(
             CompilationMode.RELEASE,
@@ -57,23 +52,20 @@
             GMSCORE_LATEST_MAX_SIZE,
             additionalProguardConfiguration,
             options -> {
-              options.testing.methodProcessingIdConsumer =
-                  (method, methodProcessingId) -> {
-                    String key = method.toSourceString();
-                    IntSet ids = methodProcessingIds.get(key);
-                    assertNotNull(ids);
-                    assertTrue(ids.remove(methodProcessingId.getPrimaryId()));
-                    if (ids.isEmpty()) {
-                      methodProcessingIds.remove(key);
-                    }
+              options.testing.processingContextsConsumer =
+                  id -> {
+                    assertNotNull(idsRoundOne.get(id));
+                    assertNull(idsRoundTwo.put(id, id));
                   };
               options.proguardMapConsumer =
                   ToolHelper.consumeString(proguardMap -> this.proguardMap2 = proguardMap);
             });
 
     // Verify that the result of the two compilations was the same.
+    assertEquals(
+        Collections.emptySet(),
+        Sets.symmetricDifference(idsRoundOne.keySet(), idsRoundTwo.keySet()));
     assertIdenticalApplications(app1, app2);
-    assertTrue(methodProcessingIds.isEmpty());
     assertEquals(proguardMap1, proguardMap2);
   }
 }
diff --git a/src/test/java/com/android/tools/r8/internal/R8GMSCoreV10DeployJarVerificationTest.java b/src/test/java/com/android/tools/r8/internal/R8GMSCoreV10DeployJarVerificationTest.java
index 98bc7ce..2c2adbf 100644
--- a/src/test/java/com/android/tools/r8/internal/R8GMSCoreV10DeployJarVerificationTest.java
+++ b/src/test/java/com/android/tools/r8/internal/R8GMSCoreV10DeployJarVerificationTest.java
@@ -6,16 +6,16 @@
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.assertNull;
 
 import com.android.tools.r8.CompilationMode;
 import com.android.tools.r8.DexIndexedConsumer.ArchiveConsumer;
 import com.android.tools.r8.R8RunArtTestsTest.CompilerUnderTest;
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.utils.AndroidApp;
-import it.unimi.dsi.fastutil.ints.IntOpenHashSet;
-import it.unimi.dsi.fastutil.ints.IntSet;
+import com.google.common.collect.Sets;
 import java.io.File;
+import java.util.Collections;
 import java.util.Map;
 import java.util.concurrent.ConcurrentHashMap;
 import org.junit.Test;
@@ -27,11 +27,9 @@
 
   @Test
   public void buildFromDeployJar() throws Exception {
-    // TODO(tamaskenez): set hasReference = true when we have the noshrink file for V10
     File tempFolder = temp.newFolder();
-
     File app1Zip = new File(tempFolder, "app1.zip");
-    Map<String, IntSet> methodProcessingIds = new ConcurrentHashMap<>();
+    Map<String, String> idsRoundOne = new ConcurrentHashMap<>();
     AndroidApp app1 =
         buildFromDeployJar(
             CompilerUnderTest.R8,
@@ -39,19 +37,15 @@
             GMSCoreCompilationTestBase.GMSCORE_V10_DIR,
             false,
             options -> {
-              options.testing.methodProcessingIdConsumer =
-                  (method, methodProcessingId) ->
-                      assertTrue(
-                          methodProcessingIds
-                              .computeIfAbsent(
-                                  method.toSourceString(), ignore -> new IntOpenHashSet(4))
-                              .add(methodProcessingId.getPrimaryId()));
+              options.testing.processingContextsConsumer =
+                  id -> assertNull(idsRoundOne.put(id, id));
               options.proguardMapConsumer =
                   ToolHelper.consumeString(proguardMap -> this.proguardMap1 = proguardMap);
             },
             () -> new ArchiveConsumer(app1Zip.toPath(), true));
 
     File app2Zip = new File(tempFolder, "app2.zip");
+    Map<String, String> idsRoundTwo = new ConcurrentHashMap<>();
     AndroidApp app2 =
         buildFromDeployJar(
             CompilerUnderTest.R8,
@@ -59,15 +53,10 @@
             GMSCoreCompilationTestBase.GMSCORE_V10_DIR,
             false,
             options -> {
-              options.testing.methodProcessingIdConsumer =
-                  (method, methodProcessingId) -> {
-                    String key = method.toSourceString();
-                    IntSet ids = methodProcessingIds.get(key);
-                    assertNotNull(ids);
-                    assertTrue(ids.remove(methodProcessingId.getPrimaryId()));
-                    if (ids.isEmpty()) {
-                      methodProcessingIds.remove(key);
-                    }
+              options.testing.processingContextsConsumer =
+                  id -> {
+                    assertNotNull(idsRoundOne.get(id));
+                    assertNull(idsRoundTwo.put(id, id));
                   };
               options.proguardMapConsumer =
                   ToolHelper.consumeString(proguardMap -> this.proguardMap2 = proguardMap);
@@ -75,9 +64,11 @@
             () -> new ArchiveConsumer(app2Zip.toPath(), true));
 
     // Verify that the result of the two compilations was the same.
+    assertEquals(
+        Collections.emptySet(),
+        Sets.symmetricDifference(idsRoundOne.keySet(), idsRoundTwo.keySet()));
     assertIdenticalApplications(app1, app2);
     assertIdenticalZipFiles(app1Zip, app2Zip);
-    assertTrue(methodProcessingIds.isEmpty());
     assertEquals(proguardMap1, proguardMap2);
   }
 }
diff --git a/src/test/java/com/android/tools/r8/internal/R8GMSCoreV10TreeShakeJarVerificationTest.java b/src/test/java/com/android/tools/r8/internal/R8GMSCoreV10TreeShakeJarVerificationTest.java
index 198ae76..dc78be2 100644
--- a/src/test/java/com/android/tools/r8/internal/R8GMSCoreV10TreeShakeJarVerificationTest.java
+++ b/src/test/java/com/android/tools/r8/internal/R8GMSCoreV10TreeShakeJarVerificationTest.java
@@ -3,15 +3,15 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.internal;
 
+import static com.android.tools.r8.utils.AssertionUtils.assertNotNull;
 import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.assertNull;
 
 import com.android.tools.r8.CompilationMode;
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.utils.AndroidApp;
-import it.unimi.dsi.fastutil.ints.IntOpenHashSet;
-import it.unimi.dsi.fastutil.ints.IntSet;
+import com.google.common.collect.Sets;
+import java.util.Collections;
 import java.util.Map;
 import java.util.concurrent.ConcurrentHashMap;
 import org.junit.Test;
@@ -24,8 +24,7 @@
 
   @Test
   public void buildAndTreeShakeFromDeployJar() throws Exception {
-    // TODO(tamaskenez): set hasReference = true when we have the noshrink file for V10
-    Map<String, IntSet> methodProcessingIds = new ConcurrentHashMap<>();
+    Map<String, String> idsRoundOne = new ConcurrentHashMap<>();
     AndroidApp app1 =
         buildAndTreeShakeFromDeployJar(
             CompilationMode.RELEASE,
@@ -33,16 +32,12 @@
             false,
             GMSCORE_V10_MAX_SIZE,
             options -> {
-              options.testing.methodProcessingIdConsumer =
-                  (method, methodProcessingId) ->
-                      assertTrue(
-                          methodProcessingIds
-                              .computeIfAbsent(
-                                  method.toSourceString(), ignore -> new IntOpenHashSet(4))
-                              .add(methodProcessingId.getPrimaryId()));
+              options.testing.processingContextsConsumer =
+                  id -> assertNull(idsRoundOne.put(id, id));
               options.proguardMapConsumer =
                   ToolHelper.consumeString(proguardMap -> this.proguardMap1 = proguardMap);
             });
+    Map<String, String> idsRoundTwo = new ConcurrentHashMap<>();
     AndroidApp app2 =
         buildAndTreeShakeFromDeployJar(
             CompilationMode.RELEASE,
@@ -50,23 +45,20 @@
             false,
             GMSCORE_V10_MAX_SIZE,
             options -> {
-              options.testing.methodProcessingIdConsumer =
-                  (method, methodProcessingId) -> {
-                    String key = method.toSourceString();
-                    IntSet ids = methodProcessingIds.get(key);
-                    assertNotNull(ids);
-                    assertTrue(ids.remove(methodProcessingId.getPrimaryId()));
-                    if (ids.isEmpty()) {
-                      methodProcessingIds.remove(key);
-                    }
+              options.testing.processingContextsConsumer =
+                  id -> {
+                    assertNotNull(idsRoundOne.get(id));
+                    assertNull(idsRoundTwo.put(id, id));
                   };
               options.proguardMapConsumer =
                   ToolHelper.consumeString(proguardMap -> this.proguardMap2 = proguardMap);
             });
 
     // Verify that the result of the two compilations was the same.
+    assertEquals(
+        Collections.emptySet(),
+        Sets.symmetricDifference(idsRoundOne.keySet(), idsRoundTwo.keySet()));
     assertIdenticalApplications(app1, app2);
-    assertTrue(methodProcessingIds.isEmpty());
     assertEquals(proguardMap1, proguardMap2);
   }
 }
diff --git a/src/test/java/com/android/tools/r8/ir/analysis/AnalysisTestBase.java b/src/test/java/com/android/tools/r8/ir/analysis/AnalysisTestBase.java
index ce5cbce..6f24c38 100644
--- a/src/test/java/com/android/tools/r8/ir/analysis/AnalysisTestBase.java
+++ b/src/test/java/com/android/tools/r8/ir/analysis/AnalysisTestBase.java
@@ -12,6 +12,7 @@
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.Instruction;
 import com.android.tools.r8.utils.AndroidApp;
+import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.android.tools.r8.utils.codeinspector.MethodSubject;
 import java.util.function.Consumer;
@@ -56,9 +57,11 @@
     this.className = className;
   }
 
+  public void configure(InternalOptions options) {}
+
   @Before
   public void setup() throws Exception {
-    appView = computeAppViewWithLiveness(app);
+    appView = computeAppViewWithLiveness(app, null, this::configure);
   }
 
   public void buildAndCheckIR(String methodName, Consumer<IRCode> irInspector) {
diff --git a/src/test/java/com/android/tools/r8/ir/analysis/fieldaccess/FieldBitAccessInfoTest.java b/src/test/java/com/android/tools/r8/ir/analysis/fieldaccess/FieldBitAccessInfoTest.java
index ac86504..024dd38 100644
--- a/src/test/java/com/android/tools/r8/ir/analysis/fieldaccess/FieldBitAccessInfoTest.java
+++ b/src/test/java/com/android/tools/r8/ir/analysis/fieldaccess/FieldBitAccessInfoTest.java
@@ -227,7 +227,7 @@
     }
 
     @Override
-    public void scheduleMethodForProcessingAfterCurrentWave(ProgramMethod method) {
+    public void scheduleDesugaredMethodForProcessing(ProgramMethod method) {
       throw new Unreachable();
     }
   }
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/string/StringBuilderOptimizerAnalysisTest.java b/src/test/java/com/android/tools/r8/ir/optimize/string/StringBuilderOptimizerAnalysisTest.java
index 9c1a2b1..eaa9941 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/string/StringBuilderOptimizerAnalysisTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/string/StringBuilderOptimizerAnalysisTest.java
@@ -17,6 +17,7 @@
 import com.android.tools.r8.ir.code.InvokeMethod;
 import com.android.tools.r8.ir.code.Value;
 import com.android.tools.r8.ir.optimize.string.StringBuilderOptimizer.BuilderState;
+import com.android.tools.r8.utils.InternalOptions;
 import java.util.HashSet;
 import java.util.Map;
 import java.util.Set;
@@ -41,8 +42,11 @@
         StringConcatenationTestClass.class);
   }
 
+  @Override
+  public void configure(InternalOptions options) {}
+
   @Test
-  public void testUnusedBuilder() throws Exception {
+  public void testUnusedBuilder() {
     buildAndCheckIR(
         "unusedBuilder",
         checkOptimizerStates(appView, optimizer -> {
@@ -58,7 +62,7 @@
   }
 
   @Test
-  public void testTrivialSequence() throws Exception {
+  public void testTrivialSequence() {
     buildAndCheckIR(
         "trivialSequence",
         checkOptimizerStates(appView, optimizer -> {
@@ -73,7 +77,7 @@
   }
 
   @Test
-  public void testBuilderWithInitialValue() throws Exception {
+  public void testBuilderWithInitialValue() {
     buildAndCheckIR(
         "builderWithInitialValue",
         checkOptimizerStates(appView, optimizer -> {
@@ -88,7 +92,7 @@
   }
 
   @Test
-  public void testBuilderWithCapacity() throws Exception {
+  public void testBuilderWithCapacity() {
     buildAndCheckIR(
         "builderWithCapacity",
         checkOptimizerStates(appView, optimizer -> {
@@ -103,7 +107,7 @@
   }
 
   @Test
-  public void testNonStringArgs() throws Exception {
+  public void testNonStringArgs() {
     buildAndCheckIR(
         "nonStringArgs",
         checkOptimizerStates(appView, optimizer -> {
@@ -118,7 +122,7 @@
   }
 
   @Test
-  public void testTypeConversion() throws Exception {
+  public void testTypeConversion() {
     buildAndCheckIR(
         "typeConversion",
         checkOptimizerStates(appView, optimizer -> {
@@ -133,7 +137,7 @@
   }
 
   @Test
-  public void testTypeConversion_withPhis() throws Exception {
+  public void testTypeConversion_withPhis() {
     buildAndCheckIR(
         "typeConversion_withPhis",
         checkOptimizerStates(appView, optimizer -> {
@@ -149,7 +153,7 @@
 
   @Ignore("TODO(b/113859361): passed to another builder should be an eligible case.")
   @Test
-  public void testNestedBuilders_appendBuilderItself() throws Exception {
+  public void testNestedBuilders_appendBuilderItself() {
     buildAndCheckIR(
         "nestedBuilders_appendBuilderItself",
         checkOptimizerStates(appView, optimizer -> {
@@ -167,7 +171,7 @@
 
   @Ignore("TODO(b/113859361): merge builder.")
   @Test
-  public void testNestedBuilders_appendBuilderResult() throws Exception {
+  public void testNestedBuilders_appendBuilderResult() {
     buildAndCheckIR(
         "nestedBuilders_appendBuilderResult",
         checkOptimizerStates(appView, optimizer -> {
@@ -185,7 +189,7 @@
 
   @Ignore("TODO(b/113859361): merge builder.")
   @Test
-  public void testNestedBuilders_conditional() throws Exception {
+  public void testNestedBuilders_conditional() {
     buildAndCheckIR(
         "nestedBuilders_conditional",
         checkOptimizerStates(appView, optimizer -> {
@@ -203,7 +207,7 @@
 
   @Ignore("TODO(b/113859361): merge builder.")
   @Test
-  public void testConcatenatedBuilders_init() throws Exception {
+  public void testConcatenatedBuilders_init() {
     buildAndCheckIR(
         "concatenatedBuilders_init",
         checkOptimizerStates(appView, optimizer -> {
@@ -221,7 +225,7 @@
 
   @Ignore("TODO(b/113859361): merge builder.")
   @Test
-  public void testConcatenatedBuilders_append() throws Exception {
+  public void testConcatenatedBuilders_append() {
     buildAndCheckIR(
         "concatenatedBuilders_append",
         checkOptimizerStates(appView, optimizer -> {
@@ -239,7 +243,7 @@
 
   @Ignore("TODO(b/113859361): merge builder.")
   @Test
-  public void testConcatenatedBuilders_conditional() throws Exception {
+  public void testConcatenatedBuilders_conditional() {
     final Set<String> expectedConstStrings = new HashSet<>();
     expectedConstStrings.add("Hello,R8");
     expectedConstStrings.add("D8");
@@ -259,7 +263,7 @@
   }
 
   @Test
-  public void testSimplePhi() throws Exception {
+  public void testSimplePhi() {
     buildAndCheckIR(
         "simplePhi",
         checkOptimizerStates(appView, optimizer -> {
@@ -269,7 +273,7 @@
   }
 
   @Test
-  public void testPhiAtInit() throws Exception {
+  public void testPhiAtInit() {
     int expectedNumOfNewBuilder = 2;
     boolean expectToMeetToString = false;
     if (parameters.isDexRuntime()
@@ -293,7 +297,7 @@
   }
 
   @Test
-  public void testPhiWithDifferentInits() throws Exception {
+  public void testPhiWithDifferentInits() {
     buildAndCheckIR(
         "phiWithDifferentInits",
         checkOptimizerStates(appView, optimizer -> {
@@ -308,7 +312,7 @@
   }
 
   @Test
-  public void testConditionalPhiWithoutAppend() throws Exception {
+  public void testConditionalPhiWithoutAppend() {
     buildAndCheckIR(
         "conditionalPhiWithoutAppend",
         checkOptimizerStates(appView, optimizer -> {
@@ -323,7 +327,7 @@
   }
 
   @Test
-  public void testLoop() throws Exception {
+  public void testLoop() {
     buildAndCheckIR(
         "loop",
         checkOptimizerStates(appView, optimizer -> {
@@ -338,7 +342,7 @@
   }
 
   @Test
-  public void testLoopWithBuilder() throws Exception {
+  public void testLoopWithBuilder() {
     buildAndCheckIR(
         "loopWithBuilder",
         checkOptimizerStates(appView, optimizer -> {
diff --git a/src/test/java/com/android/tools/r8/kotlin/ProcessKotlinReflectionLibTest.java b/src/test/java/com/android/tools/r8/kotlin/ProcessKotlinReflectionLibTest.java
index 9f92a95..f53dae8 100644
--- a/src/test/java/com/android/tools/r8/kotlin/ProcessKotlinReflectionLibTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/ProcessKotlinReflectionLibTest.java
@@ -38,7 +38,9 @@
   private void test(ThrowableConsumer<R8FullTestBuilder> consumer) throws Exception {
     testForR8(parameters.getBackend())
         .addLibraryFiles(
-            ToolHelper.getMostRecentAndroidJar(), ToolHelper.getKotlinStdlibJar(kotlinc))
+            ToolHelper.getMostRecentAndroidJar(),
+            ToolHelper.getKotlinStdlibJar(kotlinc),
+            ToolHelper.getKotlinAnnotationJar(kotlinc))
         .addProgramFiles(ToolHelper.getKotlinReflectJar(kotlinc))
         .addKeepAttributes(ProguardKeepAttributes.SIGNATURE)
         .addKeepAttributes(ProguardKeepAttributes.INNER_CLASSES)
@@ -49,14 +51,12 @@
 
   @Test
   public void testAsIs() throws Exception {
-    test(
-        builder ->
-            builder.addDontWarnJetBrains().noMinification().noOptimization().noTreeShaking());
+    test(builder -> builder.noMinification().noOptimization().noTreeShaking());
   }
 
   @Test
   public void testDontShrinkAndDontOptimize() throws Exception {
-    test(builder -> builder.addDontWarnJetBrains().noOptimization().noTreeShaking());
+    test(builder -> builder.noOptimization().noTreeShaking());
   }
 
   @Test
@@ -65,7 +65,6 @@
         builder ->
             builder
                 .addKeepRules("-keep,allowobfuscation class **.*KClasses*")
-                .addDontWarnJetBrains()
                 .noTreeShaking()
                 .addOptionsModification(
                     o -> {
@@ -78,12 +77,12 @@
 
   @Test
   public void testDontShrinkAndDontObfuscate() throws Exception {
-    test(builder -> builder.addDontWarnJetBrains().noMinification().noTreeShaking());
+    test(builder -> builder.noMinification().noTreeShaking());
   }
 
   @Test
   public void testDontShrink() throws Exception {
-    test(builder -> builder.addDontWarnJetBrains().noTreeShaking());
+    test(TestShrinkerBuilder::noTreeShaking);
   }
 
   @Test
@@ -92,7 +91,6 @@
         builder ->
             builder
                 .addKeepRules("-keep,allowobfuscation class **.*KClasses*")
-                .addDontWarnJetBrains()
                 .noTreeShaking());
   }
 
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteAllowAccessModificationTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteAllowAccessModificationTest.java
index 71bd275..b4bca5c 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteAllowAccessModificationTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteAllowAccessModificationTest.java
@@ -96,6 +96,7 @@
     Path libJar =
         testForR8(parameters.getBackend())
             .addProgramFiles(libJars.getForConfiguration(kotlinc, targetVersion))
+            .addClasspathFiles(ToolHelper.getKotlinStdlibJar(kotlinc))
             .addKeepRules("-keepclassmembers,allowaccessmodification class **.Lib { *; }")
             .addKeepRules("-keep,allowaccessmodification,allowobfuscation class **.Lib { *; }")
             .addKeepRules("-keepclassmembers,allowaccessmodification class **.Lib$Comp { *; }")
@@ -112,7 +113,6 @@
                     "  void staticInternal() -> staticInternalReference"))
             .addKeepRuntimeVisibleAnnotations()
             .addDontWarnJetBrainsNotNullAnnotation()
-            .addDontWarnKotlin()
             .allowAccessModification()
             .compile()
             .inspect(this::inspect)
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteAnnotationTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteAnnotationTest.java
index ebe0dac..cb63498 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteAnnotationTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteAnnotationTest.java
@@ -97,6 +97,7 @@
     Path libJar =
         testForR8(parameters.getBackend())
             .addProgramFiles(libJars.getForConfiguration(kotlinc, targetVersion))
+            .addClasspathFiles(ToolHelper.getKotlinStdlibJar(kotlinc))
             /// Keep the annotations
             .addKeepClassAndMembersRules(PKG_LIB + ".AnnoWithClassAndEnum")
             .addKeepClassAndMembersRules(PKG_LIB + ".AnnoWithClassArr")
@@ -119,7 +120,6 @@
                 ProguardKeepAttributes.RUNTIME_VISIBLE_PARAMETER_ANNOTATIONS,
                 ProguardKeepAttributes.RUNTIME_VISIBLE_TYPE_ANNOTATIONS)
             .addDontWarnJetBrainsNotNullAnnotation()
-            .addDontWarnKotlin()
             .compile()
             .inspect(this::inspect)
             .writeToZip();
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteCrossinlineAnonFunctionTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteCrossinlineAnonFunctionTest.java
index 989512b..a8ccc76 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteCrossinlineAnonFunctionTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteCrossinlineAnonFunctionTest.java
@@ -62,10 +62,10 @@
     Path libJar =
         testForR8(parameters.getBackend())
             .addProgramFiles(libJars.getForConfiguration(kotlinc, targetVersion))
+            .addClasspathFiles(ToolHelper.getKotlinStdlibJar(kotlinc))
             .addKeepAllClassesRule()
             .addKeepAllAttributes()
             .addDontWarnJetBrainsNotNullAnnotation()
-            .addDontWarnKotlin()
             .compile()
             .writeToZip();
     Path output =
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteCrossinlineConcreteFunctionTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteCrossinlineConcreteFunctionTest.java
index d969f34..9c138bf 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteCrossinlineConcreteFunctionTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteCrossinlineConcreteFunctionTest.java
@@ -62,10 +62,10 @@
     Path libJar =
         testForR8(parameters.getBackend())
             .addProgramFiles(libJars.getForConfiguration(kotlinc, targetVersion))
+            .addClasspathFiles(ToolHelper.getKotlinStdlibJar(kotlinc))
             .addKeepAllClassesRule()
             .addKeepAllAttributes()
             .addDontWarnJetBrainsNotNullAnnotation()
-            .addDontWarnKotlin()
             .compile()
             .writeToZip();
     Path output =
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteDelegatedPropertyTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteDelegatedPropertyTest.java
index bc8051d..564cd64 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteDelegatedPropertyTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteDelegatedPropertyTest.java
@@ -63,12 +63,12 @@
   public void testMetadataForLib() throws Exception {
     Path outputJar =
         testForR8(parameters.getBackend())
-            .addClasspathFiles(ToolHelper.getKotlinStdlibJar(kotlinc))
+            .addClasspathFiles(
+                ToolHelper.getKotlinStdlibJar(kotlinc), ToolHelper.getKotlinReflectJar(kotlinc))
             .addProgramFiles(jars.getForConfiguration(kotlinc, targetVersion))
             .addKeepAllClassesRule()
             .addKeepAttributes(ProguardKeepAttributes.RUNTIME_VISIBLE_ANNOTATIONS)
             .addDontWarnJetBrainsAnnotations()
-            .addDontWarnKotlin()
             .compile()
             .inspect(
                 inspector ->
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInLibraryTypeTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInLibraryTypeTest.java
index d583ea7..5b7d4e6 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInLibraryTypeTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInLibraryTypeTest.java
@@ -95,7 +95,6 @@
             .addKeepAttributes(ProguardKeepAttributes.RUNTIME_VISIBLE_ANNOTATIONS)
             .addDontWarn(PKG + ".**")
             .addDontWarnJetBrainsNotNullAnnotation()
-            .addDontWarnKotlin()
             .allowDiagnosticWarningMessages()
             // -dontoptimize so that basic code structure is kept.
             .noOptimization()
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteJvmStaticTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteJvmStaticTest.java
index e03a72c..b31c838 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteJvmStaticTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteJvmStaticTest.java
@@ -92,10 +92,10 @@
     Path libJar =
         testForR8(parameters.getBackend())
             .addProgramFiles(kotlincLibJar.getForConfiguration(kotlinc, targetVersion))
+            .addClasspathFiles(ToolHelper.getKotlinStdlibJar(kotlinc))
             .addKeepAllClassesRule()
             .addKeepAttributes(ProguardKeepAttributes.RUNTIME_VISIBLE_ANNOTATIONS)
             .addDontWarnJetBrainsNotNullAnnotation()
-            .addDontWarnKotlin()
             .compile()
             .inspect(this::inspect)
             .writeToZip();
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteKeepPathTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteKeepPathTest.java
index 569d2b7..1d6a41f 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteKeepPathTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteKeepPathTest.java
@@ -89,10 +89,10 @@
   public void testMissing() throws Exception {
     testForR8(parameters.getBackend())
         .addProgramFiles(libJars.getForConfiguration(kotlinc, targetVersion))
+        .addClasspathFiles(ToolHelper.getKotlinStdlibJar(kotlinc))
         .addKeepRules("-keep class " + LIB_CLASS_NAME)
         .addKeepRuntimeVisibleAnnotations()
         .addDontWarnJetBrainsNotNullAnnotation()
-        .addDontWarnKotlin()
         .compile()
         .inspect(inspector -> inspect(inspector, true));
   }
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewritePrunedObjectsTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewritePrunedObjectsTest.java
index 6d4391f..2de9622 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewritePrunedObjectsTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewritePrunedObjectsTest.java
@@ -70,9 +70,9 @@
     Path libJar =
         testForR8(parameters.getBackend())
             .addProgramFiles(libJars.getForConfiguration(kotlinc, targetVersion))
+            .addClasspathFiles(ToolHelper.getKotlinStdlibJar(kotlinc))
             .addKeepRules("-keep class " + PKG_LIB + ".Sub { <init>(); *** kept(); }")
             .addKeepRuntimeVisibleAnnotations()
-            .addDontWarnKotlinMetadata()
             .noMinification()
             .compile()
             .inspect(this::checkPruned)
diff --git a/src/test/java/com/android/tools/r8/kotlin/reflection/KotlinReflectTest.java b/src/test/java/com/android/tools/r8/kotlin/reflection/KotlinReflectTest.java
index ae80118..3732458 100644
--- a/src/test/java/com/android/tools/r8/kotlin/reflection/KotlinReflectTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/reflection/KotlinReflectTest.java
@@ -86,11 +86,11 @@
         .addProgramFiles(compiledJars.getForConfiguration(kotlinc, targetVersion))
         .addProgramFiles(ToolHelper.getKotlinStdlibJar(kotlinc))
         .addProgramFiles(ToolHelper.getKotlinReflectJar(kotlinc))
+        .addProgramFiles(ToolHelper.getKotlinAnnotationJar(kotlinc))
         .setMinApi(parameters.getApiLevel())
         .addKeepAllClassesRule()
         .addKeepAttributes(ProguardKeepAttributes.RUNTIME_VISIBLE_ANNOTATIONS)
         .allowDiagnosticWarningMessages()
-        .addDontWarnJetBrains()
         .compile()
         .writeToZip(foo.toPath())
         .assertAllWarningMessagesMatch(equalTo("Resource 'META-INF/MANIFEST.MF' already exists."))
diff --git a/src/test/java/com/android/tools/r8/maindexlist/MainDexRulesAndListD8.java b/src/test/java/com/android/tools/r8/maindexlist/MainDexRulesAndListD8.java
new file mode 100644
index 0000000..d80f45c
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/maindexlist/MainDexRulesAndListD8.java
@@ -0,0 +1,76 @@
+// Copyright (c) 2018, 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.maindexlist;
+
+import static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.FileUtils;
+import com.android.tools.r8.utils.ZipUtils;
+import com.google.common.collect.ImmutableList;
+import java.nio.file.Path;
+import java.util.List;
+import java.util.stream.Collectors;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class MainDexRulesAndListD8 extends TestBase {
+
+  private final TestParameters parameters;
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withDexRuntimes().withApiLevelsWithoutNativeMultiDex().build();
+  }
+
+  public MainDexRulesAndListD8(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  private static Path testDir;
+  private static Path mainDexRules;
+  private static Path mainDexList;
+
+  @BeforeClass
+  public static void setUp() throws Exception {
+    testDir = getStaticTemp().newFolder().toPath();
+    mainDexRules = testDir.resolve("main-dex-rules");
+    mainDexList = testDir.resolve("main-dex-list");
+    FileUtils.writeTextFile(mainDexRules, ImmutableList.of("-keep class " + A.class.getTypeName()));
+    FileUtils.writeTextFile(
+        mainDexList, ImmutableList.of(B.class.getTypeName().replace('.', '/') + ".class"));
+  }
+
+  @Test
+  public void test() throws Exception {
+    Path result =
+        testForD8(parameters.getBackend())
+            .setMinApi(parameters.getApiLevel())
+            .addInnerClasses(getClass())
+            .addMainDexRulesFiles(mainDexRules)
+            .addMainDexListFiles(mainDexList)
+            .debug()
+            .compile()
+            .writeToZip();
+    List<Path> dexFiles =
+        ZipUtils.unzip(result, testDir).stream().sorted().collect(Collectors.toList());
+    assertEquals(
+        classNamesFromDexFile(dexFiles.get(0)).stream().sorted().collect(Collectors.toList()),
+        ImmutableList.of(A.class.getTypeName(), B.class.getTypeName()));
+    assertEquals(
+        classNamesFromDexFile(dexFiles.get(1)).stream().sorted().collect(Collectors.toList()),
+        ImmutableList.of(C.class.getTypeName()));
+  }
+
+  static class A {}
+
+  static class B {}
+
+  static class C {}
+}
diff --git a/src/test/java/com/android/tools/r8/memberrebinding/MemberRebindingTest.java b/src/test/java/com/android/tools/r8/memberrebinding/MemberRebindingTest.java
index 4996911..756cf01 100644
--- a/src/test/java/com/android/tools/r8/memberrebinding/MemberRebindingTest.java
+++ b/src/test/java/com/android/tools/r8/memberrebinding/MemberRebindingTest.java
@@ -29,7 +29,6 @@
 import java.util.Collections;
 import java.util.Iterator;
 import java.util.List;
-import java.util.concurrent.ExecutionException;
 import java.util.function.Consumer;
 import java.util.stream.Collectors;
 import org.junit.Assume;
@@ -374,7 +373,7 @@
   }
 
   @Test
-  public void memberRebindingTest() throws IOException, ExecutionException {
+  public void memberRebindingTest() throws IOException {
     Assume.assumeTrue(ToolHelper.artSupported() || ToolHelper.compareAgaintsGoldenFiles());
 
     Path out = Paths.get(temp.getRoot().getCanonicalPath());
diff --git a/src/test/java/com/android/tools/r8/missingclasses/MissingClassReferencedFromInvokeVirtualToAbsentMethodParameterTest.java b/src/test/java/com/android/tools/r8/missingclasses/MissingClassReferencedFromInvokeVirtualToAbsentMethodParameterTest.java
new file mode 100644
index 0000000..21e18bf
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/missingclasses/MissingClassReferencedFromInvokeVirtualToAbsentMethodParameterTest.java
@@ -0,0 +1,80 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.missingclasses;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.R8FullTestBuilder;
+import com.android.tools.r8.TestDiagnosticMessages;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.ThrowableConsumer;
+import com.android.tools.r8.missingclasses.MissingClassReferencedFromNestMemberAttributeTest.Main;
+import com.android.tools.r8.references.MethodReference;
+import com.android.tools.r8.references.Reference;
+import com.android.tools.r8.utils.MethodReferenceUtils;
+import com.google.common.collect.ImmutableList;
+import java.io.IOException;
+import java.util.Collection;
+import org.junit.Test;
+
+/**
+ * If a method reference that refers to a missing class does not resolve to a definition, then the
+ * enclosing method is to be blamed.
+ */
+public class MissingClassReferencedFromInvokeVirtualToAbsentMethodParameterTest
+    extends MissingClassesTestBase {
+
+  private static final MethodReference referencedFrom =
+      MethodReferenceUtils.mainMethod(Reference.classFromClass(Main.class));
+
+  public MissingClassReferencedFromInvokeVirtualToAbsentMethodParameterTest(
+      TestParameters parameters) {
+    super(parameters);
+  }
+
+  @Test(expected = CompilationFailedException.class)
+  public void testNoRules() throws Exception {
+    compileWithExpectedDiagnostics(
+        addMain(), diagnostics -> inspectDiagnosticsWithNoRules(diagnostics, referencedFrom));
+  }
+
+  @Test
+  public void testDontWarnMainClass() throws Exception {
+    compileWithExpectedDiagnostics(
+        addMain().andThen(addDontWarn(Main.class)), TestDiagnosticMessages::assertNoMessages);
+  }
+
+  @Test
+  public void testDontWarnMissingClass() throws Exception {
+    compileWithExpectedDiagnostics(
+        addMain().andThen(addDontWarn(MissingClass.class)),
+        TestDiagnosticMessages::assertNoMessages);
+  }
+
+  @Test
+  public void testIgnoreWarnings() throws Exception {
+    compileWithExpectedDiagnostics(
+        addMain().andThen(addIgnoreWarnings()),
+        diagnostics -> inspectDiagnosticsWithIgnoreWarnings(diagnostics, referencedFrom));
+  }
+
+  private ThrowableConsumer<R8FullTestBuilder> addMain() {
+    return builder ->
+        builder.addProgramClassFileData(getProgramClassFileData()).addKeepMainRule(Main.class);
+  }
+
+  static Collection<byte[]> getProgramClassFileData() throws IOException {
+    return ImmutableList.of(transformer(Main.class).removeMethodsWithName("get").transform());
+  }
+
+  static class Main {
+
+    public static void main(String[] args) {
+      new Main().get(null);
+    }
+
+    /** Removed by transformer. */
+    public void get(MissingClass mc) {}
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/missingclasses/MissingClassReferencedFromInvokeVirtualToAbsentMethodReturnTest.java b/src/test/java/com/android/tools/r8/missingclasses/MissingClassReferencedFromInvokeVirtualToAbsentMethodReturnTest.java
new file mode 100644
index 0000000..d40d191
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/missingclasses/MissingClassReferencedFromInvokeVirtualToAbsentMethodReturnTest.java
@@ -0,0 +1,81 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.missingclasses;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.R8FullTestBuilder;
+import com.android.tools.r8.TestDiagnosticMessages;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.ThrowableConsumer;
+import com.android.tools.r8.references.MethodReference;
+import com.android.tools.r8.references.Reference;
+import com.android.tools.r8.utils.MethodReferenceUtils;
+import com.google.common.collect.ImmutableList;
+import java.io.IOException;
+import java.util.Collection;
+import org.junit.Test;
+
+/**
+ * If a method reference that refers to a missing class does not resolve to a definition, then the
+ * enclosing method is to be blamed.
+ */
+public class MissingClassReferencedFromInvokeVirtualToAbsentMethodReturnTest
+    extends MissingClassesTestBase {
+
+  private static final MethodReference referencedFrom =
+      MethodReferenceUtils.mainMethod(Reference.classFromClass(Main.class));
+
+  public MissingClassReferencedFromInvokeVirtualToAbsentMethodReturnTest(
+      TestParameters parameters) {
+    super(parameters);
+  }
+
+  @Test(expected = CompilationFailedException.class)
+  public void testNoRules() throws Exception {
+    compileWithExpectedDiagnostics(
+        addMain(), diagnostics -> inspectDiagnosticsWithNoRules(diagnostics, referencedFrom));
+  }
+
+  @Test
+  public void testDontWarnMainClass() throws Exception {
+    compileWithExpectedDiagnostics(
+        addMain().andThen(addDontWarn(Main.class)), TestDiagnosticMessages::assertNoMessages);
+  }
+
+  @Test
+  public void testDontWarnMissingClass() throws Exception {
+    compileWithExpectedDiagnostics(
+        addMain().andThen(addDontWarn(MissingClass.class)),
+        TestDiagnosticMessages::assertNoMessages);
+  }
+
+  @Test
+  public void testIgnoreWarnings() throws Exception {
+    compileWithExpectedDiagnostics(
+        addMain().andThen(addIgnoreWarnings()),
+        diagnostics -> inspectDiagnosticsWithIgnoreWarnings(diagnostics, referencedFrom));
+  }
+
+  private ThrowableConsumer<R8FullTestBuilder> addMain() {
+    return builder ->
+        builder.addProgramClassFileData(getProgramClassFileData()).addKeepMainRule(Main.class);
+  }
+
+  static Collection<byte[]> getProgramClassFileData() throws IOException {
+    return ImmutableList.of(transformer(Main.class).removeMethodsWithName("get").transform());
+  }
+
+  static class Main {
+
+    public static void main(String[] args) {
+      new Main().get();
+    }
+
+    /** Removed by transformer. */
+    public MissingClass get() {
+      return null;
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/missingclasses/MissingClassReferencedFromInvokeVirtualToPresentMethodParameterTest.java b/src/test/java/com/android/tools/r8/missingclasses/MissingClassReferencedFromInvokeVirtualToPresentMethodParameterTest.java
new file mode 100644
index 0000000..f5b82ad
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/missingclasses/MissingClassReferencedFromInvokeVirtualToPresentMethodParameterTest.java
@@ -0,0 +1,68 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.missingclasses;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.TestDiagnosticMessages;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.references.MethodReference;
+import com.android.tools.r8.references.Reference;
+import com.google.common.collect.ImmutableList;
+import org.junit.Test;
+
+/**
+ * If a method reference that refers to a missing class resolves to a definition, then the method
+ * definition is to be blamed, and not the enclosing method.
+ */
+public class MissingClassReferencedFromInvokeVirtualToPresentMethodParameterTest
+    extends MissingClassesTestBase {
+
+  private static final MethodReference referencedFrom =
+      Reference.method(
+          Reference.classFromClass(Main.class),
+          "get",
+          ImmutableList.of(Reference.classFromClass(MissingClass.class)),
+          null);
+
+  public MissingClassReferencedFromInvokeVirtualToPresentMethodParameterTest(
+      TestParameters parameters) {
+    super(parameters);
+  }
+
+  @Test(expected = CompilationFailedException.class)
+  public void testNoRules() throws Exception {
+    compileWithExpectedDiagnostics(
+        Main.class, diagnostics -> inspectDiagnosticsWithNoRules(diagnostics, referencedFrom));
+  }
+
+  @Test
+  public void testDontWarnMainClass() throws Exception {
+    compileWithExpectedDiagnostics(
+        Main.class, TestDiagnosticMessages::assertNoMessages, addDontWarn(Main.class));
+  }
+
+  @Test
+  public void testDontWarnMissingClass() throws Exception {
+    compileWithExpectedDiagnostics(
+        Main.class, TestDiagnosticMessages::assertNoMessages, addDontWarn(MissingClass.class));
+  }
+
+  @Test
+  public void testIgnoreWarnings() throws Exception {
+    compileWithExpectedDiagnostics(
+        Main.class,
+        diagnostics -> inspectDiagnosticsWithIgnoreWarnings(diagnostics, referencedFrom),
+        addIgnoreWarnings());
+  }
+
+  static class Main {
+
+    public static void main(String[] args) {
+      new Main().get(null);
+    }
+
+    public void get(MissingClass mc) {}
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/missingclasses/MissingClassReferencedFromInvokeVirtualToPresentMethodReturnTest.java b/src/test/java/com/android/tools/r8/missingclasses/MissingClassReferencedFromInvokeVirtualToPresentMethodReturnTest.java
new file mode 100644
index 0000000..b675c8a
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/missingclasses/MissingClassReferencedFromInvokeVirtualToPresentMethodReturnTest.java
@@ -0,0 +1,70 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.missingclasses;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.TestDiagnosticMessages;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.references.MethodReference;
+import com.android.tools.r8.references.Reference;
+import java.util.Collections;
+import org.junit.Test;
+
+/**
+ * If a method reference that refers to a missing class resolves to a definition, then the method
+ * definition is to be blamed, and not the enclosing method.
+ */
+public class MissingClassReferencedFromInvokeVirtualToPresentMethodReturnTest
+    extends MissingClassesTestBase {
+
+  private static final MethodReference referencedFrom =
+      Reference.method(
+          Reference.classFromClass(Main.class),
+          "get",
+          Collections.emptyList(),
+          Reference.classFromClass(MissingClass.class));
+
+  public MissingClassReferencedFromInvokeVirtualToPresentMethodReturnTest(
+      TestParameters parameters) {
+    super(parameters);
+  }
+
+  @Test(expected = CompilationFailedException.class)
+  public void testNoRules() throws Exception {
+    compileWithExpectedDiagnostics(
+        Main.class, diagnostics -> inspectDiagnosticsWithNoRules(diagnostics, referencedFrom));
+  }
+
+  @Test
+  public void testDontWarnMainClass() throws Exception {
+    compileWithExpectedDiagnostics(
+        Main.class, TestDiagnosticMessages::assertNoMessages, addDontWarn(Main.class));
+  }
+
+  @Test
+  public void testDontWarnMissingClass() throws Exception {
+    compileWithExpectedDiagnostics(
+        Main.class, TestDiagnosticMessages::assertNoMessages, addDontWarn(MissingClass.class));
+  }
+
+  @Test
+  public void testIgnoreWarnings() throws Exception {
+    compileWithExpectedDiagnostics(
+        Main.class,
+        diagnostics -> inspectDiagnosticsWithIgnoreWarnings(diagnostics, referencedFrom),
+        addIgnoreWarnings());
+  }
+
+  static class Main {
+
+    public static void main(String[] args) {
+      new Main().get();
+    }
+
+    public MissingClass get() {
+      return null;
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/missingclasses/MissingClassReferencedFromKeptMethodParameterTest.java b/src/test/java/com/android/tools/r8/missingclasses/MissingClassReferencedFromKeptMethodParameterTest.java
new file mode 100644
index 0000000..a86e57a
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/missingclasses/MissingClassReferencedFromKeptMethodParameterTest.java
@@ -0,0 +1,75 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.missingclasses;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.R8FullTestBuilder;
+import com.android.tools.r8.TestDiagnosticMessages;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.references.MethodReference;
+import com.android.tools.r8.references.Reference;
+import com.google.common.collect.ImmutableList;
+import org.junit.Test;
+
+/** If a method definition refers to a missing class, then the method definition is to be blamed. */
+public class MissingClassReferencedFromKeptMethodParameterTest extends MissingClassesTestBase {
+
+  private static final MethodReference referencedFrom =
+      Reference.method(
+          Reference.classFromClass(Main.class),
+          "get",
+          ImmutableList.of(Reference.classFromClass(MissingClass.class)),
+          null);
+
+  public MissingClassReferencedFromKeptMethodParameterTest(TestParameters parameters) {
+    super(parameters);
+  }
+
+  @Test(expected = CompilationFailedException.class)
+  public void testNoRules() throws Exception {
+    compileWithExpectedDiagnostics(
+        Main.class,
+        diagnostics -> inspectDiagnosticsWithNoRules(diagnostics, referencedFrom),
+        this::addKeepMethodRule);
+  }
+
+  @Test
+  public void testDontWarnMainClass() throws Exception {
+    compileWithExpectedDiagnostics(
+        Main.class,
+        TestDiagnosticMessages::assertNoMessages,
+        addDontWarn(Main.class).andThen(this::addKeepMethodRule));
+  }
+
+  @Test
+  public void testDontWarnMissingClass() throws Exception {
+    compileWithExpectedDiagnostics(
+        Main.class,
+        TestDiagnosticMessages::assertNoMessages,
+        addDontWarn(MissingClass.class).andThen(this::addKeepMethodRule));
+  }
+
+  @Test
+  public void testIgnoreWarnings() throws Exception {
+    compileWithExpectedDiagnostics(
+        Main.class,
+        diagnostics -> inspectDiagnosticsWithIgnoreWarnings(diagnostics, referencedFrom),
+        addIgnoreWarnings().andThen(this::addKeepMethodRule));
+  }
+
+  private void addKeepMethodRule(R8FullTestBuilder builder) {
+    builder.addKeepRules(
+        "-keep class " + Main.class.getTypeName() + " {",
+        "  public static void get(" + MissingClass.class.getTypeName() + ");",
+        "}");
+  }
+
+  static class Main {
+
+    public static void main(String[] args) {}
+
+    public static void get(MissingClass mc) {}
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/missingclasses/MissingClassReferencedFromKeptMethodReturnTest.java b/src/test/java/com/android/tools/r8/missingclasses/MissingClassReferencedFromKeptMethodReturnTest.java
new file mode 100644
index 0000000..f211d14
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/missingclasses/MissingClassReferencedFromKeptMethodReturnTest.java
@@ -0,0 +1,77 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.missingclasses;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.R8FullTestBuilder;
+import com.android.tools.r8.TestDiagnosticMessages;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.references.MethodReference;
+import com.android.tools.r8.references.Reference;
+import java.util.Collections;
+import org.junit.Test;
+
+/** If a method definition refers to a missing class, then the method definition is to be blamed. */
+public class MissingClassReferencedFromKeptMethodReturnTest extends MissingClassesTestBase {
+
+  private static final MethodReference referencedFrom =
+      Reference.method(
+          Reference.classFromClass(Main.class),
+          "get",
+          Collections.emptyList(),
+          Reference.classFromClass(MissingClass.class));
+
+  public MissingClassReferencedFromKeptMethodReturnTest(TestParameters parameters) {
+    super(parameters);
+  }
+
+  @Test(expected = CompilationFailedException.class)
+  public void testNoRules() throws Exception {
+    compileWithExpectedDiagnostics(
+        Main.class,
+        diagnostics -> inspectDiagnosticsWithNoRules(diagnostics, referencedFrom),
+        this::addKeepMethodRule);
+  }
+
+  @Test
+  public void testDontWarnMainClass() throws Exception {
+    compileWithExpectedDiagnostics(
+        Main.class,
+        TestDiagnosticMessages::assertNoMessages,
+        addDontWarn(Main.class).andThen(this::addKeepMethodRule));
+  }
+
+  @Test
+  public void testDontWarnMissingClass() throws Exception {
+    compileWithExpectedDiagnostics(
+        Main.class,
+        TestDiagnosticMessages::assertNoMessages,
+        addDontWarn(MissingClass.class).andThen(this::addKeepMethodRule));
+  }
+
+  @Test
+  public void testIgnoreWarnings() throws Exception {
+    compileWithExpectedDiagnostics(
+        Main.class,
+        diagnostics -> inspectDiagnosticsWithIgnoreWarnings(diagnostics, referencedFrom),
+        addIgnoreWarnings().andThen(this::addKeepMethodRule));
+  }
+
+  private void addKeepMethodRule(R8FullTestBuilder builder) {
+    builder.addKeepRules(
+        "-keep class " + Main.class.getTypeName() + " {",
+        "  public static " + MissingClass.class.getTypeName() + " get();",
+        "}");
+  }
+
+  static class Main {
+
+    public static void main(String[] args) {}
+
+    public static MissingClass get() {
+      return null;
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/naming/ProguardMapReaderTest.java b/src/test/java/com/android/tools/r8/naming/ProguardMapReaderTest.java
index 668a8a0..4e56011 100644
--- a/src/test/java/com/android/tools/r8/naming/ProguardMapReaderTest.java
+++ b/src/test/java/com/android/tools/r8/naming/ProguardMapReaderTest.java
@@ -3,13 +3,17 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.naming;
 
+import static com.android.tools.r8.DiagnosticsMatcher.diagnosticMessage;
+import static org.hamcrest.CoreMatchers.containsString;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assert.assertTrue;
 
 import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestDiagnosticMessagesImpl;
 import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.naming.ProguardMapReader.ParseException;
 import com.android.tools.r8.position.Position;
 import com.android.tools.r8.utils.StringUtils;
 import com.google.common.collect.ImmutableList;
@@ -196,4 +200,67 @@
       Assert.assertEquals(s, result);
     }
   }
+
+  @Test()
+  public void testCommentLineBeforeAnyClassMappings() throws IOException {
+    String mapping =
+        StringUtils.lines(
+            "# {'id':'some.namespace.here','unknownField':'Hi There'}", "foo.bar.baz -> a:");
+    ClassNameMapper.mapperFromString(mapping);
+  }
+
+  // TODO(b/179666867): Should not fail.
+  @Test(expected = ParseException.class)
+  public void testCommentLineOnClassMapping() throws IOException {
+    ClassNameMapper.mapperFromString("foo.bar.qux -> b: #  Some comment here");
+  }
+
+  // TODO(b/179666867): Should not fail.
+  @Test(expected = ParseException.class)
+  public void testJsonCommentLineOnClassMapping() throws IOException {
+    ClassNameMapper.mapperFromString(
+        "foo.bar.baz -> a: # {'id':'same.class.namespace.here','frame':'foo'}");
+  }
+
+  // TODO(b/179666867): Should not fail.
+  @Test(expected = ParseException.class)
+  public void testCommentLinesOnMethodMappingFiles() throws IOException {
+    ClassNameMapper.mapperFromString(
+        StringUtils.lines(
+            "foo.bar.qux -> b:",
+            "    1:10:void error(com.android.tools.r8.Diagnostic) -> error # Some comment here"));
+  }
+
+  // TODO(b/179666867): Should not fail.
+  @Test(expected = ParseException.class)
+  public void testJsonCommentLinesOnMethodMappingFiles() throws IOException {
+    ClassNameMapper.mapperFromString(
+        StringUtils.lines(
+            "foo.bar.qux -> b:",
+            "    1:10:void error(com.android.tools.r8.Diagnostic) -> error #"
+                + " {'id':'same.frame.namespace.here','frame':'bar'}"));
+  }
+
+  @Test()
+  public void testUnknownNamespaceComments() throws IOException {
+    String mappingWithComments =
+        StringUtils.lines(
+            "foo.bar.baz -> a:",
+            "# {'id':'some.other.namespace.here','fileName':'Class.kt'}",
+            "    1:10:void error(com.android.tools.r8.Diagnostic) -> error",
+            "# {'id':'some.line.namespace.here','fileName':'Class.kt'}",
+            "foo.bar.qux -> b:",
+            "# {'id':'some.final.namespace.thing','foo':'Hello World'}");
+    TestDiagnosticMessagesImpl testDiagnosticMessages = new TestDiagnosticMessagesImpl();
+    ClassNameMapper.mapperFromString(mappingWithComments, testDiagnosticMessages);
+    testDiagnosticMessages.assertOnlyInfos();
+    testDiagnosticMessages.assertInfosMatch(
+        ImmutableList.of(
+            diagnosticMessage(
+                containsString("Could not find a handler for some.other.namespace.here")),
+            diagnosticMessage(
+                containsString("Could not find a handler for some.line.namespace.here")),
+            diagnosticMessage(
+                containsString("Could not find a handler for some.final.namespace.thing"))));
+  }
 }
diff --git a/src/test/java/com/android/tools/r8/shaking/TreeShakingTest.java b/src/test/java/com/android/tools/r8/shaking/TreeShakingTest.java
index 2f1a112..869deb0 100644
--- a/src/test/java/com/android/tools/r8/shaking/TreeShakingTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/TreeShakingTest.java
@@ -197,9 +197,6 @@
                   if (optionsConsumer != null) {
                     optionsConsumer.accept(options);
                   }
-                  if (frontend == Frontend.DEX) {
-                    options.testing.allowDexInputForTesting = true;
-                  }
                 })
             .allowStdoutMessages()
             .applyIf(testBuilderConsumer != null, testBuilderConsumer);
diff --git a/src/test/java/com/android/tools/r8/shaking/annotations/b137392797/B137392797.java b/src/test/java/com/android/tools/r8/shaking/annotations/b137392797/B137392797.java
index 1112ccd..073a085 100644
--- a/src/test/java/com/android/tools/r8/shaking/annotations/b137392797/B137392797.java
+++ b/src/test/java/com/android/tools/r8/shaking/annotations/b137392797/B137392797.java
@@ -10,6 +10,7 @@
 
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.utils.BooleanUtils;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
@@ -62,7 +63,7 @@
             classWireFieldLabel(),
             classTest(defaultEnumValueInAnnotation))
         .addProgramClasses(TestClass.class)
-        .addDontWarnKotlin()
+        .addClasspathFiles(ToolHelper.getKotlinStdlibJar(ToolHelper.getKotlinC_1_3_72()))
         .addDontWarnJetBrainsAnnotations()
         .addKeepClassAndMembersRules(
             "com.squareup.wire.WireField", "com.squareup.demo.myapplication.Test")
diff --git a/src/test/java/com/android/tools/r8/smali/SmaliTestBase.java b/src/test/java/com/android/tools/r8/smali/SmaliTestBase.java
index 10962c8..000eb7e 100644
--- a/src/test/java/com/android/tools/r8/smali/SmaliTestBase.java
+++ b/src/test/java/com/android/tools/r8/smali/SmaliTestBase.java
@@ -225,14 +225,12 @@
    */
   public DexEncodedMethod oneMethodApplication(String returnType, List<String> parameters,
       int locals, String... instructions) {
-    InternalOptions options = new InternalOptions();
-
     // Build a one class application.
     AndroidApp application = singleMethodApplication(
         returnType, parameters, locals, instructions);
 
     // Process the application with R8.
-    AndroidApp processdApplication = null;
+    AndroidApp processdApplication;
     try {
       processdApplication = processApplication(application);
     } catch (CompilationFailedException e) {
diff --git a/src/test/java/com/android/tools/r8/transformers/ClassFileTransformer.java b/src/test/java/com/android/tools/r8/transformers/ClassFileTransformer.java
index 7b61e06..2f098bf 100644
--- a/src/test/java/com/android/tools/r8/transformers/ClassFileTransformer.java
+++ b/src/test/java/com/android/tools/r8/transformers/ClassFileTransformer.java
@@ -554,6 +554,11 @@
         });
   }
 
+  public ClassFileTransformer removeMethodsWithName(String nameToRemove) {
+    return removeMethods(
+        (access, name, descriptor, signature, exceptions) -> name.equals(nameToRemove));
+  }
+
   public ClassFileTransformer renameMethod(MethodPredicate predicate, String newName) {
     return addClassTransformer(
         new ClassTransformer() {
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/CodeInspector.java b/src/test/java/com/android/tools/r8/utils/codeinspector/CodeInspector.java
index 5f67ef0..c20ccd6 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/CodeInspector.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/CodeInspector.java
@@ -87,6 +87,10 @@
     this(Collections.singletonList(file), null, null);
   }
 
+  public CodeInspector(Path file, Consumer<InternalOptions> optionsConsumer) throws IOException {
+    this(Collections.singletonList(file), null, optionsConsumer);
+  }
+
   public CodeInspector(Collection<Path> files) throws IOException {
     this(files, null, null);
   }
diff --git a/src/test/javaStubs/ObjectMethods.java b/src/test/javaStubs/ObjectMethods.java
new file mode 100644
index 0000000..21aff6a
--- /dev/null
+++ b/src/test/javaStubs/ObjectMethods.java
@@ -0,0 +1,7 @@
+// Copyright (c) 2021, 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 java.lang.runtime;
+
+public class ObjectMethods {}
diff --git a/src/test/javaStubs/Record.java b/src/test/javaStubs/Record.java
new file mode 100644
index 0000000..fcf63e6
--- /dev/null
+++ b/src/test/javaStubs/Record.java
@@ -0,0 +1,18 @@
+// Copyright (c) 2021, 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 java.lang;
+
+public abstract class Record {
+  protected Record() {}
+
+  @Override
+  public abstract boolean equals(Object obj);
+
+  @Override
+  public abstract int hashCode();
+
+  @Override
+  public abstract String toString();
+}
diff --git a/src/test/javaStubs/RecordComponent.java b/src/test/javaStubs/RecordComponent.java
new file mode 100644
index 0000000..b3e0560
--- /dev/null
+++ b/src/test/javaStubs/RecordComponent.java
@@ -0,0 +1,7 @@
+// Copyright (c) 2021, 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 java.lang.reflect;
+
+public class RecordComponent {}
diff --git a/src/test/javaStubs/TypeDescriptor.java b/src/test/javaStubs/TypeDescriptor.java
new file mode 100644
index 0000000..643d1e0
--- /dev/null
+++ b/src/test/javaStubs/TypeDescriptor.java
@@ -0,0 +1,9 @@
+// Copyright (c) 2021, 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 java.lang.invoke;
+
+public interface TypeDescriptor {
+  String descriptorString();
+}
diff --git a/tools/utils.py b/tools/utils.py
index a55ea58..dcfa4f8 100644
--- a/tools/utils.py
+++ b/tools/utils.py
@@ -455,7 +455,7 @@
   cmd = [jdk.GetJavaExecutable(), '-jar', R8_JAR, 'dexsegments']
   cmd.extend(dex_files)
   PrintCmd(cmd)
-  output = subprocess.check_output(cmd)
+  output = subprocess.check_output(cmd).decode('utf-8')
 
   matches = DEX_SEGMENTS_RESULT_PATTERN.findall(output)