Merge commit 'df524d261735db40ca981db5a83bd5b4bb9bd4f6' into dev-release
diff --git a/build.gradle b/build.gradle
index 121279f..f4881c2 100644
--- a/build.gradle
+++ b/build.gradle
@@ -337,6 +337,7 @@
                 "kotlin/kotlin-compiler-1.3.11",
                 "kotlin/kotlin-compiler-1.3.41",
                 "kotlin/kotlin-compiler-1.3.72",
+                "kotlin/kotlin-compiler-1.4.20",
                 "kotlinx-coroutines-1.3.6",
                 "openjdk/openjdk-rt-1.8",
                 "openjdk/desugar_jdk_libs",
@@ -2006,6 +2007,10 @@
         systemProperty 'slow_tests', project.property('slow_tests')
     }
 
+    if (project.hasProperty('desugar_jdk_libs')) {
+        systemProperty 'desugar_jdk_libs', project.property('desugar_jdk_libs')
+    }
+
     if (project.hasProperty('one_line_per_test')) {
         beforeTest { desc ->
             println "Start executing test ${desc.name} [${desc.className}]"
diff --git a/src/library_desugar/desugar_jdk_libs.json b/src/library_desugar/desugar_jdk_libs.json
index 5600b2e..0ba3777 100644
--- a/src/library_desugar/desugar_jdk_libs.json
+++ b/src/library_desugar/desugar_jdk_libs.json
@@ -2,7 +2,7 @@
   "configuration_format_version": 3,
   "group_id" : "com.tools.android",
   "artifact_id" : "desugar_jdk_libs",
-  "version": "1.1.2",
+  "version": "1.1.3",
   "required_compilation_api_level": 26,
   "synthesized_library_classes_package_prefix": "j$.",
   "support_all_callbacks_from_library": true,
@@ -242,6 +242,7 @@
   "shrinker_config": [
     "-keepclassmembers class j$.util.concurrent.ConcurrentHashMap$TreeBin { int lockState; }",
     "-keepclassmembers class j$.util.concurrent.ConcurrentHashMap { int sizeCtl; int transferIndex; long baseCount; int cellsBusy; }",
+    "-keepclassmembers class j$.util.concurrent.ConcurrentHashMap { private void readObject(java.io.ObjectInputStream); private void writeObject(java.io.ObjectOutputStream); private void readObjectNoData(); private static final java.io.ObjectStreamField[] serialPersistentFields; private static final long serialVersionUID;}",
     "-keepclassmembers class j$.util.concurrent.ConcurrentHashMap$CounterCell { long value; }",
     "-keepclassmembers enum * { public static **[] values(); public static ** valueOf(java.lang.String); public static final !synthetic <fields>; }",
     "-keeppackagenames j$.**",
diff --git a/src/main/java/com/android/tools/r8/PrintSeeds.java b/src/main/java/com/android/tools/r8/PrintSeeds.java
index 99fc6db..d938092 100644
--- a/src/main/java/com/android/tools/r8/PrintSeeds.java
+++ b/src/main/java/com/android/tools/r8/PrintSeeds.java
@@ -14,6 +14,7 @@
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.shaking.Enqueuer;
 import com.android.tools.r8.shaking.EnqueuerFactory;
+import com.android.tools.r8.shaking.EnqueuerResult;
 import com.android.tools.r8.shaking.RootSetUtils.RootSet;
 import com.android.tools.r8.shaking.RootSetUtils.RootSetBuilder;
 import com.android.tools.r8.utils.ExceptionUtils;
@@ -94,7 +95,8 @@
               .build(executor);
       Enqueuer enqueuer =
           EnqueuerFactory.createForInitialTreeShaking(appView, executor, subtypingInfo);
-      AppInfoWithLiveness appInfo = enqueuer.traceApplication(rootSet, executor, timing);
+      EnqueuerResult enqueuerResult = enqueuer.traceApplication(rootSet, executor, timing);
+      AppInfoWithLiveness appInfo = enqueuerResult.getAppInfo();
       RootSetBuilder.writeSeeds(
           appInfo, System.out, type -> descriptors.contains(type.toDescriptorString()));
     } catch (ExecutionException e) {
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java
index 283e461..126fb87 100644
--- a/src/main/java/com/android/tools/r8/R8.java
+++ b/src/main/java/com/android/tools/r8/R8.java
@@ -32,7 +32,6 @@
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.DirectMappedDexApplication;
 import com.android.tools.r8.graph.GraphLens;
-import com.android.tools.r8.graph.GraphLens.NestedGraphLens;
 import com.android.tools.r8.graph.InitClassLens;
 import com.android.tools.r8.graph.PrunedItems;
 import com.android.tools.r8.graph.SubtypingInfo;
@@ -88,6 +87,7 @@
 import com.android.tools.r8.shaking.Enqueuer;
 import com.android.tools.r8.shaking.Enqueuer.Mode;
 import com.android.tools.r8.shaking.EnqueuerFactory;
+import com.android.tools.r8.shaking.EnqueuerResult;
 import com.android.tools.r8.shaking.MainDexInfo;
 import com.android.tools.r8.shaking.MainDexListBuilder;
 import com.android.tools.r8.shaking.MissingClasses;
@@ -603,13 +603,10 @@
                   new SubtypingInfo(appView),
                   keptGraphConsumer,
                   prunedTypes);
-          appView.setAppInfo(
-              enqueuer.traceApplication(
-                  appView.rootSet(),
-                  executorService,
-                  timing));
+          EnqueuerResult enqueuerResult =
+              enqueuer.traceApplication(appView.rootSet(), executorService, timing);
+          appView.setAppInfo(enqueuerResult.getAppInfo());
           // Rerunning the enqueuer should not give rise to any method rewritings.
-          assert enqueuer.buildGraphLens() == null;
           appView.withGeneratedMessageLiteBuilderShrinker(
               shrinker ->
                   shrinker.rewriteDeadBuilderReferencesFromDynamicMethods(
@@ -977,14 +974,10 @@
       classMergingEnqueuerExtensionBuilder.attach(enqueuer);
     }
 
+    EnqueuerResult enqueuerResult =
+        enqueuer.traceApplication(appView.rootSet(), executorService, timing);
     AppView<AppInfoWithLiveness> appViewWithLiveness =
-        appView.setAppInfo(
-            enqueuer.traceApplication(
-                appView.rootSet(),
-                executorService,
-                timing));
-    NestedGraphLens lens = enqueuer.buildGraphLens();
-    appView.rewriteWithLens(lens);
+        appView.setAppInfo(enqueuerResult.getAppInfo());
     if (InternalOptions.assertionsEnabled()) {
       // Register the dead proto types. These are needed to verify that no new missing types are
       // reported and that no dead proto types are referenced in the generated application.
diff --git a/src/main/java/com/android/tools/r8/cf/LoadStoreHelper.java b/src/main/java/com/android/tools/r8/cf/LoadStoreHelper.java
index 339f961..faf9046 100644
--- a/src/main/java/com/android/tools/r8/cf/LoadStoreHelper.java
+++ b/src/main/java/com/android/tools/r8/cf/LoadStoreHelper.java
@@ -7,7 +7,6 @@
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.ir.code.BasicBlock;
-import com.android.tools.r8.ir.code.BasicBlock.ThrowingInfo;
 import com.android.tools.r8.ir.code.ConstClass;
 import com.android.tools.r8.ir.code.ConstInstruction;
 import com.android.tools.r8.ir.code.ConstNumber;
@@ -264,15 +263,11 @@
       if (constant.isConstNumber()) {
         return new ConstNumber(stackValue, constant.asConstNumber().getRawValue());
       } else if (constant.isConstString()) {
-        return new ConstString(
-            stackValue, constant.asConstString().getValue(), ThrowingInfo.NO_THROW);
+        return new ConstString(stackValue, constant.asConstString().getValue());
       } else if (constant.isDexItemBasedConstString()) {
         DexItemBasedConstString computedConstant = constant.asDexItemBasedConstString();
         return new DexItemBasedConstString(
-            stackValue,
-            computedConstant.getItem(),
-            computedConstant.getNameComputationInfo(),
-            ThrowingInfo.NO_THROW);
+            stackValue, computedConstant.getItem(), computedConstant.getNameComputationInfo());
       } else if (constant.isConstClass()) {
         return new ConstClass(stackValue, constant.asConstClass().getValue());
       } else {
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfInstruction.java b/src/main/java/com/android/tools/r8/cf/code/CfInstruction.java
index e7ae429..42844fd 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfInstruction.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfInstruction.java
@@ -141,6 +141,18 @@
     return false;
   }
 
+  public CfInvokeDynamic asInvokeDynamic() {
+    return null;
+  }
+
+  public boolean isInvokeDynamic() {
+    return false;
+  }
+
+  public boolean isInvokeSpecial() {
+    return false;
+  }
+
   public CfLabel asLabel() {
     return null;
   }
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 70fe54d..5cc4435 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
@@ -175,6 +175,7 @@
         !method.name.toString().equals(Constants.INSTANCE_INITIALIZER_NAME);
   }
 
+  @Override
   public boolean isInvokeSpecial() {
     return opcode == Opcodes.INVOKESPECIAL;
   }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfInvokeDynamic.java b/src/main/java/com/android/tools/r8/cf/code/CfInvokeDynamic.java
index e65d095..88ffb82 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfInvokeDynamic.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfInvokeDynamic.java
@@ -44,6 +44,16 @@
   }
 
   @Override
+  public boolean isInvokeDynamic() {
+    return true;
+  }
+
+  @Override
+  public CfInvokeDynamic asInvokeDynamic() {
+    return this;
+  }
+
+  @Override
   public int getCompareToId() {
     return Opcodes.INVOKEDYNAMIC;
   }
diff --git a/src/main/java/com/android/tools/r8/diagnostic/MissingDefinitionContext.java b/src/main/java/com/android/tools/r8/diagnostic/MissingDefinitionContext.java
new file mode 100644
index 0000000..268c7cc
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/diagnostic/MissingDefinitionContext.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.diagnostic;
+
+import com.android.tools.r8.Keep;
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.references.ClassReference;
+
+/** A context that references a missing definition in the program, classpath, or library. */
+@Keep
+public interface MissingDefinitionContext {
+
+  /** The class context from which a missing definition is referenced. */
+  ClassReference getClassReference();
+
+  /** The origin of the context. */
+  Origin getOrigin();
+}
diff --git a/src/main/java/com/android/tools/r8/diagnostic/MissingDefinitionInfo.java b/src/main/java/com/android/tools/r8/diagnostic/MissingDefinitionInfo.java
new file mode 100644
index 0000000..ea8def0
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/diagnostic/MissingDefinitionInfo.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 com.android.tools.r8.diagnostic;
+
+import com.android.tools.r8.Keep;
+import java.util.Collection;
+
+/**
+ * Information about the contexts that references an item that was not part of the compilation unit.
+ */
+@Keep
+public interface MissingDefinitionInfo {
+
+  /** The contexts from which this missing definition was referenced. */
+  Collection<MissingDefinitionContext> getReferencedFromContexts();
+}
diff --git a/src/main/java/com/android/tools/r8/diagnostic/MissingDefinitionsDiagnostic.java b/src/main/java/com/android/tools/r8/diagnostic/MissingDefinitionsDiagnostic.java
new file mode 100644
index 0000000..b3111be
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/diagnostic/MissingDefinitionsDiagnostic.java
@@ -0,0 +1,23 @@
+// 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.diagnostic;
+
+import com.android.tools.r8.Diagnostic;
+import com.android.tools.r8.Keep;
+import java.util.Collection;
+
+/**
+ * Information about items that are not part of the compilation unit, but which are referenced from
+ * a reachable program location.
+ */
+@Keep
+public interface MissingDefinitionsDiagnostic extends Diagnostic {
+
+  /**
+   * Returns a collection containing information about each of the missing definitions, along with
+   * contextual information describing where these missing definitions are referenced from.
+   */
+  Collection<MissingDefinitionInfo> getMissingDefinitions();
+}
diff --git a/src/main/java/com/android/tools/r8/diagnostic/internal/MissingClassAccessContexts.java b/src/main/java/com/android/tools/r8/diagnostic/internal/MissingClassAccessContexts.java
new file mode 100644
index 0000000..3406c0d
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/diagnostic/internal/MissingClassAccessContexts.java
@@ -0,0 +1,103 @@
+// 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.diagnostic.internal;
+
+import static com.android.tools.r8.utils.PredicateUtils.not;
+
+import com.android.tools.r8.graph.DexField;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexReference;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.references.ClassReference;
+import com.android.tools.r8.references.FieldReference;
+import com.android.tools.r8.references.MethodReference;
+import com.android.tools.r8.utils.FieldReferenceUtils;
+import com.android.tools.r8.utils.MethodReferenceUtils;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Sets;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Optional;
+import java.util.Set;
+import java.util.function.Function;
+
+public class MissingClassAccessContexts {
+
+  private ImmutableSet<ClassReference> classContexts;
+  private ImmutableSet<FieldReference> fieldContexts;
+  private ImmutableSet<MethodReference> methodContexts;
+
+  private MissingClassAccessContexts(
+      ImmutableSet<ClassReference> classContexts,
+      ImmutableSet<FieldReference> fieldContexts,
+      ImmutableSet<MethodReference> methodContexts) {
+    this.classContexts = classContexts;
+    this.fieldContexts = fieldContexts;
+    this.methodContexts = methodContexts;
+  }
+
+  static Builder builder() {
+    return new Builder();
+  }
+
+  String getReferencedFromMessageSuffix(ClassReference missingClass) {
+    if (!fieldContexts.isEmpty()) {
+      return " (referenced from: "
+          + FieldReferenceUtils.toSourceString(fieldContexts.iterator().next())
+          + ")";
+    }
+    if (!methodContexts.isEmpty()) {
+      return " (referenced from: "
+          + MethodReferenceUtils.toSourceString(methodContexts.iterator().next())
+          + ")";
+    }
+    // TODO(b/175543745): The legacy reporting is context insensitive, and therefore uses the
+    //  missing classes as their own context. Once legacy reporting is removed, this should be
+    //  simplified to taking the first context.
+    Optional<ClassReference> classContext =
+        classContexts.stream().filter(not(missingClass::equals)).findFirst();
+    return classContext
+        .map(classReference -> " (referenced from: " + classReference.getTypeName() + ")")
+        .orElse("");
+  }
+
+  static class Builder {
+
+    private final Set<DexReference> contexts = Sets.newIdentityHashSet();
+
+    Builder addAll(Set<DexReference> contexts) {
+      this.contexts.addAll(contexts);
+      return this;
+    }
+
+    // TODO(b/179249745): Sort on demand in getReferencedFromMessageSuffix() instead.
+    MissingClassAccessContexts build() {
+      // Sort the contexts for deterministic reporting.
+      List<DexType> classContexts = new ArrayList<>();
+      List<DexField> fieldContexts = new ArrayList<>();
+      List<DexMethod> methodContexts = new ArrayList<>();
+      contexts.forEach(
+          context -> context.apply(classContexts::add, fieldContexts::add, methodContexts::add));
+      Collections.sort(classContexts);
+      Collections.sort(fieldContexts);
+      Collections.sort(methodContexts);
+
+      // Build immutable sets (which preserve insertion order) from the sorted lists, mapping each
+      // DexType, DexField, and DexMethod to ClassReference, FieldReference, and MethodReference,
+      // respectively.
+      return new MissingClassAccessContexts(
+          toImmutableSet(classContexts, DexType::asClassReference),
+          toImmutableSet(fieldContexts, DexField::asFieldReference),
+          toImmutableSet(methodContexts, DexMethod::asMethodReference));
+    }
+
+    private <S, T> ImmutableSet<T> toImmutableSet(List<S> list, Function<S, T> fn) {
+      ImmutableSet.Builder<T> builder = ImmutableSet.builder();
+      list.forEach(element -> builder.add(fn.apply(element)));
+      return builder.build();
+    }
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/diagnostic/internal/MissingDefinitionsDiagnosticImpl.java b/src/main/java/com/android/tools/r8/diagnostic/internal/MissingDefinitionsDiagnosticImpl.java
new file mode 100644
index 0000000..044b1d5
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/diagnostic/internal/MissingDefinitionsDiagnosticImpl.java
@@ -0,0 +1,145 @@
+// 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.diagnostic.internal;
+
+import com.android.tools.r8.diagnostic.MissingDefinitionInfo;
+import com.android.tools.r8.diagnostic.MissingDefinitionsDiagnostic;
+import com.android.tools.r8.errors.Unimplemented;
+import com.android.tools.r8.graph.DexReference;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.position.Position;
+import com.android.tools.r8.references.ClassReference;
+import com.android.tools.r8.references.Reference;
+import com.google.common.collect.ImmutableSortedMap;
+import java.util.Collection;
+import java.util.Comparator;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+import java.util.SortedMap;
+
+public class MissingDefinitionsDiagnosticImpl implements MissingDefinitionsDiagnostic {
+
+  private final boolean fatal;
+  private final SortedMap<ClassReference, MissingClassAccessContexts> missingClasses;
+
+  private MissingDefinitionsDiagnosticImpl(
+      boolean fatal, SortedMap<ClassReference, MissingClassAccessContexts> missingClasses) {
+    assert !missingClasses.isEmpty();
+    this.fatal = fatal;
+    this.missingClasses = missingClasses;
+  }
+
+  public static Builder builder() {
+    return new Builder();
+  }
+
+  @Deprecated
+  public Set<ClassReference> getMissingClasses() {
+    return missingClasses.keySet();
+  }
+
+  @Override
+  public Collection<MissingDefinitionInfo> getMissingDefinitions() {
+    throw new Unimplemented();
+  }
+
+  /** A missing class(es) failure can generally not be attributed to a single origin. */
+  @Override
+  public Origin getOrigin() {
+    return Origin.unknown();
+  }
+
+  /** A missing class(es) failure can generally not be attributed to a single position. */
+  @Override
+  public Position getPosition() {
+    return Position.UNKNOWN;
+  }
+
+  @Override
+  public String getDiagnosticMessage() {
+    return fatal ? getFatalDiagnosticMessage() : getNonFatalDiagnosticMessage();
+  }
+
+  private String getFatalDiagnosticMessage() {
+    if (missingClasses.size() == 1) {
+      StringBuilder builder =
+          new StringBuilder(
+              "Compilation can't be completed because the following class is missing: ");
+      writeMissingClass(builder, missingClasses.entrySet().iterator().next());
+      return builder.append(".").toString();
+    }
+
+    StringBuilder builder =
+        new StringBuilder("Compilation can't be completed because the following ")
+            .append(missingClasses.size())
+            .append(" classes are missing:");
+    missingClasses.forEach(
+        (missingClass, contexts) ->
+            writeMissingClass(
+                builder.append(System.lineSeparator()).append("- "), missingClass, contexts));
+    return builder.toString();
+  }
+
+  private String getNonFatalDiagnosticMessage() {
+    StringBuilder builder = new StringBuilder();
+    Iterator<Entry<ClassReference, MissingClassAccessContexts>> missingClassesIterator =
+        missingClasses.entrySet().iterator();
+
+    // The diagnostic is always non-empty.
+    assert missingClassesIterator.hasNext();
+
+    // Write first line.
+    writeMissingClass(builder.append("Missing class "), missingClassesIterator.next());
+
+    // Write remaining lines with line separator before.
+    missingClassesIterator.forEachRemaining(
+        missingClassInfo ->
+            writeMissingClass(
+                builder.append(System.lineSeparator()).append("Missing class "), missingClassInfo));
+
+    return builder.toString();
+  }
+
+  private static void writeMissingClass(
+      StringBuilder builder, Entry<ClassReference, MissingClassAccessContexts> missingClassInfo) {
+    writeMissingClass(builder, missingClassInfo.getKey(), missingClassInfo.getValue());
+  }
+
+  private static void writeMissingClass(
+      StringBuilder builder, ClassReference missingClass, MissingClassAccessContexts contexts) {
+    builder
+        .append(missingClass.getTypeName())
+        .append(contexts.getReferencedFromMessageSuffix(missingClass));
+  }
+
+  public static class Builder {
+
+    private boolean fatal;
+    private ImmutableSortedMap.Builder<ClassReference, MissingClassAccessContexts>
+        missingClassesBuilder =
+            ImmutableSortedMap.orderedBy(Comparator.comparing(ClassReference::getDescriptor));
+
+    public Builder addMissingClasses(Map<DexType, Set<DexReference>> missingClasses) {
+      missingClasses.forEach(
+          (missingClass, contexts) ->
+              missingClassesBuilder.put(
+                  Reference.classFromDescriptor(missingClass.toDescriptorString()),
+                  MissingClassAccessContexts.builder().addAll(contexts).build()));
+      return this;
+    }
+
+    public Builder setFatal(boolean fatal) {
+      this.fatal = fatal;
+      return this;
+    }
+
+    public MissingDefinitionsDiagnostic build() {
+      return new MissingDefinitionsDiagnosticImpl(fatal, missingClassesBuilder.build());
+    }
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/errors/dontwarn/NonEmptyDontWarnConfiguration.java b/src/main/java/com/android/tools/r8/errors/dontwarn/NonEmptyDontWarnConfiguration.java
index e96cb83..89bcb10 100644
--- a/src/main/java/com/android/tools/r8/errors/dontwarn/NonEmptyDontWarnConfiguration.java
+++ b/src/main/java/com/android/tools/r8/errors/dontwarn/NonEmptyDontWarnConfiguration.java
@@ -62,7 +62,7 @@
   public boolean validate(InternalOptions options) {
     assert options.testing.allowUnnecessaryDontWarnWildcards
         || validateNoUnnecessaryDontWarnWildcards();
-    assert options.testing.allowUnusedDontWarnRules || validateNoUnusedDontWarnPatterns();
+    assert options.testing.allowUnusedDontWarnRules || validateNoUnusedDontWarnPatterns(options);
     return true;
   }
 
@@ -79,9 +79,10 @@
     return true;
   }
 
-  public boolean validateNoUnusedDontWarnPatterns() {
+  public boolean validateNoUnusedDontWarnPatterns(InternalOptions options) {
     for (ProguardClassNameList dontWarnPattern : dontWarnPatterns) {
       assert matchedDontWarnPatterns.containsKey(dontWarnPattern)
+              || options.testing.allowedUnusedDontWarnPatterns.contains(dontWarnPattern.toString())
           : "Unexpected unused rule -dontwarn " + dontWarnPattern.toString();
     }
     return true;
diff --git a/src/main/java/com/android/tools/r8/graph/AccessFlags.java b/src/main/java/com/android/tools/r8/graph/AccessFlags.java
index 70d7cdb..68921ca 100644
--- a/src/main/java/com/android/tools/r8/graph/AccessFlags.java
+++ b/src/main/java/com/android/tools/r8/graph/AccessFlags.java
@@ -338,6 +338,10 @@
       return self();
     }
 
+    public B setPublic() {
+      return setPublic(true);
+    }
+
     public B setPublic(boolean value) {
       if (value) {
         flags.setPublic();
diff --git a/src/main/java/com/android/tools/r8/graph/CfCode.java b/src/main/java/com/android/tools/r8/graph/CfCode.java
index 4d45d55..4247d74 100644
--- a/src/main/java/com/android/tools/r8/graph/CfCode.java
+++ b/src/main/java/com/android/tools/r8/graph/CfCode.java
@@ -5,7 +5,6 @@
 
 import static com.android.tools.r8.graph.DexCode.FAKE_THIS_PREFIX;
 import static com.android.tools.r8.graph.DexCode.FAKE_THIS_SUFFIX;
-import static com.android.tools.r8.ir.conversion.CfSourceCode.canThrowHelper;
 import static org.objectweb.asm.Opcodes.ACC_STATIC;
 
 import com.android.tools.r8.cf.CfPrinter;
@@ -803,7 +802,7 @@
         // Check the exceptional edge prior to evaluating the instruction. The local state is stable
         // at this point as store operations are not throwing and the current stack does not
         // affect the exceptional transfer (the exception edge is always a singleton stack).
-        if (canThrowHelper(instruction, appView.options().isGeneratingClassFiles())) {
+        if (instruction.canThrow()) {
           assert !instruction.isStore();
           builder.verifyExceptionEdges();
         }
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 08d2880..2b45551 100644
--- a/src/main/java/com/android/tools/r8/graph/DexClass.java
+++ b/src/main/java/com/android/tools/r8/graph/DexClass.java
@@ -788,6 +788,10 @@
     this.innerClasses = innerClasses;
   }
 
+  public boolean hasEnclosingMethodAttribute() {
+    return enclosingMethod != null;
+  }
+
   public EnclosingMethodAttribute getEnclosingMethodAttribute() {
     return enclosingMethod;
   }
diff --git a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
index 18b0434..ed0339d 100644
--- a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
+++ b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
@@ -4,6 +4,7 @@
 package com.android.tools.r8.graph;
 
 import static com.android.tools.r8.ir.analysis.type.ClassTypeElement.computeLeastUpperBoundOfInterfaces;
+import static com.android.tools.r8.ir.desugar.LambdaClass.LAMBDA_INSTANCE_FIELD_NAME;
 
 import com.android.tools.r8.dex.Constants;
 import com.android.tools.r8.dex.Marker;
@@ -293,6 +294,7 @@
       createString(Constants.TEMPORARY_INSTANCE_INITIALIZER_PREFIX);
 
   public final DexString thisName = createString("this");
+  public final DexString lambdaInstanceFieldName = createString(LAMBDA_INSTANCE_FIELD_NAME);
 
   // As much as possible, R8 should rely on the content of the static enum field, using
   // enumMembers.isValuesFieldCandidate or checking the object state in the optimization info.
diff --git a/src/main/java/com/android/tools/r8/graph/DexMethodHandle.java b/src/main/java/com/android/tools/r8/graph/DexMethodHandle.java
index b369871..0bdb208 100644
--- a/src/main/java/com/android/tools/r8/graph/DexMethodHandle.java
+++ b/src/main/java/com/android/tools/r8/graph/DexMethodHandle.java
@@ -332,6 +332,10 @@
         .withItem(m -> m.rewrittenTarget);
   }
 
+  public Handle toAsmHandle() {
+    return toAsmHandle(NamingLens.getIdentityLens());
+  }
+
   public Handle toAsmHandle(NamingLens lens) {
     String owner;
     String name;
diff --git a/src/main/java/com/android/tools/r8/graph/DexProgramClass.java b/src/main/java/com/android/tools/r8/graph/DexProgramClass.java
index 0fdd6ad..11e5957 100644
--- a/src/main/java/com/android/tools/r8/graph/DexProgramClass.java
+++ b/src/main/java/com/android/tools/r8/graph/DexProgramClass.java
@@ -209,6 +209,10 @@
         predicate, method -> consumer.accept(new ProgramMethod(this, method)));
   }
 
+  public Iterable<ProgramMethod> programMethods() {
+    return Iterables.concat(directProgramMethods(), virtualProgramMethods());
+  }
+
   public Iterable<ProgramMethod> directProgramMethods() {
     return Iterables.transform(directMethods(), method -> new ProgramMethod(this, method));
   }
diff --git a/src/main/java/com/android/tools/r8/graph/DexValue.java b/src/main/java/com/android/tools/r8/graph/DexValue.java
index 3c17c60..52a57bf 100644
--- a/src/main/java/com/android/tools/r8/graph/DexValue.java
+++ b/src/main/java/com/android/tools/r8/graph/DexValue.java
@@ -14,7 +14,6 @@
 import com.android.tools.r8.ir.analysis.value.AbstractValue;
 import com.android.tools.r8.ir.analysis.value.AbstractValueFactory;
 import com.android.tools.r8.ir.analysis.value.UnknownValue;
-import com.android.tools.r8.ir.code.BasicBlock.ThrowingInfo;
 import com.android.tools.r8.ir.code.ConstInstruction;
 import com.android.tools.r8.ir.code.ConstString;
 import com.android.tools.r8.ir.code.DexItemBasedConstString;
@@ -1253,8 +1252,7 @@
         AppView<? extends AppInfoWithClassHierarchy> appView, IRCode code, DebugLocalInfo local) {
       TypeElement type = TypeElement.stringClassType(appView, definitelyNotNull());
       Value outValue = code.createValue(type, local);
-      ConstString instruction =
-          new ConstString(outValue, value, ThrowingInfo.defaultForConstString(appView.options()));
+      ConstString instruction = new ConstString(outValue, value);
       if (!instruction.instructionInstanceCanThrow()) {
         return instruction;
       }
@@ -1346,11 +1344,7 @@
       TypeElement type = TypeElement.stringClassType(appView, definitelyNotNull());
       Value outValue = code.createValue(type, local);
       DexItemBasedConstString instruction =
-          new DexItemBasedConstString(
-              outValue,
-              value,
-              nameComputationInfo,
-              ThrowingInfo.defaultForConstString(appView.options()));
+          new DexItemBasedConstString(outValue, value, nameComputationInfo);
       // DexItemBasedConstString cannot throw.
       assert !instruction.instructionInstanceCanThrow();
       return instruction;
diff --git a/src/main/java/com/android/tools/r8/graph/GenericSignature.java b/src/main/java/com/android/tools/r8/graph/GenericSignature.java
index a92dc75..1dc1f4f 100644
--- a/src/main/java/com/android/tools/r8/graph/GenericSignature.java
+++ b/src/main/java/com/android/tools/r8/graph/GenericSignature.java
@@ -134,6 +134,12 @@
     default boolean hasNoSignature() {
       return !hasSignature();
     }
+
+    default boolean isInvalid() {
+      return false;
+    }
+
+    DexDefinitionSignature<T> toInvalid();
   }
 
   public static class FormalTypeParameter {
@@ -207,6 +213,12 @@
     }
 
     @Override
+    public InvalidClassSignature toInvalid() {
+      // Since we could create the structure we are also able to generate a string for it.
+      return new InvalidClassSignature(toString());
+    }
+
+    @Override
     public boolean isClassSignature() {
       return true;
     }
@@ -216,6 +228,10 @@
       return this;
     }
 
+    public List<FormalTypeParameter> getFormalTypeParameters() {
+      return formalTypeParameters;
+    }
+
     public void visit(GenericSignatureVisitor visitor) {
       visitor.visitFormalTypeParameters(formalTypeParameters);
       visitor.visitSuperClass(superClassSignature);
@@ -244,6 +260,42 @@
     }
   }
 
+  private static class InvalidClassSignature extends ClassSignature {
+
+    private final String genericSignatureString;
+
+    InvalidClassSignature(String genericSignatureString) {
+      super(EMPTY_TYPE_PARAMS, NO_FIELD_TYPE_SIGNATURE, EMPTY_SUPER_INTERFACES);
+      this.genericSignatureString = genericSignatureString;
+    }
+
+    @Override
+    public String toRenamedString(NamingLens namingLens, Predicate<DexType> isTypeMissing) {
+      return genericSignatureString;
+    }
+
+    @Override
+    public String toString() {
+      return genericSignatureString;
+    }
+
+    @Override
+    public InvalidClassSignature toInvalid() {
+      assert false : "Should not invoke toInvalid on an invalid signature";
+      return this;
+    }
+
+    @Override
+    public void visit(GenericSignatureVisitor visitor) {
+      assert false : "Should not visit an invalid signature";
+    }
+
+    @Override
+    public boolean isInvalid() {
+      return true;
+    }
+  }
+
   public abstract static class TypeSignature {
 
     public boolean isFieldTypeSignature() {
@@ -354,6 +406,43 @@
     public static FieldTypeSignature noSignature() {
       return NO_FIELD_TYPE_SIGNATURE;
     }
+
+    @Override
+    public InvalidFieldTypeSignature toInvalid() {
+      return new InvalidFieldTypeSignature(toString());
+    }
+  }
+
+  private static class InvalidFieldTypeSignature extends FieldTypeSignature {
+
+    private final String genericSignature;
+
+    public InvalidFieldTypeSignature(String genericSignature) {
+      super(WildcardIndicator.NONE);
+      this.genericSignature = genericSignature;
+    }
+
+    @Override
+    public FieldTypeSignature asArgument(WildcardIndicator indicator) {
+      assert false : "Should not be called for an invalid signature";
+      return this;
+    }
+
+    @Override
+    public String toString() {
+      return genericSignature;
+    }
+
+    @Override
+    public String toRenamedString(NamingLens namingLens, Predicate<DexType> isTypeMissing) {
+      return genericSignature;
+    }
+
+    @Override
+    public InvalidFieldTypeSignature toInvalid() {
+      assert false : " Should not be called for an invalid signature";
+      return this;
+    }
   }
 
   static final class StarFieldTypeSignature extends FieldTypeSignature {
@@ -436,10 +525,6 @@
       return argument;
     }
 
-    public boolean isNoSignature() {
-      return this == NO_FIELD_TYPE_SIGNATURE;
-    }
-
     @Override
     public ArrayTypeSignature toArrayTypeSignature() {
       return new ArrayTypeSignature(this);
@@ -538,7 +623,7 @@
       return new ArrayTypeSignature(this);
     }
 
-    public String getTypeVariable() {
+    public String typeVariable() {
       return typeVariable;
     }
   }
@@ -674,6 +759,47 @@
     public String toString() {
       return toRenamedString(NamingLens.getIdentityLens(), alwaysTrue());
     }
+
+    @Override
+    public InvalidMethodTypeSignature toInvalid() {
+      return new InvalidMethodTypeSignature(toString());
+    }
+  }
+
+  private static class InvalidMethodTypeSignature extends MethodTypeSignature {
+
+    private final String genericSignature;
+
+    public InvalidMethodTypeSignature(String genericSignature) {
+      super(EMPTY_TYPE_PARAMS, EMPTY_TYPE_SIGNATURES, ReturnType.VOID, EMPTY_TYPE_SIGNATURES);
+      this.genericSignature = genericSignature;
+    }
+
+    @Override
+    public String toRenamedString(NamingLens namingLens, Predicate<DexType> isTypeMissing) {
+      return genericSignature;
+    }
+
+    @Override
+    public String toString() {
+      return genericSignature;
+    }
+
+    @Override
+    public boolean isInvalid() {
+      return true;
+    }
+
+    @Override
+    public void visit(GenericSignatureVisitor visitor) {
+      assert false : "Should not visit an invalid signature";
+    }
+
+    @Override
+    public InvalidMethodTypeSignature toInvalid() {
+      assert false : "Should not be called for an invalid signature";
+      return this;
+    }
   }
 
   public static ClassSignature parseClassSignature(
diff --git a/src/main/java/com/android/tools/r8/graph/GenericSignatureCorrectnessHelper.java b/src/main/java/com/android/tools/r8/graph/GenericSignatureCorrectnessHelper.java
new file mode 100644
index 0000000..a7979f6
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/graph/GenericSignatureCorrectnessHelper.java
@@ -0,0 +1,305 @@
+// 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.graph;
+
+import static com.android.tools.r8.graph.GenericSignatureCorrectnessHelper.SignatureEvaluationResult.INVALID_APPLICATION_COUNT;
+import static com.android.tools.r8.graph.GenericSignatureCorrectnessHelper.SignatureEvaluationResult.INVALID_INTERFACE_COUNT;
+import static com.android.tools.r8.graph.GenericSignatureCorrectnessHelper.SignatureEvaluationResult.INVALID_SUPER_TYPE;
+import static com.android.tools.r8.graph.GenericSignatureCorrectnessHelper.SignatureEvaluationResult.INVALID_TYPE_VARIABLE_UNDEFINED;
+import static com.android.tools.r8.graph.GenericSignatureCorrectnessHelper.SignatureEvaluationResult.VALID;
+
+import com.android.tools.r8.graph.GenericSignature.ClassSignature;
+import com.android.tools.r8.graph.GenericSignature.ClassTypeSignature;
+import com.android.tools.r8.graph.GenericSignature.DexDefinitionSignature;
+import com.android.tools.r8.graph.GenericSignature.FieldTypeSignature;
+import com.android.tools.r8.graph.GenericSignature.FormalTypeParameter;
+import com.android.tools.r8.graph.GenericSignature.MethodTypeSignature;
+import com.android.tools.r8.graph.GenericSignature.ReturnType;
+import com.android.tools.r8.graph.GenericSignature.TypeSignature;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.function.Consumer;
+import java.util.function.Function;
+import java.util.function.Supplier;
+
+public class GenericSignatureCorrectnessHelper {
+
+  private enum Mode {
+    VERIFY,
+    MARK_AS_INVALID;
+
+    public boolean doNotVerify() {
+      return markAsInvalid();
+    }
+
+    public boolean markAsInvalid() {
+      return this == MARK_AS_INVALID;
+    }
+  }
+
+  public enum SignatureEvaluationResult {
+    INVALID_SUPER_TYPE,
+    INVALID_INTERFACE_COUNT,
+    INVALID_APPLICATION_COUNT,
+    INVALID_TYPE_VARIABLE_UNDEFINED,
+    VALID;
+
+    boolean isValid() {
+      return this == VALID;
+    }
+
+    boolean isInvalid() {
+      return this != VALID;
+    }
+  }
+
+  private final AppView<?> appView;
+  private final Mode mode;
+
+  private GenericSignatureCorrectnessHelper(AppView<?> appView, Mode mode) {
+    this.appView = appView;
+    this.mode = mode;
+  }
+
+  public static GenericSignatureCorrectnessHelper createForInitialCheck(AppView<?> appView) {
+    return new GenericSignatureCorrectnessHelper(appView, Mode.MARK_AS_INVALID);
+  }
+
+  public static GenericSignatureCorrectnessHelper createForVerification(AppView<?> appView) {
+    return new GenericSignatureCorrectnessHelper(appView, Mode.VERIFY);
+  }
+
+  public void run() {
+    appView.appInfo().classes().forEach(this::evaluateSignaturesForClass);
+  }
+
+  public SignatureEvaluationResult evaluateSignaturesForClass(DexProgramClass clazz) {
+    GenericSignatureContextEvaluator genericSignatureContextEvaluator =
+        new GenericSignatureContextEvaluator(appView, clazz, mode);
+    ClassSignature classSignature = clazz.getClassSignature();
+    SignatureEvaluationResult result = VALID;
+    if (classSignature.hasNoSignature() || !classSignature.isInvalid()) {
+      result = genericSignatureContextEvaluator.evaluateClassSignature(classSignature);
+      if (result.isInvalid() && mode.markAsInvalid()) {
+        clazz.setClassSignature(classSignature.toInvalid());
+      }
+    }
+    for (DexEncodedMethod method : clazz.methods()) {
+      SignatureEvaluationResult methodResult =
+          evaluate(
+              method::getGenericSignature,
+              genericSignatureContextEvaluator::visitMethodSignature,
+              method::setGenericSignature);
+      if (result.isValid() && methodResult.isInvalid()) {
+        result = methodResult;
+      }
+    }
+    for (DexEncodedField field : clazz.fields()) {
+      SignatureEvaluationResult fieldResult =
+          evaluate(
+              field::getGenericSignature,
+              genericSignatureContextEvaluator::visitFieldTypeSignature,
+              field::setGenericSignature);
+      if (result.isValid() && fieldResult.isInvalid()) {
+        result = fieldResult;
+      }
+    }
+    return result;
+  }
+
+  private <T extends DexDefinitionSignature<?>> SignatureEvaluationResult evaluate(
+      Supplier<T> getter, Function<T, SignatureEvaluationResult> evaluate, Consumer<T> setter) {
+    T signature = getter.get();
+    if (signature.hasNoSignature() || signature.isInvalid()) {
+      // Already marked as invalid, do nothing
+      return VALID;
+    }
+    SignatureEvaluationResult signatureResult = evaluate.apply(signature);
+    assert signatureResult.isValid() || mode.doNotVerify();
+    if (signatureResult.isInvalid() && mode.doNotVerify()) {
+      setter.accept((T) signature.toInvalid());
+    }
+    return signatureResult;
+  }
+
+  private static class GenericSignatureContextEvaluator {
+
+    private final AppView<?> appView;
+    private final DexProgramClass context;
+    private final Set<String> classFormalTypeParameters = new HashSet<>();
+    private final Set<String> methodTypeArguments = new HashSet<>();
+    private final Mode mode;
+
+    public GenericSignatureContextEvaluator(
+        AppView<?> appView, DexProgramClass context, Mode mode) {
+      this.appView = appView;
+      this.context = context;
+      this.mode = mode;
+    }
+
+    private SignatureEvaluationResult evaluateClassSignature(ClassSignature classSignature) {
+      classSignature
+          .getFormalTypeParameters()
+          .forEach(param -> classFormalTypeParameters.add(param.name));
+      if (classSignature.hasNoSignature()) {
+        return VALID;
+      }
+      SignatureEvaluationResult signatureEvaluationResult =
+          evaluateFormalTypeParameters(classSignature.formalTypeParameters);
+      if (signatureEvaluationResult.isInvalid()) {
+        return signatureEvaluationResult;
+      }
+      if ((context.superType != appView.dexItemFactory().objectType
+              && context.superType != classSignature.superClassSignature().type())
+          || (context.superType == appView.dexItemFactory().objectType
+              && classSignature.superClassSignature().hasNoSignature())) {
+        assert mode.doNotVerify();
+        return INVALID_SUPER_TYPE;
+      }
+      signatureEvaluationResult =
+          evaluateTypeArgumentsAppliedToType(
+              classSignature.superClassSignature().typeArguments(), context.superType);
+      if (signatureEvaluationResult.isInvalid()) {
+        return signatureEvaluationResult;
+      }
+      List<ClassTypeSignature> superInterfaces = classSignature.superInterfaceSignatures();
+      if (context.interfaces.size() != superInterfaces.size()) {
+        assert mode.doNotVerify();
+        return INVALID_INTERFACE_COUNT;
+      }
+      DexType[] actualInterfaces = context.interfaces.values;
+      for (int i = 0; i < actualInterfaces.length; i++) {
+        signatureEvaluationResult =
+            evaluateTypeArgumentsAppliedToType(
+                superInterfaces.get(i).typeArguments(), actualInterfaces[i]);
+        if (signatureEvaluationResult.isInvalid()) {
+          return signatureEvaluationResult;
+        }
+      }
+      return VALID;
+    }
+
+    private SignatureEvaluationResult visitMethodSignature(MethodTypeSignature methodSignature) {
+      methodSignature
+          .getFormalTypeParameters()
+          .forEach(param -> methodTypeArguments.add(param.name));
+      SignatureEvaluationResult evaluateResult =
+          evaluateFormalTypeParameters(methodSignature.getFormalTypeParameters());
+      if (evaluateResult.isInvalid()) {
+        return evaluateResult;
+      }
+      evaluateResult = evaluateTypeArguments(methodSignature.typeSignatures);
+      if (evaluateResult.isInvalid()) {
+        return evaluateResult;
+      }
+      evaluateResult = evaluateTypeArguments(methodSignature.throwsSignatures);
+      if (evaluateResult.isInvalid()) {
+        return evaluateResult;
+      }
+      ReturnType returnType = methodSignature.returnType();
+      if (!returnType.isVoidDescriptor()) {
+        evaluateResult = evaluateTypeArgument(returnType.typeSignature());
+        if (evaluateResult.isInvalid()) {
+          return evaluateResult;
+        }
+      }
+      methodTypeArguments.clear();
+      return evaluateResult;
+    }
+
+    private SignatureEvaluationResult evaluateTypeArguments(List<TypeSignature> typeSignatures) {
+      for (TypeSignature typeSignature : typeSignatures) {
+        SignatureEvaluationResult signatureEvaluationResult = evaluateTypeArgument(typeSignature);
+        if (signatureEvaluationResult.isInvalid()) {
+          return signatureEvaluationResult;
+        }
+      }
+      return VALID;
+    }
+
+    private SignatureEvaluationResult visitFieldTypeSignature(FieldTypeSignature fieldSignature) {
+      return evaluateTypeArgument(fieldSignature);
+    }
+
+    private SignatureEvaluationResult evaluateFormalTypeParameters(
+        List<FormalTypeParameter> typeParameters) {
+      for (FormalTypeParameter typeParameter : typeParameters) {
+        SignatureEvaluationResult evaluationResult = evaluateTypeParameter(typeParameter);
+        if (evaluationResult.isInvalid()) {
+          return evaluationResult;
+        }
+      }
+      return VALID;
+    }
+
+    private SignatureEvaluationResult evaluateTypeParameter(FormalTypeParameter typeParameter) {
+      SignatureEvaluationResult evaluationResult = evaluateTypeArgument(typeParameter.classBound);
+      if (evaluationResult.isInvalid()) {
+        return evaluationResult;
+      }
+      if (typeParameter.interfaceBounds != null) {
+        for (FieldTypeSignature interfaceBound : typeParameter.interfaceBounds) {
+          evaluationResult = evaluateTypeArgument(interfaceBound);
+          if (evaluationResult != VALID) {
+            return evaluationResult;
+          }
+        }
+      }
+      return VALID;
+    }
+
+    private SignatureEvaluationResult evaluateTypeArgument(TypeSignature typeSignature) {
+      if (typeSignature.isBaseTypeSignature()) {
+        return VALID;
+      }
+      FieldTypeSignature fieldTypeSignature = typeSignature.asFieldTypeSignature();
+      if (fieldTypeSignature.hasNoSignature()) {
+        return VALID;
+      }
+      if (fieldTypeSignature.isTypeVariableSignature()) {
+        // This is in an applied position, just check that the variable is registered.
+        String typeVariable = fieldTypeSignature.asTypeVariableSignature().typeVariable();
+        if (classFormalTypeParameters.contains(typeVariable)
+            || methodTypeArguments.contains(typeVariable)) {
+          return VALID;
+        }
+        assert mode.doNotVerify();
+        return INVALID_TYPE_VARIABLE_UNDEFINED;
+      }
+      if (fieldTypeSignature.isArrayTypeSignature()) {
+        return evaluateTypeArgument(fieldTypeSignature.asArrayTypeSignature().elementSignature());
+      }
+      assert fieldTypeSignature.isClassTypeSignature();
+      return evaluateTypeArguments(fieldTypeSignature.asClassTypeSignature());
+    }
+
+    private SignatureEvaluationResult evaluateTypeArguments(ClassTypeSignature classTypeSignature) {
+      return evaluateTypeArgumentsAppliedToType(
+          classTypeSignature.typeArguments, classTypeSignature.type());
+    }
+
+    private SignatureEvaluationResult evaluateTypeArgumentsAppliedToType(
+        List<FieldTypeSignature> typeArguments, DexType type) {
+      for (FieldTypeSignature typeArgument : typeArguments) {
+        SignatureEvaluationResult evaluationResult = evaluateTypeArgument(typeArgument);
+        if (evaluationResult.isInvalid()) {
+          assert mode.doNotVerify();
+          return evaluationResult;
+        }
+      }
+      DexClass clazz = appView.definitionFor(type);
+      if (clazz == null) {
+        // We do not know if the application of arguments works or not.
+        return VALID;
+      }
+      if (typeArguments.size() != clazz.classSignature.getFormalTypeParameters().size()) {
+        assert mode.doNotVerify();
+        return INVALID_APPLICATION_COUNT;
+      }
+      return VALID;
+    }
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/graph/GenericSignaturePrinter.java b/src/main/java/com/android/tools/r8/graph/GenericSignaturePrinter.java
index f68675f..a670da0 100644
--- a/src/main/java/com/android/tools/r8/graph/GenericSignaturePrinter.java
+++ b/src/main/java/com/android/tools/r8/graph/GenericSignaturePrinter.java
@@ -148,7 +148,7 @@
     } else {
       assert fieldTypeSignature.isClassTypeSignature();
       ClassTypeSignature classTypeSignature = fieldTypeSignature.asClassTypeSignature();
-      if (classTypeSignature.isNoSignature()) {
+      if (classTypeSignature.hasNoSignature()) {
         return;
       }
       String renamedString = namingLens.lookupDescriptor(classTypeSignature.type).toString();
diff --git a/src/main/java/com/android/tools/r8/graph/GenericSignatureTypeRewriter.java b/src/main/java/com/android/tools/r8/graph/GenericSignatureTypeRewriter.java
index fd77b73..d20833f 100644
--- a/src/main/java/com/android/tools/r8/graph/GenericSignatureTypeRewriter.java
+++ b/src/main/java/com/android/tools/r8/graph/GenericSignatureTypeRewriter.java
@@ -57,14 +57,14 @@
   }
 
   public ClassSignature rewrite(ClassSignature classSignature) {
-    if (classSignature.hasNoSignature()) {
+    if (classSignature.hasNoSignature() || classSignature.isInvalid()) {
       return classSignature;
     }
     return new ClassSignatureRewriter().run(classSignature);
   }
 
   public FieldTypeSignature rewrite(FieldTypeSignature fieldTypeSignature) {
-    if (fieldTypeSignature.hasNoSignature()) {
+    if (fieldTypeSignature.hasNoSignature() || fieldTypeSignature.isInvalid()) {
       return fieldTypeSignature;
     }
     FieldTypeSignature rewrittenSignature = new TypeSignatureRewriter().run(fieldTypeSignature);
@@ -72,7 +72,7 @@
   }
 
   public MethodTypeSignature rewrite(MethodTypeSignature methodTypeSignature) {
-    if (methodTypeSignature.hasNoSignature()) {
+    if (methodTypeSignature.hasNoSignature() || methodTypeSignature.isInvalid()) {
       return methodTypeSignature;
     }
     return new MethodTypeSignatureRewriter().run(methodTypeSignature);
@@ -117,7 +117,7 @@
       classSignature.visit(this);
       if (rewrittenTypeParameters.isEmpty()
           && rewrittenSuperInterfaces.isEmpty()
-          && rewrittenSuperClass.isNoSignature()
+          && rewrittenSuperClass.hasNoSignature()
           && rewrittenSuperClass.type == factory.objectType) {
         return ClassSignature.noSignature();
       }
@@ -255,7 +255,7 @@
       }
       assert fieldTypeSignature.isClassTypeSignature();
       ClassTypeSignature classTypeSignature = fieldTypeSignature.asClassTypeSignature();
-      if (classTypeSignature.isNoSignature()) {
+      if (classTypeSignature.hasNoSignature()) {
         return classTypeSignature;
       }
       return new ClassTypeSignatureRewriter(false).run(classTypeSignature);
diff --git a/src/main/java/com/android/tools/r8/graph/GraphLens.java b/src/main/java/com/android/tools/r8/graph/GraphLens.java
index 56d3f52..a296543 100644
--- a/src/main/java/com/android/tools/r8/graph/GraphLens.java
+++ b/src/main/java/com/android/tools/r8/graph/GraphLens.java
@@ -5,7 +5,6 @@
 
 import static com.android.tools.r8.graph.DexProgramClass.asProgramClassOrNull;
 import static com.android.tools.r8.horizontalclassmerging.ClassMerger.CLASS_ID_FIELD_NAME;
-import static com.android.tools.r8.ir.desugar.LambdaRewriter.LAMBDA_INSTANCE_FIELD_NAME;
 
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.ir.code.Invoke.Type;
@@ -640,14 +639,15 @@
   private boolean isD8R8SynthesizedField(DexField field, AppView<?> appView) {
     // TODO(b/167947782): Should be a general check to see if the field is D8/R8 synthesized
     //  instead of relying on field names.
-    if (field.match(appView.dexItemFactory().objectMembers.clinitField)) {
+    DexItemFactory dexItemFactory = appView.dexItemFactory();
+    if (field.match(dexItemFactory.objectMembers.clinitField)) {
       return true;
     }
     if (field.getName().toSourceString().equals(CLASS_ID_FIELD_NAME)) {
       return true;
     }
     if (appView.getSyntheticItems().isNonLegacySynthetic(field.getHolderType())
-        && field.getName().toSourceString().equals(LAMBDA_INSTANCE_FIELD_NAME)) {
+        && field.getName() == dexItemFactory.lambdaInstanceFieldName) {
       return true;
     }
     return false;
@@ -660,6 +660,10 @@
 
     private final Map<DexType, DexType> arrayTypeCache = new ConcurrentHashMap<>();
 
+    public NonIdentityGraphLens(AppView<?> appView) {
+      this(appView.dexItemFactory(), appView.graphLens());
+    }
+
     public NonIdentityGraphLens(DexItemFactory dexItemFactory, GraphLens previousLens) {
       this.dexItemFactory = dexItemFactory;
       this.previousLens = previousLens;
diff --git a/src/main/java/com/android/tools/r8/graph/ProgramPackage.java b/src/main/java/com/android/tools/r8/graph/ProgramPackage.java
index 81627e9..272f1e9 100644
--- a/src/main/java/com/android/tools/r8/graph/ProgramPackage.java
+++ b/src/main/java/com/android/tools/r8/graph/ProgramPackage.java
@@ -4,6 +4,7 @@
 
 package com.android.tools.r8.graph;
 
+import com.android.tools.r8.utils.DescriptorUtils;
 import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Sets;
 import java.util.Iterator;
@@ -47,6 +48,10 @@
     return packageDescriptor;
   }
 
+  public String getPackageName() {
+    return DescriptorUtils.getJavaTypeFromBinaryName(packageDescriptor);
+  }
+
   public void forEachClass(Consumer<DexProgramClass> consumer) {
     forEach(consumer);
   }
@@ -67,4 +72,9 @@
   public Iterator<DexProgramClass> iterator() {
     return classes.iterator();
   }
+
+  @Override
+  public String toString() {
+    return "ProgramPackage(" + getPackageName() + ")";
+  }
 }
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 ab3673e..1053b49 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
@@ -18,7 +18,6 @@
 import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.code.ArrayPut;
 import com.android.tools.r8.ir.code.BasicBlock;
-import com.android.tools.r8.ir.code.BasicBlock.ThrowingInfo;
 import com.android.tools.r8.ir.code.ConstString;
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.IRCodeUtils;
@@ -45,7 +44,6 @@
   private final RawMessageInfoDecoder decoder;
   private final RawMessageInfoEncoder encoder;
   private final ProtoReferences references;
-  private final ThrowingInfo throwingInfo;
 
   private final TypeElement objectArrayType;
   private final TypeElement stringType;
@@ -58,7 +56,6 @@
     this.decoder = decoder;
     this.encoder = new RawMessageInfoEncoder(appView.dexItemFactory());
     this.references = references;
-    this.throwingInfo = ThrowingInfo.defaultForConstString(appView.options());
 
     // Types.
     this.objectArrayType =
@@ -154,9 +151,7 @@
   private void rewriteInfoArgumentToNewMessageInfo(
       IRCode code, Value infoValue, ProtoMessageInfo protoMessageInfo) {
     infoValue.definition.replace(
-        new ConstString(
-            code.createValue(stringType), encoder.encodeInfo(protoMessageInfo), throwingInfo),
-        code);
+        new ConstString(code.createValue(stringType), encoder.encodeInfo(protoMessageInfo)), code);
   }
 
   private void rewriteObjectsArgumentToNewMessageInfo(
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/proto/schema/LiveProtoFieldObject.java b/src/main/java/com/android/tools/r8/ir/analysis/proto/schema/LiveProtoFieldObject.java
index 308ea07..4727869 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/proto/schema/LiveProtoFieldObject.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/proto/schema/LiveProtoFieldObject.java
@@ -34,10 +34,9 @@
         code.createValue(TypeElement.stringClassType(appView, Nullability.definitelyNotNull()));
     ThrowingInfo throwingInfo = ThrowingInfo.defaultForConstString(appView.options());
     if (appView.options().isMinifying()) {
-      return new DexItemBasedConstString(
-          value, field, FieldNameComputationInfo.forFieldName(), throwingInfo);
+      return new DexItemBasedConstString(value, field, FieldNameComputationInfo.forFieldName());
     }
-    return new ConstString(value, field.name, throwingInfo);
+    return new ConstString(value, field.name);
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/proto/schema/ProtoEnqueuerExtension.java b/src/main/java/com/android/tools/r8/ir/analysis/proto/schema/ProtoEnqueuerExtension.java
index 24b17fb..eb00100 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/proto/schema/ProtoEnqueuerExtension.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/proto/schema/ProtoEnqueuerExtension.java
@@ -381,7 +381,7 @@
           // Map/required fields cannot be removed. Therefore, we mark such fields as both read and
           // written such that we cannot optimize any field reads or writes.
           enqueuer.registerReflectiveFieldAccess(valueStorage.getReference(), dynamicMethod);
-          worklist.enqueueMarkInstanceFieldAsReachableAction(
+          worklist.enqueueMarkFieldAsReachableAction(
               valueStorage, dynamicMethod, KeepReason.reflectiveUseIn(dynamicMethod));
           valueStorageIsLive = true;
         } else {
@@ -446,7 +446,7 @@
           // Unconditionally register the hazzer and one-of proto fields as written from
           // dynamicMethod().
           if (enqueuer.registerReflectiveFieldWrite(newlyLiveField.getReference(), dynamicMethod)) {
-            worklist.enqueueMarkInstanceFieldAsReachableAction(
+            worklist.enqueueMarkFieldAsReachableAction(
                 newlyLiveField, dynamicMethod, KeepReason.reflectiveUseIn(dynamicMethod));
           }
         }
@@ -566,7 +566,7 @@
     }
 
     if (enqueuer.registerReflectiveFieldWrite(oneOfField.getReference(), dynamicMethod)) {
-      worklist.enqueueMarkInstanceFieldAsReachableAction(
+      worklist.enqueueMarkFieldAsReachableAction(
           oneOfField, dynamicMethod, KeepReason.reflectiveUseIn(dynamicMethod));
     }
   }
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/value/SingleDexItemBasedStringValue.java b/src/main/java/com/android/tools/r8/ir/analysis/value/SingleDexItemBasedStringValue.java
index 88ea973..f2d8962 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/value/SingleDexItemBasedStringValue.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/value/SingleDexItemBasedStringValue.java
@@ -14,7 +14,6 @@
 import com.android.tools.r8.graph.GraphLens;
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.analysis.type.TypeElement;
-import com.android.tools.r8.ir.code.BasicBlock.ThrowingInfo;
 import com.android.tools.r8.ir.code.DexItemBasedConstString;
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.Instruction;
@@ -78,11 +77,7 @@
     Value returnedValue =
         code.createValue(stringClassType(appView, definitelyNotNull()), debugLocalInfo);
     DexItemBasedConstString instruction =
-        new DexItemBasedConstString(
-            returnedValue,
-            item,
-            nameComputationInfo,
-            ThrowingInfo.defaultForConstString(appView.options()));
+        new DexItemBasedConstString(returnedValue, item, nameComputationInfo);
     assert !instruction.instructionInstanceCanThrow();
     return instruction;
   }
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/value/SingleStringValue.java b/src/main/java/com/android/tools/r8/ir/analysis/value/SingleStringValue.java
index e494c43..cd0c9e9 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/value/SingleStringValue.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/value/SingleStringValue.java
@@ -14,7 +14,6 @@
 import com.android.tools.r8.graph.GraphLens;
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.analysis.type.TypeElement;
-import com.android.tools.r8.ir.code.BasicBlock.ThrowingInfo;
 import com.android.tools.r8.ir.code.ConstString;
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.Instruction;
@@ -73,9 +72,7 @@
         .isTrue();
     Value returnedValue =
         code.createValue(stringClassType(appView, definitelyNotNull()), debugLocalInfo);
-    ConstString instruction =
-        new ConstString(
-            returnedValue, string, ThrowingInfo.defaultForConstString(appView.options()));
+    ConstString instruction = new ConstString(returnedValue, string);
     assert !instruction.instructionInstanceCanThrow();
     return instruction;
   }
diff --git a/src/main/java/com/android/tools/r8/ir/code/ConstString.java b/src/main/java/com/android/tools/r8/ir/code/ConstString.java
index 93b95e7..f873fd8 100644
--- a/src/main/java/com/android/tools/r8/ir/code/ConstString.java
+++ b/src/main/java/com/android/tools/r8/ir/code/ConstString.java
@@ -17,7 +17,6 @@
 import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.analysis.value.AbstractValue;
 import com.android.tools.r8.ir.analysis.value.UnknownValue;
-import com.android.tools.r8.ir.code.BasicBlock.ThrowingInfo;
 import com.android.tools.r8.ir.conversion.CfBuilder;
 import com.android.tools.r8.ir.conversion.DexBuilder;
 import com.android.tools.r8.ir.optimize.DeadCodeRemover.DeadInstructionResult;
@@ -27,12 +26,10 @@
 public class ConstString extends ConstInstruction {
 
   private final DexString value;
-  private final ThrowingInfo throwingInfo;
 
-  public ConstString(Value dest, DexString value, ThrowingInfo throwingInfo) {
+  public ConstString(Value dest, DexString value) {
     super(dest);
     this.value = value;
-    this.throwingInfo = throwingInfo;
   }
 
   @Override
@@ -53,7 +50,7 @@
 
   public static ConstString copyOf(Value newValue, ConstString original) {
     assert newValue != original.outValue();
-    return new ConstString(newValue, original.getValue(), original.throwingInfo);
+    return new ConstString(newValue, original.getValue());
   }
 
   public Value dest() {
@@ -98,7 +95,7 @@
 
   @Override
   public boolean instructionTypeCanThrow() {
-    return throwingInfo == ThrowingInfo.CAN_THROW;
+    return true;
   }
 
   @Override
@@ -118,9 +115,6 @@
 
   @Override
   public boolean instructionInstanceCanThrow() {
-    if (throwingInfo == ThrowingInfo.NO_THROW) {
-      return false;
-    }
     // The const-string instruction can be a throwing instruction in DEX, if decode() fails.
     try {
       value.toString();
diff --git a/src/main/java/com/android/tools/r8/ir/code/DexItemBasedConstString.java b/src/main/java/com/android/tools/r8/ir/code/DexItemBasedConstString.java
index 9c209c9..4c8c646 100644
--- a/src/main/java/com/android/tools/r8/ir/code/DexItemBasedConstString.java
+++ b/src/main/java/com/android/tools/r8/ir/code/DexItemBasedConstString.java
@@ -14,7 +14,6 @@
 import com.android.tools.r8.ir.analysis.type.Nullability;
 import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.analysis.value.AbstractValue;
-import com.android.tools.r8.ir.code.BasicBlock.ThrowingInfo;
 import com.android.tools.r8.ir.conversion.CfBuilder;
 import com.android.tools.r8.ir.conversion.DexBuilder;
 import com.android.tools.r8.ir.optimize.DeadCodeRemover.DeadInstructionResult;
@@ -27,17 +26,12 @@
 
   private final DexReference item;
   private final NameComputationInfo<?> nameComputationInfo;
-  private final ThrowingInfo throwingInfo;
 
   public DexItemBasedConstString(
-      Value dest,
-      DexReference item,
-      NameComputationInfo<?> nameComputationInfo,
-      ThrowingInfo throwingInfo) {
+      Value dest, DexReference item, NameComputationInfo<?> nameComputationInfo) {
     super(dest);
     this.item = item;
     this.nameComputationInfo = nameComputationInfo;
-    this.throwingInfo = throwingInfo;
   }
 
   @Override
@@ -58,8 +52,7 @@
 
   public static DexItemBasedConstString copyOf(Value newValue, DexItemBasedConstString original) {
     assert newValue != original.outValue();
-    return new DexItemBasedConstString(
-        newValue, original.getItem(), original.nameComputationInfo, original.throwingInfo);
+    return new DexItemBasedConstString(newValue, original.getItem(), original.nameComputationInfo);
   }
 
   public DexReference getItem() {
@@ -118,7 +111,7 @@
 
   @Override
   public boolean instructionTypeCanThrow() {
-    return throwingInfo == ThrowingInfo.CAN_THROW;
+    return true;
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/IRCode.java b/src/main/java/com/android/tools/r8/ir/code/IRCode.java
index 36eeadf..cbc8c6b 100644
--- a/src/main/java/com/android/tools/r8/ir/code/IRCode.java
+++ b/src/main/java/com/android/tools/r8/ir/code/IRCode.java
@@ -19,7 +19,6 @@
 import com.android.tools.r8.ir.analysis.type.ClassTypeElement;
 import com.android.tools.r8.ir.analysis.type.Nullability;
 import com.android.tools.r8.ir.analysis.type.TypeElement;
-import com.android.tools.r8.ir.code.BasicBlock.ThrowingInfo;
 import com.android.tools.r8.ir.code.Phi.RegisterReadType;
 import com.android.tools.r8.ir.conversion.IRBuilder;
 import com.android.tools.r8.origin.Origin;
@@ -1148,7 +1147,7 @@
   public ConstString createStringConstant(
       AppView<?> appView, DexString value, DebugLocalInfo local) {
     Value out = createValue(TypeElement.stringClassType(appView, definitelyNotNull()), local);
-    return new ConstString(out, value, ThrowingInfo.defaultForConstString(appView.options()));
+    return new ConstString(out, value);
   }
 
   public Phi createPhi(BasicBlock block, TypeElement type) {
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/CfSourceCode.java b/src/main/java/com/android/tools/r8/ir/conversion/CfSourceCode.java
index 5df390f..50ba68b 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/CfSourceCode.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/CfSourceCode.java
@@ -293,28 +293,13 @@
   @Override
   public void clear() {}
 
-  // Utility method that treats constant strings as not throwing in the case of having CF output.
-  // This is the only instruction that differ in throwing between DEX and CF. If we find more
-  // consider rewriting CfInstruction.canThrow to take in options.
-  private boolean canThrowHelper(CfInstruction instruction) {
-    return canThrowHelper(instruction, internalOutputMode.isGeneratingClassFiles());
-  }
-
-  public static boolean canThrowHelper(CfInstruction instruction, boolean isGeneratingClassFiles) {
-    if (isGeneratingClassFiles
-        && (instruction.isConstString() || instruction.isDexItemBasedConstString())) {
-      return false;
-    }
-    return instruction.canThrow();
-  }
-
   @Override
   public int traceInstruction(int instructionIndex, IRBuilder builder) {
     CfInstruction instruction = code.getInstructions().get(instructionIndex);
     AppView<?> appView = builder.appView;
     assert appView.options().isGeneratingClassFiles()
         == internalOutputMode.isGeneratingClassFiles();
-    if (canThrowHelper(instruction)) {
+    if (instruction.canThrow()) {
       TryHandlerList tryHandlers = getTryHandlers(instructionIndex, appView.dexItemFactory());
       if (!tryHandlers.isEmpty()) {
         // Ensure the block starts at the start of the try-range (don't enqueue, not a target).
@@ -537,7 +522,7 @@
     assert currentBlockInfo != null;
     setLocalVariableLists();
 
-    if (canThrowHelper(instruction)) {
+    if (instruction.canThrow()) {
       Snapshot exceptionTransfer =
           state.getSnapshot().exceptionTransfer(builder.appView.dexItemFactory().throwableType);
       for (int target : currentBlockInfo.exceptionalSuccessors) {
@@ -858,7 +843,7 @@
   @Override
   public boolean verifyCurrentInstructionCanThrow() {
     return isCurrentlyGeneratingMethodSynchronization()
-        || canThrowHelper(code.getInstructions().get(currentInstructionIndex));
+        || code.getInstructions().get(currentInstructionIndex).canThrow();
   }
 
   @Override
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 1a25cb1..fb9c5d3 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
@@ -38,12 +38,17 @@
         : new DefaultClassConverter(appView, converter, methodProcessor);
   }
 
-  public void convertClasses(ExecutorService executorService) throws ExecutionException {
-    internalConvertClasses(executorService);
+  public ClassConverterResult convertClasses(ExecutorService executorService)
+      throws ExecutionException {
+    ClassConverterResult.Builder resultBuilder = ClassConverterResult.builder();
+    internalConvertClasses(resultBuilder, executorService);
     notifyAllClassesConverted();
+    return resultBuilder.build();
   }
 
-  private void internalConvertClasses(ExecutorService executorService) throws ExecutionException {
+  private void internalConvertClasses(
+      ClassConverterResult.Builder resultBuilder, ExecutorService executorService)
+      throws ExecutionException {
     List<DexProgramClass> classes = appView.appInfo().classes();
     while (!classes.isEmpty()) {
       Set<DexType> seenNestHosts = Sets.newIdentityHashSet();
@@ -62,7 +67,8 @@
 
       // Process the wave and wait for all IR processing to complete.
       D8CfInstructionDesugaringEventConsumer desugaringEventConsumer =
-          CfInstructionDesugaringEventConsumer.createForD8();
+          CfInstructionDesugaringEventConsumer.createForD8(
+              resultBuilder::addSynthesizedLambdaClass, methodProcessor);
       methodProcessor.newWave();
       ThreadUtils.processItems(
           wave, clazz -> convertClass(clazz, desugaringEventConsumer), executorService);
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/ClassConverterResult.java b/src/main/java/com/android/tools/r8/ir/conversion/ClassConverterResult.java
new file mode 100644
index 0000000..4a07e14
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/conversion/ClassConverterResult.java
@@ -0,0 +1,51 @@
+// 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.conversion;
+
+import com.android.tools.r8.ir.desugar.LambdaClass;
+import java.util.ArrayList;
+import java.util.Comparator;
+import java.util.List;
+import java.util.function.Consumer;
+
+public class ClassConverterResult {
+
+  private final List<LambdaClass> synthesizedLambdaClassesWithDeterministicOrder;
+
+  private ClassConverterResult(List<LambdaClass> synthesizedLambdaClassesWithDeterministicOrder) {
+    this.synthesizedLambdaClassesWithDeterministicOrder =
+        synthesizedLambdaClassesWithDeterministicOrder;
+  }
+
+  public static Builder builder() {
+    return new Builder();
+  }
+
+  public void forEachSynthesizedLambdaClassWithDeterministicOrdering(
+      Consumer<LambdaClass> consumer) {
+    synthesizedLambdaClassesWithDeterministicOrder.forEach(consumer);
+  }
+
+  public List<LambdaClass> getSynthesizedLambdaClasses() {
+    return synthesizedLambdaClassesWithDeterministicOrder;
+  }
+
+  public static class Builder {
+
+    private final List<LambdaClass> synthesizedLambdaClasses = new ArrayList<>();
+
+    public Builder addSynthesizedLambdaClass(LambdaClass lambdaClass) {
+      synchronized (synthesizedLambdaClasses) {
+        synthesizedLambdaClasses.add(lambdaClass);
+      }
+      return this;
+    }
+
+    public ClassConverterResult build() {
+      synthesizedLambdaClasses.sort(Comparator.comparing(LambdaClass::getType));
+      return new ClassConverterResult(synthesizedLambdaClasses);
+    }
+  }
+}
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 2d3247e..bee8182 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
@@ -8,7 +8,7 @@
 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.desugar.CfInstructionDesugaringEventConsumer;
 import com.android.tools.r8.ir.optimize.info.OptimizationFeedbackIgnore;
 import com.android.tools.r8.utils.ThreadUtils;
 import com.google.common.collect.Sets;
@@ -75,6 +75,11 @@
             executorService));
   }
 
+  public D8MethodProcessor scheduleDesugaredMethodsForProcessing(Iterable<ProgramMethod> methods) {
+    methods.forEach(this::scheduleDesugaredMethodForProcessing);
+    return this;
+  }
+
   @Override
   public CallSiteInformation getCallSiteInformation() {
     throw new Unreachable("Invalid attempt to obtain call-site information in D8");
@@ -86,7 +91,7 @@
   }
 
   public void processMethod(
-      ProgramMethod method, D8CfInstructionDesugaringEventConsumer desugaringEventConsumer) {
+      ProgramMethod method, CfInstructionDesugaringEventConsumer desugaringEventConsumer) {
     converter.convertMethod(
         method,
         desugaringEventConsumer,
@@ -94,6 +99,10 @@
         processorContext.createMethodProcessingContext(method));
   }
 
+  public void processDesugaredMethod(ProgramMethod method) {
+    processMethod(method, CfInstructionDesugaringEventConsumer.createForDesugaredCode());
+  }
+
   public boolean verifyNoPendingMethodProcessing() {
     assert futures.isEmpty();
     return true;
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java b/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java
index 1bee2d4..93c61ab 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java
@@ -1272,15 +1272,13 @@
   }
 
   private ThrowingInfo throwingInfoForConstStrings() {
-    return appView.options().isGeneratingClassFiles()
-        ? ThrowingInfo.NO_THROW
-        : ThrowingInfo.CAN_THROW;
+    return ThrowingInfo.CAN_THROW;
   }
 
   public void addConstString(int dest, DexString string) {
     TypeElement typeLattice = TypeElement.stringClassType(appView, definitelyNotNull());
     ThrowingInfo throwingInfo = throwingInfoForConstStrings();
-    add(new ConstString(writeRegister(dest, typeLattice, throwingInfo), string, throwingInfo));
+    add(new ConstString(writeRegister(dest, typeLattice, throwingInfo), string));
   }
 
   public void addDexItemBasedConstString(
@@ -1288,7 +1286,7 @@
     TypeElement typeLattice = TypeElement.stringClassType(appView, definitelyNotNull());
     ThrowingInfo throwingInfo = throwingInfoForConstStrings();
     Value out = writeRegister(dest, typeLattice, throwingInfo);
-    add(new DexItemBasedConstString(out, item, nameComputationInfo, throwingInfo));
+    add(new DexItemBasedConstString(out, item, nameComputationInfo));
   }
 
   public void addDiv(NumericType type, int dest, int left, int right) {
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 7a45467..db7c135 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
@@ -5,6 +5,7 @@
 
 import static com.android.tools.r8.ir.desugar.InterfaceMethodRewriter.Flavor.ExcludeDexResources;
 import static com.android.tools.r8.ir.desugar.InterfaceMethodRewriter.Flavor.IncludeAllResources;
+import static com.android.tools.r8.ir.desugar.lambda.D8LambdaDesugaring.synthesizeAccessibilityBridgesForLambdaClasses;
 
 import com.android.tools.r8.contexts.CompilationContext.MethodProcessingContext;
 import com.android.tools.r8.errors.CompilationError;
@@ -43,6 +44,7 @@
 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.CfInstructionDesugaringCollection;
 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;
@@ -51,12 +53,10 @@
 import com.android.tools.r8.ir.desugar.DesugaredLibraryRetargeter;
 import com.android.tools.r8.ir.desugar.InterfaceMethodRewriter;
 import com.android.tools.r8.ir.desugar.InterfaceMethodRewriter.Flavor;
-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.lambda.LambdaDeserializationMethodRemover;
 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;
 import com.android.tools.r8.ir.optimize.AssumeInserter;
 import com.android.tools.r8.ir.optimize.ClassInitializerDefaultsOptimization;
@@ -101,6 +101,7 @@
 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.IterableUtils;
 import com.android.tools.r8.utils.ListUtils;
 import com.android.tools.r8.utils.StringDiagnostic;
 import com.android.tools.r8.utils.SupplierUtils;
@@ -130,15 +131,13 @@
   private final Timing timing;
   private final Outliner outliner;
   private final ClassInitializerDefaultsOptimization classInitializerDefaultsOptimization;
+  private final CfInstructionDesugaringCollection desugaring;
   private final FieldAccessAnalysis fieldAccessAnalysis;
   private final LibraryMethodOverrideAnalysis libraryMethodOverrideAnalysis;
   private final StringConcatRewriter stringConcatRewriter;
   private final StringOptimizer stringOptimizer;
   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;
   private final BackportedMethodRewriter backportedMethodRewriter;
@@ -213,17 +212,22 @@
             .collect(Collectors.toList());
     if (options.isDesugaredLibraryCompilation()) {
       // Specific L8 Settings, performs all desugaring including L8 specific desugaring.
-      // The following desugaring are required for L8 specific desugaring:
+      //
+      // The following desugarings are required for L8 specific desugaring:
       // - DesugaredLibraryRetargeter for retarget core library members.
       // - InterfaceMethodRewriter for emulated interfaces,
-      // - LambdaRewriter since InterfaceMethodDesugaring does not support invokeCustom rewriting,
+      // - Lambda desugaring since interface method desugaring does not support invoke-custom
+      //   rewriting,
       // - DesugaredLibraryAPIConverter to duplicate APIs.
+      //
       // The following desugaring are present so all desugaring is performed cf to cf in L8, and
       // the second L8 phase can just run with Desugar turned off:
       // - InterfaceMethodRewriter for non L8 specific interface method desugaring,
       // - TwrCloseResourceRewriter,
-      // - NestBaseAccessDesugaring.
-      assert options.desugarState == DesugarState.ON;
+      // - nest based access desugaring,
+      // - invoke-special desugaring.
+      assert options.desugarState.isOn();
+      this.desugaring = CfInstructionDesugaringCollection.create(appView);
       this.desugaredLibraryRetargeter =
           options.desugaredLibraryConfiguration.getRetargetCoreLibMember().isEmpty()
               ? null
@@ -232,7 +236,6 @@
           options.desugaredLibraryConfiguration.getEmulateLibraryInterface().isEmpty()
               ? null
               : new InterfaceMethodRewriter(appView, this);
-      this.lambdaRewriter = new LambdaRewriter(appView);
       this.desugaredLibraryAPIConverter =
           new DesugaredLibraryAPIConverter(appView, Mode.GENERATE_CALLBACKS_AND_WRAPPERS);
       this.backportedMethodRewriter = new BackportedMethodRewriter(appView);
@@ -240,9 +243,6 @@
           TwrCloseResourceRewriter.enableTwrCloseResourceDesugaring(appView.options())
               ? new TwrCloseResourceRewriter(appView)
               : null;
-      this.invokeSpecialToSelfDesugaring = new InvokeSpecialToSelfDesugaring(appView);
-      this.d8NestBasedAccessDesugaring =
-          options.shouldDesugarNests() ? new D8NestBasedAccessDesugaring(appView) : null;
       this.covariantReturnTypeAnnotationTransformer = null;
       this.dynamicTypeOptimization = null;
       this.classInliner = null;
@@ -264,10 +264,10 @@
       this.assumeInserter = null;
       return;
     }
-    this.lambdaRewriter =
-        (options.desugarState == DesugarState.ON && !appView.enableWholeProgramOptimizations())
-            ? new LambdaRewriter(appView)
-            : null;
+    this.desugaring =
+        appView.enableWholeProgramOptimizations()
+            ? CfInstructionDesugaringCollection.empty()
+            : CfInstructionDesugaringCollection.create(appView);
     this.interfaceMethodRewriter =
         options.isInterfaceMethodDesugaringEnabled()
             ? new InterfaceMethodRewriter(appView, this)
@@ -321,8 +321,6 @@
       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
               ? new ServiceLoaderRewriter(appViewWithLiveness)
@@ -348,9 +346,6 @@
       this.identifierNameStringMarker = null;
       this.devirtualizer = null;
       this.typeChecker = null;
-      this.invokeSpecialToSelfDesugaring = new InvokeSpecialToSelfDesugaring(appView);
-      this.d8NestBasedAccessDesugaring =
-          options.shouldDesugarNests() ? new D8NestBasedAccessDesugaring(appView) : null;
       this.desugaredLibraryAPIConverter =
           appView.rewritePrefix.isRewriting()
               ? new DesugaredLibraryAPIConverter(appView, Mode.GENERATE_CALLBACKS_AND_WRAPPERS)
@@ -375,31 +370,19 @@
     this(AppView.createForD8(appInfo), timing, printer);
   }
 
-  private void removeLambdaDeserializationMethods() {
-    if (lambdaRewriter != null) {
-      lambdaRewriter.removeLambdaDeserializationMethods(appView.appInfo().classes());
-    }
-  }
-
   private void synthesizeBridgesForNestBasedAccessesOnClasspath(
-      MethodProcessor methodProcessor, ExecutorService executorService) throws ExecutionException {
-    if (d8NestBasedAccessDesugaring != null) {
-      d8NestBasedAccessDesugaring.synthesizeBridgesForNestBasedAccessesOnClasspath(
-          methodProcessor, executorService);
-    }
+      D8MethodProcessor methodProcessor, ExecutorService executorService)
+      throws ExecutionException {
+    desugaring.withD8NestBasedAccessDesugaring(
+        d8NestBasedAccessDesugaring ->
+            d8NestBasedAccessDesugaring.synthesizeBridgesForNestBasedAccessesOnClasspath(
+                methodProcessor, executorService));
+    methodProcessor.awaitMethodProcessing();
   }
 
   private void reportNestDesugarDependencies() {
-    if (d8NestBasedAccessDesugaring != null) {
-      d8NestBasedAccessDesugaring.reportDesugarDependencies();
-    }
-  }
-
-  private void synthesizeLambdaClasses(ExecutorService executorService) throws ExecutionException {
-    if (lambdaRewriter != null) {
-      assert !appView.enableWholeProgramOptimizations();
-      lambdaRewriter.finalizeLambdaDesugaringForD8(this, executorService);
-    }
+    desugaring.withD8NestBasedAccessDesugaring(
+        D8NestBasedAccessDesugaring::reportDesugarDependencies);
   }
 
   private void staticizeClasses(OptimizationFeedback feedback, ExecutorService executorService)
@@ -462,7 +445,7 @@
 
   public void convert(AppView<AppInfo> appView, ExecutorService executor)
       throws ExecutionException {
-    removeLambdaDeserializationMethods();
+    LambdaDeserializationMethodRemover.run(appView);
     workaroundAbstractMethodOnNonAbstractClassVerificationBug(
         executor, OptimizationFeedbackIgnore.getInstance());
     DexApplication application = appView.appInfo().app();
@@ -471,10 +454,8 @@
     convertClasses(executor);
 
     reportNestDesugarDependencies();
-
-    // Synthesize lambda classes and commit to the app in full.
-    synthesizeLambdaClasses(executor);
     processTwrCloseResourceUtilityMethods();
+
     if (appView.getSyntheticItems().hasPendingSyntheticClasses()) {
       appView.setAppInfo(
           new AppInfo(
@@ -503,11 +484,22 @@
 
   private void convertClasses(ExecutorService executorService) throws ExecutionException {
     D8MethodProcessor methodProcessor = new D8MethodProcessor(this, executorService);
-    ClassConverter classConverter = ClassConverter.create(appView, this, methodProcessor);
-    classConverter.convertClasses(executorService);
+    ClassConverterResult classConverterResult =
+        ClassConverter.create(appView, this, methodProcessor).convertClasses(executorService);
 
+    // The synthesis of accessibility bridges in lambda desugaring and nest based access desugaring
+    // will schedule and await the processing of synthesized methods.
     synthesizeBridgesForNestBasedAccessesOnClasspath(methodProcessor, executorService);
-    methodProcessor.awaitMethodProcessing();
+    synthesizeAccessibilityBridgesForLambdaClasses(appView, classConverterResult, methodProcessor);
+    methodProcessor
+        .scheduleDesugaredMethodsForProcessing(
+            IterableUtils.flatMap(
+                classConverterResult.getSynthesizedLambdaClasses(),
+                lambdaClass -> lambdaClass.getLambdaProgramClass().programMethods()))
+        .awaitMethodProcessing();
+
+    // There should be no outstanding method processing.
+    methodProcessor.verifyNoPendingMethodProcessing();
   }
 
   void convertMethods(
@@ -550,7 +542,7 @@
 
   void convertMethod(
       ProgramMethod method,
-      D8CfInstructionDesugaringEventConsumer desugaringEventConsumer,
+      CfInstructionDesugaringEventConsumer desugaringEventConsumer,
       MethodProcessor methodProcessor,
       MethodProcessingContext methodProcessingContext) {
     DexEncodedMethod definition = method.getDefinition();
@@ -673,8 +665,8 @@
   public DexApplication optimize(
       AppView<AppInfoWithLiveness> appView, ExecutorService executorService)
       throws ExecutionException {
-    // Lambda rewriting happens in the enqueuer.
-    assert lambdaRewriter == null;
+    // Desugaring happens in the enqueuer.
+    assert desugaring.isEmpty();
 
     DexApplication application = appView.appInfo().app();
 
@@ -1173,22 +1165,14 @@
     boolean didDesugar = false;
     Supplier<AppInfoWithClassHierarchy> lazyAppInfo =
         SupplierUtils.nonThreadSafeMemoize(appView::appInfoForDesugaring);
-    if (lambdaRewriter != null) {
-      didDesugar |=
-          lambdaRewriter.desugarLambdas(method, lazyAppInfo.get(), methodProcessingContext) > 0;
+    if (desugaring.needsDesugaring(method)) {
+      desugaring.desugar(method, methodProcessingContext, desugaringEventConsumer);
+      didDesugar = true;
     }
     if (backportedMethodRewriter != null) {
       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;
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/NeedsIRDesugarUseRegistry.java b/src/main/java/com/android/tools/r8/ir/conversion/NeedsIRDesugarUseRegistry.java
index d91b447..c12c6f0 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/NeedsIRDesugarUseRegistry.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/NeedsIRDesugarUseRegistry.java
@@ -91,7 +91,7 @@
     if (!needsDesugaring) {
       needsDesugaring =
           interfaceMethodRewriter != null
-              && interfaceMethodRewriter.needsRewriting(method, invokeType, appView);
+              && interfaceMethodRewriter.needsRewriting(method, invokeType);
     }
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/StringSwitchRemover.java b/src/main/java/com/android/tools/r8/ir/conversion/StringSwitchRemover.java
index fec0919..845793f 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/StringSwitchRemover.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/StringSwitchRemover.java
@@ -14,7 +14,6 @@
 import com.android.tools.r8.ir.analysis.type.PrimitiveTypeElement;
 import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.code.BasicBlock;
-import com.android.tools.r8.ir.code.BasicBlock.ThrowingInfo;
 import com.android.tools.r8.ir.code.ConstString;
 import com.android.tools.r8.ir.code.Goto;
 import com.android.tools.r8.ir.code.IRCode;
@@ -50,13 +49,11 @@
   private final AppView<?> appView;
   private final IdentifierNameStringMarker identifierNameStringMarker;
   private final ClassTypeElement stringType;
-  private final ThrowingInfo throwingInfo;
 
   StringSwitchRemover(AppView<?> appView, IdentifierNameStringMarker identifierNameStringMarker) {
     this.appView = appView;
     this.identifierNameStringMarker = identifierNameStringMarker;
     this.stringType = TypeElement.stringClassType(appView, definitelyNotNull());
-    this.throwingInfo = ThrowingInfo.defaultForConstString(appView.options());
   }
 
   void run(IRCode code) {
@@ -222,7 +219,7 @@
       BasicBlock previous = null;
       for (Entry<DexString, BasicBlock> entry : structure.entrySet()) {
         ConstString constStringInstruction =
-            new ConstString(code.createValue(stringType), entry.getKey(), throwingInfo);
+            new ConstString(code.createValue(stringType), entry.getKey());
         constStringInstruction.setPosition(position);
         InvokeVirtual invokeInstruction =
             new InvokeVirtual(
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
index 08a7934..22b1d92 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/CfInstructionDesugaring.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/CfInstructionDesugaring.java
@@ -5,8 +5,9 @@
 package com.android.tools.r8.ir.desugar;
 
 import com.android.tools.r8.cf.code.CfInstruction;
+import com.android.tools.r8.contexts.CompilationContext.MethodProcessingContext;
 import com.android.tools.r8.graph.ProgramMethod;
-import java.util.List;
+import java.util.Collection;
 
 /** Interface for desugaring a single class-file instruction. */
 public interface CfInstructionDesugaring {
@@ -15,10 +16,12 @@
    * 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(
+  Collection<CfInstruction> desugarInstruction(
       CfInstruction instruction,
-      CfInstructionDesugaringEventConsumer consumer,
-      ProgramMethod context);
+      FreshLocalProvider freshLocalProvider,
+      CfInstructionDesugaringEventConsumer eventConsumer,
+      ProgramMethod context,
+      MethodProcessingContext methodProcessingContext);
 
   /**
    * Returns true if the given instruction needs desugaring.
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
index bf63ace..21ac7ae 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/CfInstructionDesugaringCollection.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/CfInstructionDesugaringCollection.java
@@ -4,7 +4,11 @@
 
 package com.android.tools.r8.ir.desugar;
 
+import com.android.tools.r8.contexts.CompilationContext.MethodProcessingContext;
+import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.ir.desugar.nest.D8NestBasedAccessDesugaring;
+import com.android.tools.r8.utils.ThrowingConsumer;
 
 /**
  * Abstracts a collection of low-level desugarings (i.e., mappings from class-file instructions to
@@ -14,13 +18,35 @@
  */
 public abstract class CfInstructionDesugaringCollection {
 
+  public static CfInstructionDesugaringCollection create(AppView<?> appView) {
+    if (appView.options().desugarState.isOn()) {
+      return new NonEmptyCfInstructionDesugaringCollection(appView);
+    }
+    // TODO(b/145775365): invoke-special desugaring is mandatory, since we currently can't map
+    //  invoke-special instructions that require desugaring into IR.
+    if (appView.options().isGeneratingClassFiles()) {
+      return NonEmptyCfInstructionDesugaringCollection.createForCfToCfNonDesugar(appView);
+    }
+    return empty();
+  }
+
   public static CfInstructionDesugaringCollection empty() {
-    return new EmptyCfInstructionDesugaringCollection();
+    return EmptyCfInstructionDesugaringCollection.getInstance();
   }
 
   /** Desugars the instructions in the given method. */
-  public abstract void desugar(ProgramMethod method, CfInstructionDesugaringEventConsumer consumer);
+  public abstract void desugar(
+      ProgramMethod method,
+      MethodProcessingContext methodProcessingContext,
+      CfInstructionDesugaringEventConsumer eventConsumer);
+
+  public boolean isEmpty() {
+    return false;
+  }
 
   /** Returns true if the given method needs desugaring. */
   public abstract boolean needsDesugaring(ProgramMethod method);
+
+  public abstract <T extends Throwable> void withD8NestBasedAccessDesugaring(
+      ThrowingConsumer<D8NestBasedAccessDesugaring, T> consumer) throws T;
 }
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
index 0b01594..4041efd 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/CfInstructionDesugaringEventConsumer.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/CfInstructionDesugaringEventConsumer.java
@@ -6,47 +6,90 @@
 
 import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
 import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexReference;
+import com.android.tools.r8.graph.ProgramField;
 import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.ir.conversion.D8MethodProcessor;
 import com.android.tools.r8.ir.desugar.invokespecial.InvokeSpecialBridgeInfo;
+import com.android.tools.r8.ir.desugar.invokespecial.InvokeSpecialToSelfDesugaringEventConsumer;
+import com.android.tools.r8.ir.desugar.lambda.LambdaDeserializationMethodRemover;
+import com.android.tools.r8.ir.desugar.lambda.LambdaDesugaringEventConsumer;
+import com.android.tools.r8.ir.desugar.nest.NestBasedAccessDesugaringEventConsumer;
+import com.google.common.collect.Sets;
 import java.util.ArrayList;
 import java.util.Collections;
+import java.util.IdentityHashMap;
 import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.Set;
 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 abstract class CfInstructionDesugaringEventConsumer
+    implements InvokeSpecialToSelfDesugaringEventConsumer,
+        LambdaDesugaringEventConsumer,
+        NestBasedAccessDesugaringEventConsumer {
 
-  public static D8CfInstructionDesugaringEventConsumer createForD8() {
-    return new D8CfInstructionDesugaringEventConsumer();
+  public static D8CfInstructionDesugaringEventConsumer createForD8(
+      Consumer<LambdaClass> lambdaClassConsumer, D8MethodProcessor methodProcessor) {
+    return new D8CfInstructionDesugaringEventConsumer(lambdaClassConsumer, methodProcessor);
   }
 
-  public static R8CfInstructionDesugaringEventConsumer createForR8() {
-    return new R8CfInstructionDesugaringEventConsumer();
+  public static R8CfInstructionDesugaringEventConsumer createForR8(
+      AppView<? extends AppInfoWithClassHierarchy> appView) {
+    return new R8CfInstructionDesugaringEventConsumer(appView);
   }
 
   public static CfInstructionDesugaringEventConsumer createForDesugaredCode() {
     return new CfInstructionDesugaringEventConsumer() {
+
       @Override
       public void acceptInvokeSpecialBridgeInfo(InvokeSpecialBridgeInfo info) {
         assert false;
       }
+
+      @Override
+      public void acceptLambdaClass(LambdaClass lambdaClass, ProgramMethod context) {
+        assert false;
+      }
+
+      @Override
+      public void acceptNestFieldGetBridge(ProgramField target, ProgramMethod bridge) {
+        assert false;
+      }
+
+      @Override
+      public void acceptNestFieldPutBridge(ProgramField target, ProgramMethod bridge) {
+        assert false;
+      }
+
+      @Override
+      public void acceptNestMethodBridge(ProgramMethod target, ProgramMethod bridge) {
+        assert false;
+      }
     };
   }
 
-  public abstract void acceptInvokeSpecialBridgeInfo(InvokeSpecialBridgeInfo info);
-
   public static class D8CfInstructionDesugaringEventConsumer
       extends CfInstructionDesugaringEventConsumer {
 
+    private final Consumer<LambdaClass> lambdaClassConsumer;
+    private final D8MethodProcessor methodProcessor;
+
     private final Map<DexReference, InvokeSpecialBridgeInfo> pendingInvokeSpecialBridges =
         new LinkedHashMap<>();
 
+    private D8CfInstructionDesugaringEventConsumer(
+        Consumer<LambdaClass> lambdaClassConsumer, D8MethodProcessor methodProcessor) {
+      this.lambdaClassConsumer = lambdaClassConsumer;
+      this.methodProcessor = methodProcessor;
+    }
+
     @Override
     public void acceptInvokeSpecialBridgeInfo(InvokeSpecialBridgeInfo info) {
       synchronized (pendingInvokeSpecialBridges) {
@@ -55,6 +98,26 @@
       }
     }
 
+    @Override
+    public void acceptLambdaClass(LambdaClass lambdaClass, ProgramMethod context) {
+      lambdaClassConsumer.accept(lambdaClass);
+    }
+
+    @Override
+    public void acceptNestFieldGetBridge(ProgramField target, ProgramMethod bridge) {
+      methodProcessor.scheduleDesugaredMethodForProcessing(bridge);
+    }
+
+    @Override
+    public void acceptNestFieldPutBridge(ProgramField target, ProgramMethod bridge) {
+      methodProcessor.scheduleDesugaredMethodForProcessing(bridge);
+    }
+
+    @Override
+    public void acceptNestMethodBridge(ProgramMethod target, ProgramMethod bridge) {
+      methodProcessor.scheduleDesugaredMethodForProcessing(bridge);
+    }
+
     public List<ProgramMethod> finalizeDesugaring(AppView<?> appView) {
       List<ProgramMethod> needsReprocessing = new ArrayList<>();
       finalizeInvokeSpecialDesugaring(appView, needsReprocessing::add);
@@ -98,8 +161,17 @@
   public static class R8CfInstructionDesugaringEventConsumer
       extends CfInstructionDesugaringEventConsumer {
 
+    private final AppView<? extends AppInfoWithClassHierarchy> appView;
+
+    private final Map<LambdaClass, ProgramMethod> synthesizedLambdaClasses =
+        new IdentityHashMap<>();
     private final List<InvokeSpecialBridgeInfo> pendingInvokeSpecialBridges = new ArrayList<>();
 
+    public R8CfInstructionDesugaringEventConsumer(
+        AppView<? extends AppInfoWithClassHierarchy> appView) {
+      this.appView = appView;
+    }
+
     @Override
     public void acceptInvokeSpecialBridgeInfo(InvokeSpecialBridgeInfo info) {
       synchronized (pendingInvokeSpecialBridges) {
@@ -107,7 +179,37 @@
       }
     }
 
-    public void finalizeDesugaring(AppView<? extends AppInfoWithClassHierarchy> appView) {
+    @Override
+    public void acceptLambdaClass(LambdaClass lambdaClass, ProgramMethod context) {
+      synchronized (synthesizedLambdaClasses) {
+        synthesizedLambdaClasses.put(lambdaClass, context);
+      }
+    }
+
+    @Override
+    public void acceptNestFieldGetBridge(ProgramField target, ProgramMethod bridge) {
+      // Intentionally empty. These bridges will be hit by the tracing in R8 as if they were present
+      // in the input code, and thus nothing needs to be done.
+    }
+
+    @Override
+    public void acceptNestFieldPutBridge(ProgramField target, ProgramMethod bridge) {
+      // Intentionally empty. These bridges will be hit by the tracing in R8 as if they were present
+      // in the input code, and thus nothing needs to be done.
+    }
+
+    @Override
+    public void acceptNestMethodBridge(ProgramMethod target, ProgramMethod bridge) {
+      // Intentionally empty. These bridges will be hit by the tracing in R8 as if they were present
+      // in the input code, and thus nothing needs to be done.
+    }
+
+    public void finalizeDesugaring() {
+      finalizeInvokeSpecialDesugaring();
+      finalizeLambdaDesugaring();
+    }
+
+    private void finalizeInvokeSpecialDesugaring() {
       Collections.sort(pendingInvokeSpecialBridges);
       pendingInvokeSpecialBridges.forEach(
           info ->
@@ -115,5 +217,22 @@
                   .getDefinition()
                   .setCode(info.getVirtualMethodCode(), appView));
     }
+
+    private void finalizeLambdaDesugaring() {
+      Set<DexProgramClass> classesWithSerializableLambdas = Sets.newIdentityHashSet();
+      synthesizedLambdaClasses.forEach(
+          (lambdaClass, context) -> {
+            lambdaClass.target.ensureAccessibilityIfNeeded();
+
+            // Populate set of types with serialized lambda method for removal.
+            if (lambdaClass.descriptor.interfaces.contains(
+                appView.dexItemFactory().serializableType)) {
+              classesWithSerializableLambdas.add(context.getHolder());
+            }
+          });
+
+      // Remove all '$deserializeLambda$' methods which are not supported by desugaring.
+      LambdaDeserializationMethodRemover.run(appView, classesWithSerializableLambdas);
+    }
   }
 }
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
index 8c56b4d..60b3537 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/EmptyCfInstructionDesugaringCollection.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/EmptyCfInstructionDesugaringCollection.java
@@ -4,17 +4,44 @@
 
 package com.android.tools.r8.ir.desugar;
 
+import com.android.tools.r8.contexts.CompilationContext.MethodProcessingContext;
 import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.ir.desugar.nest.D8NestBasedAccessDesugaring;
+import com.android.tools.r8.utils.ThrowingConsumer;
 
 public class EmptyCfInstructionDesugaringCollection extends CfInstructionDesugaringCollection {
 
+  private static final EmptyCfInstructionDesugaringCollection INSTANCE =
+      new EmptyCfInstructionDesugaringCollection();
+
+  private EmptyCfInstructionDesugaringCollection() {}
+
+  /** Intentionally package-private, prefer {@link CfInstructionDesugaringCollection#empty()}. */
+  static EmptyCfInstructionDesugaringCollection getInstance() {
+    return INSTANCE;
+  }
+
   @Override
-  public void desugar(ProgramMethod method, CfInstructionDesugaringEventConsumer consumer) {
+  public void desugar(
+      ProgramMethod method,
+      MethodProcessingContext methodProcessingContext,
+      CfInstructionDesugaringEventConsumer eventConsumer) {
     // Intentionally empty.
   }
 
   @Override
+  public boolean isEmpty() {
+    return true;
+  }
+
+  @Override
   public boolean needsDesugaring(ProgramMethod method) {
     return false;
   }
+
+  @Override
+  public <T extends Throwable> void withD8NestBasedAccessDesugaring(
+      ThrowingConsumer<D8NestBasedAccessDesugaring, T> consumer) {
+    // Intentionally empty.
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/FreshLocalProvider.java b/src/main/java/com/android/tools/r8/ir/desugar/FreshLocalProvider.java
new file mode 100644
index 0000000..6c62b59
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/desugar/FreshLocalProvider.java
@@ -0,0 +1,10 @@
+// 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;
+
+public interface FreshLocalProvider {
+
+  int getFreshLocal(int requiredRegisters);
+}
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 6ffc8e2..7d041f6 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
@@ -68,6 +68,7 @@
 import com.android.tools.r8.ir.desugar.DefaultMethodsHelper.Collection;
 import com.android.tools.r8.ir.desugar.InterfaceProcessor.InterfaceProcessorNestedGraphLens;
 import com.android.tools.r8.ir.optimize.UtilityMethodsForCodeOptimizations;
+import com.android.tools.r8.ir.optimize.UtilityMethodsForCodeOptimizations.MethodSynthesizerConsumer;
 import com.android.tools.r8.ir.optimize.UtilityMethodsForCodeOptimizations.UtilityMethodForCodeOptimizations;
 import com.android.tools.r8.ir.synthetic.ForwardMethodBuilder;
 import com.android.tools.r8.origin.Origin;
@@ -78,6 +79,7 @@
 import com.android.tools.r8.utils.DescriptorUtils;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.IterableUtils;
+import com.android.tools.r8.utils.IteratorUtils;
 import com.android.tools.r8.utils.Pair;
 import com.android.tools.r8.utils.StringDiagnostic;
 import com.android.tools.r8.utils.collections.SortedProgramMethodSet;
@@ -236,7 +238,7 @@
     return emulatedInterfaces.containsKey(itf);
   }
 
-  public boolean needsRewriting(DexMethod method, Type invokeType, AppView<?> appView) {
+  public boolean needsRewriting(DexMethod method, Type invokeType) {
     if (invokeType == SUPER || invokeType == STATIC || invokeType == DIRECT) {
       DexClass clazz = appView.appInfo().definitionFor(method.getHolderType());
       if (clazz != null && clazz.isInterface()) {
@@ -307,7 +309,15 @@
                 methodProcessingContext);
             break;
           case INVOKE_SUPER:
-            rewriteInvokeSuper(instruction.asInvokeSuper(), instructions, context);
+            rewriteInvokeSuper(
+                instruction.asInvokeSuper(),
+                code,
+                blocks,
+                instructions,
+                affectedValues,
+                blocksToRemove,
+                methodProcessor,
+                methodProcessingContext);
             break;
           case INVOKE_INTERFACE:
           case INVOKE_VIRTUAL:
@@ -373,7 +383,7 @@
       // This can be a private instance method call. Note that the referenced
       // method is expected to be in the current class since it is private, but desugaring
       // may move some methods or their code into other classes.
-      assert needsRewriting(method, DIRECT, appView);
+      assert needsRewriting(method, DIRECT);
       instructions.replaceCurrentInstruction(
           new InvokeStatic(
               directTarget.getDefinition().isPrivateMethod()
@@ -387,7 +397,7 @@
           appView.appInfoForDesugaring().lookupMaximallySpecificMethod(clazz, method);
       if (virtualTarget != null) {
         // This is a invoke-direct call to a virtual method.
-        assert needsRewriting(method, DIRECT, appView);
+        assert needsRewriting(method, DIRECT);
         instructions.replaceCurrentInstruction(
             new InvokeStatic(
                 defaultAsMethodOfCompanionClass(virtualTarget),
@@ -470,7 +480,7 @@
                                         .setStaticTarget(invokedMethod, true)
                                         .setStaticSource(m)
                                         .build()));
-        assert needsRewriting(invokedMethod, STATIC, appView);
+        assert needsRewriting(invokedMethod, STATIC);
         instructions.replaceCurrentInstruction(
             new InvokeStatic(
                 newProgramMethod.getReference(), invoke.outValue(), invoke.arguments()));
@@ -492,41 +502,42 @@
             .appInfoForDesugaring()
             .resolveMethodOnInterface(clazz, invokedMethod)
             .asSingleResolution();
-    if (resolutionResult != null && resolutionResult.getResolvedMethod().isStatic()) {
-      assert needsRewriting(invokedMethod, STATIC, appView);
-      instructions.replaceCurrentInstruction(
-          new InvokeStatic(
-              staticAsMethodOfCompanionClass(resolutionResult.getResolutionPair()),
-              invoke.outValue(),
-              invoke.arguments()));
+    if (clazz.isInterface()
+        && rewriteInvokeToThrow(
+            invoke,
+            resolutionResult,
+            code,
+            blockIterator,
+            instructions,
+            affectedValues,
+            blocksToRemove,
+            methodProcessor,
+            methodProcessingContext)) {
+      assert needsRewriting(invoke.getInvokedMethod(), STATIC);
       return;
     }
 
-    // Replace by throw new IncompatibleClassChangeError/NoSuchMethodError.
-    UtilityMethodForCodeOptimizations throwMethod =
-        resolutionResult == null
-            ? UtilityMethodsForCodeOptimizations.synthesizeThrowNoSuchMethodErrorMethod(
-                appView, methodProcessingContext)
-            : UtilityMethodsForCodeOptimizations.synthesizeThrowIncompatibleClassChangeErrorMethod(
-                appView, methodProcessingContext);
-    throwMethod.optimize(methodProcessor);
+    assert resolutionResult != null;
+    assert resolutionResult.getResolvedMethod().isStatic();
+    assert needsRewriting(invokedMethod, STATIC);
 
-    InvokeStatic throwInvoke =
-        InvokeStatic.builder()
-            .setMethod(throwMethod.getMethod())
-            .setFreshOutValue(appView, code)
-            .setPosition(invoke)
-            .build();
-    instructions.previous();
-    instructions.add(throwInvoke);
-    instructions.next();
-    assert needsRewriting(invokedMethod, STATIC, appView);
-    instructions.replaceCurrentInstructionWithThrow(
-        appView, code, blockIterator, throwInvoke.outValue(), blocksToRemove, affectedValues);
+    instructions.replaceCurrentInstruction(
+        new InvokeStatic(
+            staticAsMethodOfCompanionClass(resolutionResult.getResolutionPair()),
+            invoke.outValue(),
+            invoke.arguments()));
   }
 
   private void rewriteInvokeSuper(
-      InvokeSuper invoke, InstructionListIterator instructions, ProgramMethod context) {
+      InvokeSuper invoke,
+      IRCode code,
+      ListIterator<BasicBlock> blockIterator,
+      InstructionListIterator instructions,
+      Set<Value> affectedValues,
+      Set<BasicBlock> blocksToRemove,
+      MethodProcessor methodProcessor,
+      MethodProcessingContext methodProcessingContext) {
+    ProgramMethod context = code.context();
     DexMethod invokedMethod = invoke.getInvokedMethod();
     DexClass clazz = appView.definitionFor(invokedMethod.holder, context);
     if (clazz == null) {
@@ -537,6 +548,23 @@
       return;
     }
 
+    SingleResolutionResult resolutionResult =
+        appView.appInfoForDesugaring().resolveMethodOn(clazz, invokedMethod).asSingleResolution();
+    if (clazz.isInterface()
+        && rewriteInvokeToThrow(
+            invoke,
+            resolutionResult,
+            code,
+            blockIterator,
+            instructions,
+            affectedValues,
+            blocksToRemove,
+            methodProcessor,
+            methodProcessingContext)) {
+      assert needsRewriting(invoke.getInvokedMethod(), SUPER);
+      return;
+    }
+
     if (clazz.isInterface() && !clazz.isLibraryClass()) {
       // NOTE: we intentionally don't desugar super calls into interface methods
       // coming from android.jar since it is only possible in case v24+ version
@@ -547,7 +575,7 @@
       //
       // WARNING: This may result in incorrect code on older platforms!
       // Retarget call to an appropriate method of companion class.
-      assert needsRewriting(invokedMethod, SUPER, appView);
+      assert needsRewriting(invokedMethod, SUPER);
       DexMethod amendedMethod = amendDefaultMethod(context.getHolder(), invokedMethod);
       instructions.replaceCurrentInstruction(
           new InvokeStatic(
@@ -563,7 +591,7 @@
           if (target != null && target.getDefinition().isDefaultMethod()) {
             DexClass holder = target.getHolder();
             if (holder.isLibraryClass() && holder.isInterface()) {
-              assert needsRewriting(invokedMethod, SUPER, appView);
+              assert needsRewriting(invokedMethod, SUPER);
               instructions.replaceCurrentInstruction(
                   new InvokeStatic(
                       defaultAsMethodOfCompanionClass(target),
@@ -593,11 +621,11 @@
                     factory.protoWithDifferentFirstParameter(
                         originalCompanionMethod.proto, emulatedItf),
                     originalCompanionMethod.name);
-            assert needsRewriting(invokedMethod, SUPER, appView);
+            assert needsRewriting(invokedMethod, SUPER);
             instructions.replaceCurrentInstruction(
                 new InvokeStatic(companionMethod, invoke.outValue(), invoke.arguments()));
           } else {
-            assert needsRewriting(invokedMethod, SUPER, appView);
+            assert needsRewriting(invokedMethod, SUPER);
             instructions.replaceCurrentInstruction(
                 new InvokeStatic(retargetMethod, invoke.outValue(), invoke.arguments()));
           }
@@ -624,12 +652,66 @@
     if (resolution != null
         && (resolution.getResolvedHolder().isLibraryClass()
             || appView.options().isDesugaredLibraryCompilation())) {
-      assert needsRewriting(invokedMethod, VIRTUAL, appView);
+      assert needsRewriting(invokedMethod, VIRTUAL);
       rewriteCurrentInstructionToEmulatedInterfaceCall(
           emulatedItf, invokedMethod, invoke, instructions);
     }
   }
 
+  private boolean rewriteInvokeToThrow(
+      InvokeMethod invoke,
+      SingleResolutionResult resolutionResult,
+      IRCode code,
+      ListIterator<BasicBlock> blockIterator,
+      InstructionListIterator instructions,
+      Set<Value> affectedValues,
+      Set<BasicBlock> blocksToRemove,
+      MethodProcessor methodProcessor,
+      MethodProcessingContext methodProcessingContext) {
+    MethodSynthesizerConsumer methodSynthesizerConsumer;
+    if (resolutionResult == null) {
+      methodSynthesizerConsumer =
+          UtilityMethodsForCodeOptimizations::synthesizeThrowNoSuchMethodErrorMethod;
+    } else if (resolutionResult.getResolvedMethod().isStatic() != invoke.isInvokeStatic()) {
+      methodSynthesizerConsumer =
+          UtilityMethodsForCodeOptimizations::synthesizeThrowIncompatibleClassChangeErrorMethod;
+    } else {
+      return false;
+    }
+
+    // Replace by throw new SomeException.
+    UtilityMethodForCodeOptimizations throwMethod =
+        methodSynthesizerConsumer.synthesizeMethod(appView, methodProcessingContext);
+    throwMethod.optimize(methodProcessor);
+
+    InvokeStatic throwInvoke =
+        InvokeStatic.builder()
+            .setMethod(throwMethod.getMethod())
+            .setFreshOutValue(appView, code)
+            .setPosition(invoke)
+            .build();
+    instructions.previous();
+
+    // Split the block before the invoke instruction, and position the block iterator at the newly
+    // created throw block (this involves rewinding the block iterator back over the blocks created
+    // as a result of critical edge splitting, if any).
+    BasicBlock throwBlock = instructions.splitCopyCatchHandlers(code, blockIterator, options);
+    IteratorUtils.previousUntil(blockIterator, block -> block == throwBlock);
+    blockIterator.next();
+
+    // Insert the `SomeException e = throwSomeException()` invoke before the goto
+    // instruction.
+    instructions.previous();
+    instructions.add(throwInvoke);
+
+    // Insert the `throw e` instruction in the newly created throw block.
+    InstructionListIterator throwBlockIterator = throwBlock.listIterator(code);
+    throwBlockIterator.next();
+    throwBlockIterator.replaceCurrentInstructionWithThrow(
+        appView, code, blockIterator, throwInvoke.outValue(), blocksToRemove, affectedValues);
+    return true;
+  }
+
   private DexType maximallySpecificEmulatedInterfaceOrNull(DexMethod invokedMethod) {
     // Here we try to avoid doing the expensive look-up on all invokes.
     if (!emulatedMethods.contains(invokedMethod.name)) {
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/LambdaClass.java b/src/main/java/com/android/tools/r8/ir/desugar/LambdaClass.java
index 477d8d8..076c0c4 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/LambdaClass.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/LambdaClass.java
@@ -4,6 +4,9 @@
 
 package com.android.tools.r8.ir.desugar;
 
+import static com.android.tools.r8.ir.desugar.lambda.ForcefullyMovedLambdaMethodConsumer.emptyForcefullyMovedLambdaMethodConsumer;
+import static com.android.tools.r8.utils.ConsumerUtils.emptyConsumer;
+
 import com.android.tools.r8.dex.Constants;
 import com.android.tools.r8.errors.Unimplemented;
 import com.android.tools.r8.errors.Unreachable;
@@ -30,15 +33,15 @@
 import com.android.tools.r8.graph.ResolutionResult.SingleResolutionResult;
 import com.android.tools.r8.ir.code.Invoke;
 import com.android.tools.r8.ir.code.Invoke.Type;
+import com.android.tools.r8.ir.desugar.lambda.ForcefullyMovedLambdaMethodConsumer;
+import com.android.tools.r8.ir.desugar.lambda.LambdaInstructionDesugaring;
 import com.android.tools.r8.ir.optimize.info.OptimizationFeedback;
 import com.android.tools.r8.ir.optimize.info.OptimizationFeedbackSimple;
-import com.android.tools.r8.ir.synthetic.ForwardMethodSourceCode;
-import com.android.tools.r8.ir.synthetic.SynthesizedCode;
 import com.android.tools.r8.synthesis.SyntheticProgramClassBuilder;
-import com.android.tools.r8.utils.OptionalBool;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
+import java.util.function.Consumer;
 
 /**
  * Represents lambda class generated for a lambda descriptor in context of lambda instantiation
@@ -57,10 +60,14 @@
  */
 public final class LambdaClass {
 
+  public static final String LAMBDA_INSTANCE_FIELD_NAME = "INSTANCE";
+  public static final String JAVAC_EXPECTED_LAMBDA_METHOD_PREFIX = "lambda$";
+  public static final String R8_LAMBDA_ACCESSOR_METHOD_PREFIX = "$r8$lambda$";
+
   private static final OptimizationFeedback feedback = OptimizationFeedbackSimple.getInstance();
 
   final AppView<?> appView;
-  final LambdaRewriter rewriter;
+  final LambdaInstructionDesugaring desugaring;
   public final DexType type;
   public LambdaDescriptor descriptor;
   public final DexMethod constructor;
@@ -71,17 +78,17 @@
   // Considered final but is set after due to circularity in allocation.
   private DexProgramClass clazz = null;
 
-  LambdaClass(
+  public LambdaClass(
       SyntheticProgramClassBuilder builder,
       AppView<?> appView,
-      LambdaRewriter rewriter,
+      LambdaInstructionDesugaring desugaring,
       ProgramMethod accessedFrom,
       LambdaDescriptor descriptor) {
-    assert rewriter != null;
+    assert desugaring != null;
     assert descriptor != null;
     this.type = builder.getType();
     this.appView = appView;
-    this.rewriter = rewriter;
+    this.desugaring = desugaring;
     this.descriptor = descriptor;
 
     DexItemFactory factory = builder.getFactory();
@@ -97,7 +104,7 @@
             ? null
             : factory.createMethod(type, constructorProto, factory.classConstructorMethodName);
     this.lambdaField =
-        !stateless ? null : factory.createField(type, type, rewriter.instanceFieldName);
+        !stateless ? null : factory.createField(type, type, factory.lambdaInstanceFieldName);
 
     // Synthesize the program class one all fields are set.
     synthesizeLambdaClass(builder);
@@ -108,7 +115,11 @@
     return clazz;
   }
 
-  void setClass(DexProgramClass clazz) {
+  public DexType getType() {
+    return type;
+  }
+
+  public void setClass(DexProgramClass clazz) {
     assert this.clazz == null;
     assert clazz != null;
     assert type == clazz.type;
@@ -256,7 +267,8 @@
   // Creates a delegation target for this particular lambda class. Note that we
   // should always be able to create targets for the lambdas we support.
   private Target createTarget(ProgramMethod accessedFrom) {
-    if (descriptor.delegatesToLambdaImplMethod()) {
+    if (appView.options().canAccessModifyLambdaImplementationMethods(appView)
+        && descriptor.delegatesToLambdaImplMethod()) {
       return createLambdaImplMethodTarget(accessedFrom);
     }
 
@@ -339,7 +351,7 @@
     assert descriptor.implHandle.type.isInvokeInstance() ||
         descriptor.implHandle.type.isInvokeDirect();
 
-    if (!descriptor.needsAccessor(accessedFrom)) {
+    if (!descriptor.needsAccessor(appView, accessedFrom)) {
       return new NoAccessorMethodTarget(Invoke.Type.VIRTUAL);
     }
     // We need to generate an accessor method in `accessedFrom` class/interface
@@ -371,7 +383,7 @@
   private Target createStaticMethodTarget(ProgramMethod accessedFrom) {
     assert descriptor.implHandle.type.isInvokeStatic();
 
-    if (!descriptor.needsAccessor(accessedFrom)) {
+    if (!descriptor.needsAccessor(appView, accessedFrom)) {
       return new NoAccessorMethodTarget(Invoke.Type.STATIC);
     }
 
@@ -395,7 +407,7 @@
     assert implHandle != null;
     assert implHandle.type.isInvokeConstructor();
 
-    if (!descriptor.needsAccessor(accessedFrom)) {
+    if (!descriptor.needsAccessor(appView, accessedFrom)) {
       return new NoAccessorMethodTarget(Invoke.Type.DIRECT);
     }
 
@@ -418,14 +430,14 @@
   // Create targets for interface methods.
   private Target createInterfaceMethodTarget(ProgramMethod accessedFrom) {
     assert descriptor.implHandle.type.isInvokeInterface();
-    assert !descriptor.needsAccessor(accessedFrom);
+    assert !descriptor.needsAccessor(appView, accessedFrom);
     return new NoAccessorMethodTarget(Invoke.Type.INTERFACE);
   }
 
   private DexString generateUniqueLambdaMethodName() {
     return appView
         .dexItemFactory()
-        .createString(LambdaRewriter.EXPECTED_LAMBDA_METHOD_PREFIX + descriptor.uniqueId);
+        .createString(R8_LAMBDA_ACCESSOR_METHOD_PREFIX + descriptor.uniqueId);
   }
 
   // Represents information about the method lambda class need to delegate the call to. It may
@@ -447,15 +459,23 @@
     }
 
     // Ensure access of the referenced symbol(s).
-    abstract ProgramMethod ensureAccessibility(boolean allowMethodModification);
+    abstract ProgramMethod ensureAccessibility(
+        ForcefullyMovedLambdaMethodConsumer forcefullyMovedLambdaMethodConsumer,
+        Consumer<ProgramMethod> needsProcessingConsumer);
+
+    public final void ensureAccessibilityIfNeeded() {
+      ensureAccessibilityIfNeeded(emptyForcefullyMovedLambdaMethodConsumer(), emptyConsumer());
+    }
 
     // Ensure access of the referenced symbol(s).
-    public ProgramMethod ensureAccessibilityIfNeeded(boolean allowMethodModification) {
+    public final void ensureAccessibilityIfNeeded(
+        ForcefullyMovedLambdaMethodConsumer forcefullyMovedLambdaMethodConsumer,
+        Consumer<ProgramMethod> needsProcessingConsumer) {
       if (!hasEnsuredAccessibility) {
-        accessibilityBridge = ensureAccessibility(allowMethodModification);
+        accessibilityBridge =
+            ensureAccessibility(forcefullyMovedLambdaMethodConsumer, needsProcessingConsumer);
         hasEnsuredAccessibility = true;
       }
-      return accessibilityBridge;
     }
 
     boolean isInterface() {
@@ -463,6 +483,13 @@
     }
   }
 
+  public abstract class D8SpecificTarget extends Target {
+    D8SpecificTarget(DexMethod callTarget, Type invokeType) {
+      super(callTarget, invokeType);
+      assert !appView.enableWholeProgramOptimizations();
+    }
+  }
+
   // Used for targeting methods referenced directly without creating accessors.
   private final class NoAccessorMethodTarget extends Target {
 
@@ -471,13 +498,15 @@
     }
 
     @Override
-    ProgramMethod ensureAccessibility(boolean allowMethodModification) {
+    ProgramMethod ensureAccessibility(
+        ForcefullyMovedLambdaMethodConsumer forcefullyMovedLambdaMethodConsumer,
+        Consumer<ProgramMethod> needsProcessingConsumer) {
       return null;
     }
   }
 
   // Used for static private lambda$ methods. Only needs access relaxation.
-  private final class StaticLambdaImplTarget extends Target {
+  private final class StaticLambdaImplTarget extends D8SpecificTarget {
 
     final ProgramMethod target;
 
@@ -487,11 +516,14 @@
     }
 
     @Override
-    ProgramMethod ensureAccessibility(boolean allowMethodModification) {
+    ProgramMethod ensureAccessibility(
+        ForcefullyMovedLambdaMethodConsumer forcefullyMovedLambdaMethodConsumer,
+        Consumer<ProgramMethod> needsProcessingConsumer) {
       // We already found the static method to be called, just relax its accessibility.
-      target.getDefinition().accessFlags.unsetPrivate();
+      MethodAccessFlags flags = target.getAccessFlags();
+      flags.unsetPrivate();
       if (target.getHolder().isInterface()) {
-        target.getDefinition().accessFlags.setPublic();
+        flags.setPublic();
       }
       return null;
     }
@@ -499,14 +531,16 @@
 
   // Used for instance private lambda$ methods on interfaces which need to be converted to public
   // static methods. They can't remain instance methods as they will end up on the companion class.
-  private class InterfaceLambdaImplTarget extends Target {
+  private class InterfaceLambdaImplTarget extends D8SpecificTarget {
 
     InterfaceLambdaImplTarget(DexMethod staticMethod) {
       super(staticMethod, Type.STATIC);
     }
 
     @Override
-    ProgramMethod ensureAccessibility(boolean allowMethodModification) {
+    ProgramMethod ensureAccessibility(
+        ForcefullyMovedLambdaMethodConsumer forcefullyMovedLambdaMethodConsumer,
+        Consumer<ProgramMethod> needsProcessingConsumer) {
       // For all instantiation points for which the compiler creates lambda$
       // methods, it creates these methods in the same class/interface.
       DexMethod implMethod = descriptor.implHandle.asMethod();
@@ -523,9 +557,7 @@
                     MethodAccessFlags newAccessFlags = encodedMethod.accessFlags.copy();
                     newAccessFlags.setStatic();
                     newAccessFlags.unsetPrivate();
-                    // Always make the method public to provide access when r8 minification is
-                    // allowed to move the lambda class accessing this method to another package
-                    // (-allowaccessmodification).
+                    // Always make the method public to provide access.
                     newAccessFlags.setPublic();
                     DexEncodedMethod newMethod =
                         new DexEncodedMethod(
@@ -537,14 +569,24 @@
                             encodedMethod.getCode(),
                             true);
                     newMethod.copyMetadata(encodedMethod);
-                    rewriter.forcefullyMoveMethod(encodedMethod.method, callTarget);
+                    forcefullyMovedLambdaMethodConsumer.acceptForcefullyMovedLambdaMethod(
+                        encodedMethod.method, callTarget);
 
                     DexEncodedMethod.setDebugInfoWithFakeThisParameter(
                         newMethod.getCode(), callTarget.getArity(), appView);
                     return newMethod;
                   });
       if (replacement != null) {
-        return new ProgramMethod(implMethodHolder, replacement);
+        // Since we've copied the code object from an existing method, the code should already be
+        // processed, and thus we don't need to schedule it for processing in D8.
+        assert !appView.options().isGeneratingClassFiles() || replacement.getCode().isCfCode();
+        assert !appView.options().isGeneratingDex() || replacement.getCode().isDexCode();
+        ProgramMethod newMethod = new ProgramMethod(implMethodHolder, replacement);
+        if (appView.options().isDesugaredLibraryCompilation()) {
+          assert appView.options().isGeneratingClassFiles();
+          needsProcessingConsumer.accept(newMethod);
+        }
+        return newMethod;
       }
       // The method might already have been moved by another invoke-dynamic targeting it.
       // If so, it must be defined on the holder.
@@ -565,33 +607,30 @@
     }
 
     @Override
-    ProgramMethod ensureAccessibility(boolean allowMethodModification) {
+    ProgramMethod ensureAccessibility(
+        ForcefullyMovedLambdaMethodConsumer forcefullyMovedLambdaMethodConsumer,
+        Consumer<ProgramMethod> needsProcessingConsumer) {
       return null;
     }
   }
 
   // Used for instance private lambda$ methods which need to be converted to public methods.
-  private class InstanceLambdaImplTarget extends Target {
+  private class InstanceLambdaImplTarget extends D8SpecificTarget {
 
     InstanceLambdaImplTarget(DexMethod staticMethod) {
       super(staticMethod, Type.VIRTUAL);
     }
 
     @Override
-    ProgramMethod ensureAccessibility(boolean allowMethodModification) {
+    ProgramMethod ensureAccessibility(
+        ForcefullyMovedLambdaMethodConsumer forcefullyMovedLambdaMethodConsumer,
+        Consumer<ProgramMethod> needsProcessingConsumer) {
       // When compiling with whole program optimization, check that we are not inplace modifying.
-      assert !(appView.enableWholeProgramOptimizations() && allowMethodModification);
       // For all instantiation points for which the compiler creates lambda$
       // methods, it creates these methods in the same class/interface.
       DexMethod implMethod = descriptor.implHandle.asMethod();
       DexProgramClass implMethodHolder = appView.definitionFor(implMethod.holder).asProgramClass();
-      return allowMethodModification
-          ? modifyLambdaImplementationMethod(implMethod, implMethodHolder)
-          : createSyntheticAccessor(implMethod, implMethodHolder);
-    }
 
-    private ProgramMethod modifyLambdaImplementationMethod(
-        DexMethod implMethod, DexProgramClass implMethodHolder) {
       DexEncodedMethod replacement =
           implMethodHolder
               .getMethodCollection()
@@ -614,11 +653,21 @@
                             encodedMethod.getCode(),
                             true);
                     newMethod.copyMetadata(encodedMethod);
-                    rewriter.forcefullyMoveMethod(encodedMethod.method, callTarget);
+                    forcefullyMovedLambdaMethodConsumer.acceptForcefullyMovedLambdaMethod(
+                        encodedMethod.method, callTarget);
                     return newMethod;
                   });
       if (replacement != null) {
-        return new ProgramMethod(implMethodHolder, replacement);
+        // Since we've copied the code object from an existing method, the code should already be
+        // processed, and thus we don't need to schedule it for processing in D8.
+        assert !appView.options().isGeneratingClassFiles() || replacement.getCode().isCfCode();
+        assert !appView.options().isGeneratingDex() || replacement.getCode().isDexCode();
+        ProgramMethod newMethod = new ProgramMethod(implMethodHolder, replacement);
+        if (appView.options().isDesugaredLibraryCompilation()) {
+          assert appView.options().isGeneratingClassFiles();
+          needsProcessingConsumer.accept(newMethod);
+        }
+        return newMethod;
       }
       // The method might already have been moved by another invoke-dynamic targeting it.
       // If so, it must be defined on the holder.
@@ -627,45 +676,6 @@
       assert modified.getDefinition().isNonPrivateVirtualMethod();
       return modified;
     }
-
-    private ProgramMethod createSyntheticAccessor(
-        DexMethod implMethod, DexProgramClass implMethodHolder) {
-      // The accessor might already have been created by another invoke-dynamic targeting it.
-      ProgramMethod existing = implMethodHolder.lookupProgramMethod(callTarget);
-      if (existing != null) {
-        assert existing.getAccessFlags().isSynthetic();
-        assert existing.getAccessFlags().isPublic();
-        assert existing.getDefinition().isVirtualMethod();
-        return existing;
-      }
-      MethodAccessFlags accessorFlags =
-          MethodAccessFlags.fromSharedAccessFlags(
-              Constants.ACC_SYNTHETIC | Constants.ACC_PUBLIC, false);
-
-      ForwardMethodSourceCode.Builder forwardSourceCodeBuilder =
-          ForwardMethodSourceCode.builder(callTarget)
-              .setReceiver(implMethod.holder)
-              .setTargetReceiver(implMethod.holder)
-              .setTarget(implMethod)
-              .setInvokeType(Type.DIRECT)
-              .setIsInterface(false);
-
-      DexEncodedMethod accessorEncodedMethod =
-          new DexEncodedMethod(
-              callTarget,
-              accessorFlags,
-              MethodTypeSignature.noSignature(),
-              DexAnnotationSet.empty(),
-              ParameterAnnotationsList.empty(),
-              new SynthesizedCode(
-                  forwardSourceCodeBuilder::build,
-                  registry -> registry.registerInvokeDirect(implMethod)),
-              true);
-      accessorEncodedMethod.setLibraryMethodOverride(OptionalBool.FALSE);
-
-      implMethodHolder.addVirtualMethod(accessorEncodedMethod);
-      return new ProgramMethod(implMethodHolder, accessorEncodedMethod);
-    }
   }
 
   // Used for instance/static methods or constructors accessed via
@@ -677,7 +687,9 @@
     }
 
     @Override
-    ProgramMethod ensureAccessibility(boolean allowMethodModification) {
+    ProgramMethod ensureAccessibility(
+        ForcefullyMovedLambdaMethodConsumer forcefullyMovedLambdaMethodConsumer,
+        Consumer<ProgramMethod> needsProcessingConsumer) {
       // Create a static accessor with proper accessibility.
       DexProgramClass accessorClass = appView.definitionForProgramType(callTarget.holder);
       assert accessorClass != null;
@@ -693,23 +705,23 @@
 
       // Always make the method public to provide access when r8 minification is allowed to move
       // the lambda class accessing this method to another package (-allowaccessmodification).
-      MethodAccessFlags accessorFlags =
-          MethodAccessFlags.fromSharedAccessFlags(
-              Constants.ACC_SYNTHETIC | Constants.ACC_STATIC | Constants.ACC_PUBLIC,
-              false);
-
-      DexEncodedMethod accessorEncodedMethod =
-          new DexEncodedMethod(
-              callTarget,
-              accessorFlags,
-              MethodTypeSignature.noSignature(),
-              DexAnnotationSet.empty(),
-              ParameterAnnotationsList.empty(),
-              AccessorMethodSourceCode.build(LambdaClass.this, callTarget),
-              true);
-
-      accessorClass.addDirectMethod(accessorEncodedMethod);
-      return new ProgramMethod(accessorClass, accessorEncodedMethod);
+      ProgramMethod accessorMethod =
+          new ProgramMethod(
+              accessorClass,
+              new DexEncodedMethod(
+                  callTarget,
+                  MethodAccessFlags.createPublicStaticSynthetic(),
+                  MethodTypeSignature.noSignature(),
+                  DexAnnotationSet.empty(),
+                  ParameterAnnotationsList.empty(),
+                  AccessorMethodSourceCode.build(LambdaClass.this, callTarget),
+                  true));
+      accessorClass.addDirectMethod(accessorMethod.getDefinition());
+      if (appView.options().isDesugaredLibraryCompilation()
+          || appView.options().isGeneratingDex()) {
+        needsProcessingConsumer.accept(accessorMethod);
+      }
+      return accessorMethod;
     }
   }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/LambdaDescriptor.java b/src/main/java/com/android/tools/r8/ir/desugar/LambdaDescriptor.java
index f661256..3cc480d 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/LambdaDescriptor.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/LambdaDescriptor.java
@@ -4,8 +4,11 @@
 
 package com.android.tools.r8.ir.desugar;
 
+import static com.android.tools.r8.ir.desugar.LambdaClass.JAVAC_EXPECTED_LAMBDA_METHOD_PREFIX;
+
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
+import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexCallSite;
 import com.android.tools.r8.graph.DexDefinitionSupplier;
 import com.android.tools.r8.graph.DexEncodedMethod;
@@ -177,10 +180,14 @@
     return targetHolder == type;
   }
 
+  public boolean canAccessModifyLambdaImplementationMethods(AppView<?> appView) {
+    return appView.enableWholeProgramOptimizations();
+  }
+
   /** If the lambda delegates to lambda$ method. */
   public boolean delegatesToLambdaImplMethod() {
-    DexString methodName = implHandle.asMethod().name;
-    return methodName.toString().startsWith(LambdaRewriter.EXPECTED_LAMBDA_METHOD_PREFIX);
+    String methodName = implHandle.asMethod().getName().toString();
+    return methodName.startsWith(JAVAC_EXPECTED_LAMBDA_METHOD_PREFIX);
   }
 
   /** Is a stateless lambda, i.e. lambda does not capture any values */
@@ -189,8 +196,9 @@
   }
 
   /** Checks if call site needs a accessor when referenced from `accessedFrom`. */
-  boolean needsAccessor(ProgramMethod accessedFrom) {
-    if (delegatesToLambdaImplMethod()) {
+  boolean needsAccessor(AppView<?> appView, ProgramMethod accessedFrom) {
+    if (appView.options().canAccessModifyLambdaImplementationMethods(appView)
+        && delegatesToLambdaImplMethod()) {
       return false;
     }
 
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
deleted file mode 100644
index b12b2a7..0000000
--- a/src/main/java/com/android/tools/r8/ir/desugar/LambdaRewriter.java
+++ /dev/null
@@ -1,318 +0,0 @@
-// Copyright (c) 2017, 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.CfFieldInstruction;
-import com.android.tools.r8.cf.code.CfInstruction;
-import com.android.tools.r8.cf.code.CfInvoke;
-import com.android.tools.r8.cf.code.CfInvokeDynamic;
-import com.android.tools.r8.cf.code.CfLoad;
-import com.android.tools.r8.cf.code.CfNew;
-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;
-import com.android.tools.r8.graph.DexCallSite;
-import com.android.tools.r8.graph.DexField;
-import com.android.tools.r8.graph.DexItemFactory;
-import com.android.tools.r8.graph.DexMethod;
-import com.android.tools.r8.graph.DexProgramClass;
-import com.android.tools.r8.graph.DexString;
-import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.graph.EnclosingMethodAttribute;
-import com.android.tools.r8.graph.GraphLens;
-import com.android.tools.r8.graph.GraphLens.NestedGraphLens;
-import com.android.tools.r8.graph.ProgramMethod;
-import com.android.tools.r8.ir.code.ValueType;
-import com.android.tools.r8.ir.conversion.IRConverter;
-import com.android.tools.r8.synthesis.SyntheticNaming;
-import com.android.tools.r8.utils.Box;
-import com.android.tools.r8.utils.collections.BidirectionalManyToOneRepresentativeMap;
-import com.android.tools.r8.utils.collections.BidirectionalOneToOneMap;
-import com.android.tools.r8.utils.collections.SortedProgramMethodSet;
-import com.google.common.base.Suppliers;
-import com.google.common.collect.ImmutableSet;
-import com.google.common.collect.Sets;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.ExecutionException;
-import java.util.concurrent.ExecutorService;
-import java.util.function.Function;
-import java.util.function.Supplier;
-import org.objectweb.asm.Opcodes;
-
-/**
- * Lambda desugaring rewriter.
- *
- * <p>Performs lambda instantiation point matching, lambda class generation, and instruction
- * patching.
- */
-public class LambdaRewriter {
-
-  static final String EXPECTED_LAMBDA_METHOD_PREFIX = "lambda$";
-  public static final String LAMBDA_INSTANCE_FIELD_NAME = "INSTANCE";
-
-  private final AppView<?> appView;
-
-  final DexString instanceFieldName;
-
-  private final LambdaRewriterLens.Builder lensBuilder = LambdaRewriterLens.builder();
-  private final Set<DexMethod> forcefullyMovedMethods = Sets.newIdentityHashSet();
-
-  // Maps lambda class type into lambda class representation.
-  // NOTE: synchronize concurrent access on `knownLambdaClasses`.
-  private final List<LambdaClass> knownLambdaClasses = new ArrayList<>();
-
-  private final Map<DexMethod, Integer> methodIds = new ConcurrentHashMap<>();
-
-  public LambdaRewriter(AppView<?> appView) {
-    this.appView = appView;
-    this.instanceFieldName = appView.dexItemFactory().createString(LAMBDA_INSTANCE_FIELD_NAME);
-  }
-
-  void forcefullyMoveMethod(DexMethod from, DexMethod to) {
-    lensBuilder.move(from, to);
-    forcefullyMovedMethods.add(from);
-  }
-
-  public Set<DexMethod> getForcefullyMovedMethods() {
-    return forcefullyMovedMethods;
-  }
-
-  private void synthesizeAccessibilityBridgesForLambdaClassesD8(
-      Collection<LambdaClass> lambdaClasses, IRConverter converter, ExecutorService executorService)
-      throws ExecutionException {
-    SortedProgramMethodSet nonDexAccessibilityBridges = SortedProgramMethodSet.create();
-    List<LambdaClass> sortedLambdaClasses = new ArrayList<>(lambdaClasses);
-    sortedLambdaClasses.sort((x, y) -> x.type.compareTo(y.type));
-    for (LambdaClass lambdaClass : sortedLambdaClasses) {
-      // This call may cause originalMethodSignatures to be updated.
-      ProgramMethod accessibilityBridge = lambdaClass.target.ensureAccessibilityIfNeeded(true);
-      if (accessibilityBridge != null
-          && !accessibilityBridge.getDefinition().getCode().isDexCode()) {
-        nonDexAccessibilityBridges.add(accessibilityBridge);
-      }
-    }
-    if (!nonDexAccessibilityBridges.isEmpty()) {
-      converter.processMethodsConcurrently(nonDexAccessibilityBridges, executorService);
-    }
-  }
-
-  /**
-   * Detect and desugar lambdas and method references found in the code.
-   *
-   * <p>NOTE: this method can be called concurrently for several different methods.
-   */
-  public int desugarLambdas(
-      ProgramMethod method,
-      AppInfoWithClassHierarchy appInfo,
-      MethodProcessingContext methodProcessingContext) {
-    return desugarLambdas(
-        method,
-        callsite -> {
-          LambdaDescriptor descriptor = LambdaDescriptor.tryInfer(callsite, appInfo, method);
-          if (descriptor == null) {
-            return null;
-          }
-          return createLambdaClass(descriptor, method, methodProcessingContext);
-        });
-  }
-
-  // Same as above, but where lambdas are always known to exist for the call sites.
-  public static int desugarLambdas(
-      ProgramMethod method, Function<DexCallSite, LambdaClass> callSites) {
-    CfCode code = method.getDefinition().getCode().asCfCode();
-    List<CfInstruction> instructions = code.getInstructions();
-    Supplier<List<CfInstruction>> lazyNewInstructions =
-        Suppliers.memoize(() -> new ArrayList<>(instructions));
-    int replaced = 0;
-    int maxTemp = 0;
-    int newInstructionsDelta = 0;
-    for (int i = 0; i < instructions.size(); i++) {
-      CfInstruction instruction = instructions.get(i);
-      if (instruction instanceof CfInvokeDynamic) {
-        LambdaClass lambdaClass = callSites.apply(((CfInvokeDynamic) instruction).getCallSite());
-        if (lambdaClass == null) {
-          continue;
-        }
-        int newInstructionsIndex = i + newInstructionsDelta;
-        if (lambdaClass.isStateless()) {
-          CfFieldInstruction getStaticLambdaInstance =
-              new CfFieldInstruction(
-                  Opcodes.GETSTATIC, lambdaClass.lambdaField, lambdaClass.lambdaField);
-          lazyNewInstructions.get().set(newInstructionsIndex, getStaticLambdaInstance);
-        } else {
-          List<CfInstruction> replacement = new ArrayList<>();
-          int arguments = lambdaClass.descriptor.captures.size();
-          int temp = code.getMaxLocals();
-          for (int j = arguments - 1; j >= 0; j--) {
-            ValueType type = ValueType.fromDexType(lambdaClass.descriptor.captures.values[j]);
-            replacement.add(new CfStore(type, temp));
-            temp += type.requiredRegisters();
-          }
-          maxTemp = Math.max(temp, maxTemp);
-          replacement.add(new CfNew(lambdaClass.type));
-          replacement.add(new CfStackInstruction(Opcode.Dup));
-          for (int j = 0; j < arguments; j++) {
-            ValueType type = ValueType.fromDexType(lambdaClass.descriptor.captures.values[j]);
-            temp -= type.requiredRegisters();
-            replacement.add(new CfLoad(type, temp));
-          }
-          replacement.add(new CfInvoke(Opcodes.INVOKESPECIAL, lambdaClass.constructor, false));
-          List<CfInstruction> newInstructions = lazyNewInstructions.get();
-          newInstructions.remove(newInstructionsIndex);
-          newInstructions.addAll(newInstructionsIndex, replacement);
-          newInstructionsDelta += replacement.size() - 1;
-        }
-        ++replaced;
-      }
-    }
-    if (maxTemp > 0) {
-      assert maxTemp > code.getMaxLocals();
-      code.setMaxLocals(maxTemp);
-    }
-    if (replaced > 0) {
-      code.setInstructions(lazyNewInstructions.get());
-    }
-    return replaced;
-  }
-
-  /** Remove lambda deserialization methods. */
-  public void removeLambdaDeserializationMethods(Iterable<DexProgramClass> classes) {
-    for (DexProgramClass clazz : classes) {
-      clazz.removeMethod(appView.dexItemFactory().deserializeLambdaMethod);
-    }
-  }
-
-  /** Generates lambda classes and adds them to the builder. */
-  public void finalizeLambdaDesugaringForD8(IRConverter converter, ExecutorService executorService)
-      throws ExecutionException {
-    synthesizeAccessibilityBridgesForLambdaClassesD8(
-        knownLambdaClasses, converter, executorService);
-    fixup();
-    optimizeSynthesizedClasses(converter, executorService);
-  }
-
-  private void optimizeSynthesizedClasses(IRConverter converter, ExecutorService executorService)
-      throws ExecutionException {
-    converter.optimizeSynthesizedClasses(
-        knownLambdaClasses.stream()
-            .map(LambdaClass::getLambdaProgramClass)
-            .collect(ImmutableSet.toImmutableSet()),
-        executorService);
-  }
-
-  // Creates a lambda class corresponding to the lambda descriptor and context.
-  public LambdaClass createLambdaClass(
-      LambdaDescriptor descriptor, ProgramMethod accessedFrom, MethodProcessingContext context) {
-    Box<LambdaClass> box = new Box<>();
-    DexProgramClass clazz =
-        appView
-            .getSyntheticItems()
-            .createClass(
-                SyntheticNaming.SyntheticKind.LAMBDA,
-                context.createUniqueContext(),
-                appView.dexItemFactory(),
-                builder ->
-                    box.set(new LambdaClass(builder, appView, this, accessedFrom, descriptor)));
-    // Immediately set the actual program class on the lambda.
-    LambdaClass lambdaClass = box.get();
-    lambdaClass.setClass(clazz);
-    synchronized (knownLambdaClasses) {
-      knownLambdaClasses.add(lambdaClass);
-    }
-    return lambdaClass;
-  }
-
-  public Collection<LambdaClass> getKnownLambdaClasses() {
-    return Collections.unmodifiableList(knownLambdaClasses);
-  }
-
-  public NestedGraphLens fixup() {
-    LambdaRewriterLens lens = lensBuilder.build(appView.graphLens(), appView.dexItemFactory());
-    if (lens == null) {
-      return null;
-    }
-    for (DexProgramClass clazz : appView.appInfo().classes()) {
-      EnclosingMethodAttribute enclosingMethod = clazz.getEnclosingMethodAttribute();
-      if (enclosingMethod != null) {
-        if (enclosingMethod.getEnclosingMethod() != null) {
-          DexMethod mappedEnclosingMethod = lens.lookupMethod(enclosingMethod.getEnclosingMethod());
-          if (mappedEnclosingMethod != enclosingMethod.getEnclosingMethod()) {
-            clazz.setEnclosingMethodAttribute(new EnclosingMethodAttribute(mappedEnclosingMethod));
-          }
-        } else {
-          assert enclosingMethod.getEnclosingClass() != null;
-          DexType mappedEnclosingClass = lens.lookupType(enclosingMethod.getEnclosingClass());
-          if (mappedEnclosingClass != enclosingMethod.getEnclosingClass()) {
-            clazz.setEnclosingMethodAttribute(new EnclosingMethodAttribute(mappedEnclosingClass));
-          }
-        }
-      }
-    }
-    // Return lens without method map (but still retaining originalMethodSignatures), as the
-    // generated lambdas classes are generated with the an invoke to the new method, so no
-    // code rewriting is required.
-    return lens.withoutMethodMap();
-  }
-
-  static class LambdaRewriterLens extends NestedGraphLens {
-
-    LambdaRewriterLens(
-        Map<DexType, DexType> typeMap,
-        Map<DexMethod, DexMethod> methodMap,
-        BidirectionalManyToOneRepresentativeMap<DexField, DexField> fieldMap,
-        BidirectionalOneToOneMap<DexMethod, DexMethod> originalMethodSignatures,
-        GraphLens previousLens,
-        DexItemFactory dexItemFactory) {
-      super(
-          typeMap,
-          methodMap,
-          fieldMap,
-          originalMethodSignatures,
-          previousLens,
-          dexItemFactory);
-    }
-
-    @Override
-    protected boolean isLegitimateToHaveEmptyMappings() {
-      return true;
-    }
-
-    private LambdaRewriterLens withoutMethodMap() {
-      methodMap.clear();
-      return this;
-    }
-
-    public static LambdaRewriterLens.Builder builder() {
-      return new LambdaRewriterLens.Builder();
-    }
-
-    public static class Builder extends NestedGraphLens.Builder {
-      public LambdaRewriterLens build(GraphLens previousLens, DexItemFactory dexItemFactory) {
-        if (typeMap.isEmpty() && methodMap.isEmpty() && fieldMap.isEmpty()) {
-          return null;
-        }
-        assert typeMap.isEmpty();
-        assert fieldMap.isEmpty();
-        return new LambdaRewriterLens(
-            typeMap,
-            methodMap,
-            fieldMap,
-            originalMethodSignatures,
-            previousLens,
-            dexItemFactory);
-      }
-    }
-  }
-}
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
index 6a11e86..c34e32c 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/NonEmptyCfInstructionDesugaringCollection.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/NonEmptyCfInstructionDesugaringCollection.java
@@ -4,19 +4,26 @@
 
 package com.android.tools.r8.ir.desugar;
 
+
 import com.android.tools.r8.cf.code.CfInstruction;
+import com.android.tools.r8.contexts.CompilationContext.MethodProcessingContext;
 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.lambda.LambdaInstructionDesugaring;
+import com.android.tools.r8.ir.desugar.nest.D8NestBasedAccessDesugaring;
 import com.android.tools.r8.ir.desugar.nest.NestBasedAccessDesugaring;
+import com.android.tools.r8.utils.IntBox;
 import com.android.tools.r8.utils.IteratorUtils;
 import com.android.tools.r8.utils.ListUtils;
 import com.android.tools.r8.utils.StringDiagnostic;
+import com.android.tools.r8.utils.ThrowingConsumer;
 import com.google.common.collect.Iterables;
 import java.util.ArrayList;
+import java.util.Collection;
 import java.util.Iterator;
 import java.util.List;
 
@@ -24,26 +31,40 @@
 
   private final AppView<?> appView;
   private final List<CfInstructionDesugaring> desugarings = new ArrayList<>();
-  private final InvokeSpecialToSelfDesugaring invokeSpecialToSelfDesugaring;
+
   private final NestBasedAccessDesugaring nestBasedAccessDesugaring;
 
-  public NonEmptyCfInstructionDesugaringCollection(AppView<?> appView) {
+  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);
+    this.nestBasedAccessDesugaring = NestBasedAccessDesugaring.create(appView);
+    desugarings.add(new LambdaInstructionDesugaring(appView));
+    desugarings.add(new InvokeSpecialToSelfDesugaring(appView));
+    if (nestBasedAccessDesugaring != null) {
+      desugarings.add(nestBasedAccessDesugaring);
     }
   }
 
+  // TODO(b/145775365): special constructor for cf-to-cf compilations with desugaring disabled.
+  //  This should be removed once we can represent invoke-special instructions in the IR.
+  private NonEmptyCfInstructionDesugaringCollection(
+      AppView<?> appView, InvokeSpecialToSelfDesugaring invokeSpecialToSelfDesugaring) {
+    this.appView = appView;
+    this.nestBasedAccessDesugaring = null;
+    desugarings.add(invokeSpecialToSelfDesugaring);
+  }
+
+  static NonEmptyCfInstructionDesugaringCollection createForCfToCfNonDesugar(AppView<?> appView) {
+    assert appView.options().desugarState.isOff();
+    assert appView.options().isGeneratingClassFiles();
+    return new NonEmptyCfInstructionDesugaringCollection(
+        appView, new InvokeSpecialToSelfDesugaring(appView));
+  }
+
   @Override
-  public void desugar(ProgramMethod method, CfInstructionDesugaringEventConsumer consumer) {
+  public void desugar(
+      ProgramMethod method,
+      MethodProcessingContext methodProcessingContext,
+      CfInstructionDesugaringEventConsumer eventConsumer) {
     Code code = method.getDefinition().getCode();
     if (!code.isCfCode()) {
       appView
@@ -58,30 +79,60 @@
     }
 
     CfCode cfCode = code.asCfCode();
+
+    // Tracking of temporary locals used for instruction desugaring. The desugaring of each
+    // instruction is assumed to use locals only for the duration of the instruction, such that any
+    // temporarily used locals will be free again at the next instruction to be desugared.
+    IntBox maxLocalsForCode = new IntBox(cfCode.getMaxLocals());
+    IntBox maxLocalsForInstruction = new IntBox(cfCode.getMaxLocals());
+
     List<CfInstruction> desugaredInstructions =
         ListUtils.flatMap(
             cfCode.getInstructions(),
-            instruction -> desugarInstruction(instruction, consumer, method),
+            instruction -> {
+              Collection<CfInstruction> replacement =
+                  desugarInstruction(
+                      instruction,
+                      maxLocalsForInstruction::getAndIncrement,
+                      eventConsumer,
+                      method,
+                      methodProcessingContext);
+              if (replacement != null) {
+                // Record if we increased the max number of locals for the method, and reset the
+                // next temporary locals register.
+                maxLocalsForCode.setMax(maxLocalsForInstruction.getAndSet(cfCode.getMaxLocals()));
+              } else {
+                // The next temporary locals register should be unchanged.
+                assert maxLocalsForInstruction.get() == cfCode.getMaxLocals();
+              }
+              return replacement;
+            },
             null);
     if (desugaredInstructions != null) {
+      assert maxLocalsForCode.get() >= cfCode.getMaxLocals();
       cfCode.setInstructions(desugaredInstructions);
+      cfCode.setMaxLocals(maxLocalsForCode.get());
     } else {
       assert false : "Expected code to be desugared";
     }
   }
 
-  private List<CfInstruction> desugarInstruction(
+  private Collection<CfInstruction> desugarInstruction(
       CfInstruction instruction,
-      CfInstructionDesugaringEventConsumer consumer,
-      ProgramMethod context) {
+      FreshLocalProvider freshLocalProvider,
+      CfInstructionDesugaringEventConsumer eventConsumer,
+      ProgramMethod context,
+      MethodProcessingContext methodProcessingContext) {
     // 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);
+      Collection<CfInstruction> replacement =
+          desugaring.desugarInstruction(
+              instruction, freshLocalProvider, eventConsumer, context, methodProcessingContext);
       if (replacement != null) {
-        assert verifyNoOtherDesugaringNeeded(instruction, context, iterator);
+        assert verifyNoOtherDesugaringNeeded(
+            instruction, context, methodProcessingContext, iterator);
         return replacement;
       }
     }
@@ -115,16 +166,31 @@
   private static boolean verifyNoOtherDesugaringNeeded(
       CfInstruction instruction,
       ProgramMethod context,
+      MethodProcessingContext methodProcessingContext,
       Iterator<CfInstructionDesugaring> iterator) {
     assert IteratorUtils.nextUntil(
             iterator,
             desugaring ->
                 desugaring.desugarInstruction(
                         instruction,
+                        requiredRegisters -> {
+                          assert false;
+                          return 0;
+                        },
                         CfInstructionDesugaringEventConsumer.createForDesugaredCode(),
-                        context)
+                        context,
+                        methodProcessingContext)
                     != null)
         == null;
     return true;
   }
+
+  @Override
+  public <T extends Throwable> void withD8NestBasedAccessDesugaring(
+      ThrowingConsumer<D8NestBasedAccessDesugaring, T> consumer) throws T {
+    if (nestBasedAccessDesugaring != null) {
+      assert nestBasedAccessDesugaring instanceof D8NestBasedAccessDesugaring;
+      consumer.accept((D8NestBasedAccessDesugaring) nestBasedAccessDesugaring);
+    }
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/StringConcatRewriter.java b/src/main/java/com/android/tools/r8/ir/desugar/StringConcatRewriter.java
index 4b33d2f..514ee97 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/StringConcatRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/StringConcatRewriter.java
@@ -18,7 +18,6 @@
 import com.android.tools.r8.graph.DexValue.DexValueString;
 import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.code.BasicBlock;
-import com.android.tools.r8.ir.code.BasicBlock.ThrowingInfo;
 import com.android.tools.r8.ir.code.ConstString;
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.Instruction;
@@ -441,11 +440,7 @@
       @Override
       Value getOrCreateValue() {
         Value value = code.createValue(TypeElement.stringClassType(appView, definitelyNotNull()));
-        appendInstruction(
-            new ConstString(
-                value,
-                factory.createString(str),
-                ThrowingInfo.defaultForConstString(appView.options())));
+        appendInstruction(new ConstString(value, factory.createString(str)));
         return value;
       }
     }
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
index 2d99341..a4f4b9b 100644
--- 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
@@ -6,9 +6,9 @@
 
 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.Code;
 import com.android.tools.r8.graph.DexClassAndMethod;
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexMethod;
@@ -16,10 +16,10 @@
 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.desugar.FreshLocalProvider;
 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.Collection;
 import java.util.List;
 import org.objectweb.asm.Opcodes;
 
@@ -28,17 +28,15 @@
 
   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()) {
+    if (instruction.isInvokeSpecial()) {
       return needsDesugaring(instruction.asInvoke(), context) != null;
     }
     return false;
@@ -66,46 +64,23 @@
     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(
+  public Collection<CfInstruction> desugarInstruction(
       CfInstruction instruction,
-      CfInstructionDesugaringEventConsumer consumer,
-      ProgramMethod context) {
-    if (instruction.isInvoke()) {
-      return desugarInvokeInstruction(instruction.asInvoke(), consumer, context);
+      FreshLocalProvider freshLocalProvider,
+      CfInstructionDesugaringEventConsumer eventConsumer,
+      ProgramMethod context,
+      MethodProcessingContext methodProcessingContext) {
+    if (instruction.isInvokeSpecial()) {
+      return desugarInvokeInstruction(instruction.asInvoke(), eventConsumer, context);
     }
     return null;
   }
 
   private List<CfInstruction> desugarInvokeInstruction(
-      CfInvoke invoke, CfInstructionDesugaringEventConsumer consumer, ProgramMethod context) {
+      CfInvoke invoke,
+      InvokeSpecialToSelfDesugaringEventConsumer eventConsumer,
+      ProgramMethod context) {
     ProgramMethod method = needsDesugaring(invoke, context);
     if (method == null) {
       return null;
@@ -119,13 +94,13 @@
 
     // 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);
+    DexMethod bridgeMethod = ensureInvokeSpecialBridge(method, eventConsumer);
     return ImmutableList.of(
         new CfInvoke(Opcodes.INVOKESPECIAL, bridgeMethod, invoke.isInterface()));
   }
 
   private DexMethod ensureInvokeSpecialBridge(
-      ProgramMethod method, CfInstructionDesugaringEventConsumer consumer) {
+      ProgramMethod method, InvokeSpecialToSelfDesugaringEventConsumer eventConsumer) {
     DexMethod bridgeReference = getInvokeSpecialBridgeReference(method);
     DexProgramClass clazz = method.getHolder();
     synchronized (clazz.getMethodCollection()) {
@@ -144,7 +119,7 @@
         // Add the newly created direct method to its holder.
         clazz.addDirectMethod(newDirectMethod.getDefinition());
 
-        consumer.acceptInvokeSpecialBridgeInfo(
+        eventConsumer.acceptInvokeSpecialBridgeInfo(
             new InvokeSpecialBridgeInfo(newDirectMethod, method, virtualMethodCode));
       }
     }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/invokespecial/InvokeSpecialToSelfDesugaringEventConsumer.java b/src/main/java/com/android/tools/r8/ir/desugar/invokespecial/InvokeSpecialToSelfDesugaringEventConsumer.java
new file mode 100644
index 0000000..b706a7b
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/desugar/invokespecial/InvokeSpecialToSelfDesugaringEventConsumer.java
@@ -0,0 +1,10 @@
+// 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;
+
+public interface InvokeSpecialToSelfDesugaringEventConsumer {
+
+  void acceptInvokeSpecialBridgeInfo(InvokeSpecialBridgeInfo info);
+}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/lambda/D8LambdaDesugaring.java b/src/main/java/com/android/tools/r8/ir/desugar/lambda/D8LambdaDesugaring.java
new file mode 100644
index 0000000..2d89fce
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/desugar/lambda/D8LambdaDesugaring.java
@@ -0,0 +1,56 @@
+// 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.lambda;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.EnclosingMethodAttribute;
+import com.android.tools.r8.ir.conversion.ClassConverterResult;
+import com.android.tools.r8.ir.conversion.D8MethodProcessor;
+import com.android.tools.r8.utils.collections.ProgramMethodSet;
+import java.util.IdentityHashMap;
+import java.util.Map;
+import java.util.concurrent.ExecutionException;
+
+public class D8LambdaDesugaring {
+
+  public static void synthesizeAccessibilityBridgesForLambdaClasses(
+      AppView<?> appView,
+      ClassConverterResult classConverterResult,
+      D8MethodProcessor methodProcessor)
+      throws ExecutionException {
+    Map<DexMethod, DexMethod> forcefullyMovedLambdaMethods = new IdentityHashMap<>();
+    ProgramMethodSet seenAccessibilityBridges = ProgramMethodSet.createConcurrent();
+    classConverterResult.forEachSynthesizedLambdaClassWithDeterministicOrdering(
+        lambdaClass -> {
+          // Collect the accessibility bridges that require processing. Note that we cannot schedule
+          // the methods for processing directly here, since that would lead to concurrent IR
+          // processing meanwhile we update the program (insert bridges on existing classes).
+          lambdaClass.target.ensureAccessibilityIfNeeded(
+              forcefullyMovedLambdaMethods::put, seenAccessibilityBridges::add);
+        });
+    methodProcessor
+        .scheduleDesugaredMethodsForProcessing(seenAccessibilityBridges)
+        .awaitMethodProcessing();
+    rewriteEnclosingMethodAttributes(appView, forcefullyMovedLambdaMethods);
+  }
+
+  private static void rewriteEnclosingMethodAttributes(
+      AppView<?> appView, Map<DexMethod, DexMethod> forcefullyMovedLambdaMethods) {
+    if (forcefullyMovedLambdaMethods.isEmpty()) {
+      return;
+    }
+    for (DexProgramClass clazz : appView.appInfo().classes()) {
+      if (clazz.hasEnclosingMethodAttribute()) {
+        DexMethod enclosingMethod = clazz.getEnclosingMethodAttribute().getEnclosingMethod();
+        DexMethod rewrittenEnclosingMethod = forcefullyMovedLambdaMethods.get(enclosingMethod);
+        if (rewrittenEnclosingMethod != null) {
+          clazz.setEnclosingMethodAttribute(new EnclosingMethodAttribute(rewrittenEnclosingMethod));
+        }
+      }
+    }
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/lambda/ForcefullyMovedLambdaMethodConsumer.java b/src/main/java/com/android/tools/r8/ir/desugar/lambda/ForcefullyMovedLambdaMethodConsumer.java
new file mode 100644
index 0000000..1925f61
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/desugar/lambda/ForcefullyMovedLambdaMethodConsumer.java
@@ -0,0 +1,16 @@
+// 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.lambda;
+
+import com.android.tools.r8.graph.DexMethod;
+
+public interface ForcefullyMovedLambdaMethodConsumer {
+
+  void acceptForcefullyMovedLambdaMethod(DexMethod from, DexMethod to);
+
+  static ForcefullyMovedLambdaMethodConsumer emptyForcefullyMovedLambdaMethodConsumer() {
+    return (from, to) -> {};
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/lambda/LambdaDeserializationMethodRemover.java b/src/main/java/com/android/tools/r8/ir/desugar/lambda/LambdaDeserializationMethodRemover.java
new file mode 100644
index 0000000..d496b33
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/desugar/lambda/LambdaDeserializationMethodRemover.java
@@ -0,0 +1,30 @@
+// 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.lambda;
+
+import com.android.tools.r8.graph.AppInfo;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexProgramClass;
+import java.util.Collection;
+
+public class LambdaDeserializationMethodRemover {
+
+  /** Remove lambda deserialization methods. */
+  public static void run(AppView<AppInfo> appView) {
+    if (appView.options().desugarState.isOn()) {
+      run(appView, appView.appInfo().classes());
+    }
+  }
+
+  /** Remove lambda deserialization methods. */
+  public static void run(AppView<?> appView, Collection<DexProgramClass> classes) {
+    assert appView.options().desugarState.isOn() || classes.isEmpty();
+    DexMethod reference = appView.dexItemFactory().deserializeLambdaMethod;
+    for (DexProgramClass clazz : classes) {
+      clazz.removeMethod(reference);
+    }
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/lambda/LambdaDesugaringEventConsumer.java b/src/main/java/com/android/tools/r8/ir/desugar/lambda/LambdaDesugaringEventConsumer.java
new file mode 100644
index 0000000..2d5fe08
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/desugar/lambda/LambdaDesugaringEventConsumer.java
@@ -0,0 +1,13 @@
+// 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.lambda;
+
+import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.ir.desugar.LambdaClass;
+
+public interface LambdaDesugaringEventConsumer {
+
+  void acceptLambdaClass(LambdaClass lambdaClass, ProgramMethod context);
+}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/lambda/LambdaInstructionDesugaring.java b/src/main/java/com/android/tools/r8/ir/desugar/lambda/LambdaInstructionDesugaring.java
new file mode 100644
index 0000000..54477eb
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/desugar/lambda/LambdaInstructionDesugaring.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.lambda;
+
+import com.android.tools.r8.cf.code.CfFieldInstruction;
+import com.android.tools.r8.cf.code.CfInstruction;
+import com.android.tools.r8.cf.code.CfInvoke;
+import com.android.tools.r8.cf.code.CfInvokeDynamic;
+import com.android.tools.r8.cf.code.CfLoad;
+import com.android.tools.r8.cf.code.CfNew;
+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.AppView;
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.DexTypeList;
+import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.ir.code.ValueType;
+import com.android.tools.r8.ir.desugar.CfInstructionDesugaring;
+import com.android.tools.r8.ir.desugar.CfInstructionDesugaringEventConsumer;
+import com.android.tools.r8.ir.desugar.FreshLocalProvider;
+import com.android.tools.r8.ir.desugar.LambdaClass;
+import com.android.tools.r8.ir.desugar.LambdaDescriptor;
+import com.android.tools.r8.synthesis.SyntheticNaming;
+import com.android.tools.r8.utils.Box;
+import com.google.common.collect.ImmutableList;
+import java.util.ArrayDeque;
+import java.util.Collection;
+import java.util.Deque;
+import org.objectweb.asm.Opcodes;
+
+public class LambdaInstructionDesugaring implements CfInstructionDesugaring {
+
+  private final AppView<?> appView;
+
+  public LambdaInstructionDesugaring(AppView<?> appView) {
+    this.appView = appView;
+  }
+
+  @Override
+  public Collection<CfInstruction> desugarInstruction(
+      CfInstruction instruction,
+      FreshLocalProvider freshLocalProvider,
+      CfInstructionDesugaringEventConsumer eventConsumer,
+      ProgramMethod context,
+      MethodProcessingContext methodProcessingContext) {
+    if (instruction.isInvokeDynamic()) {
+      return desugarInvokeDynamicInstruction(
+          instruction.asInvokeDynamic(),
+          freshLocalProvider,
+          eventConsumer,
+          context,
+          methodProcessingContext);
+    }
+    return null;
+  }
+
+  private Collection<CfInstruction> desugarInvokeDynamicInstruction(
+      CfInvokeDynamic invoke,
+      FreshLocalProvider freshLocalProvider,
+      LambdaDesugaringEventConsumer eventConsumer,
+      ProgramMethod context,
+      MethodProcessingContext methodProcessingContext) {
+    LambdaClass lambdaClass = createLambdaClass(invoke, context, methodProcessingContext);
+    if (lambdaClass == null) {
+      return null;
+    }
+
+    eventConsumer.acceptLambdaClass(lambdaClass, context);
+
+    if (lambdaClass.isStateless()) {
+      return ImmutableList.of(
+          new CfFieldInstruction(
+              Opcodes.GETSTATIC, lambdaClass.lambdaField, lambdaClass.lambdaField));
+    }
+
+    DexTypeList captureTypes = lambdaClass.descriptor.captures;
+    Deque<CfInstruction> replacement = new ArrayDeque<>(3 + captureTypes.size() * 2);
+    replacement.add(new CfNew(lambdaClass.getType()));
+    replacement.add(new CfStackInstruction(Opcode.Dup));
+    captureTypes.forEach(
+        captureType -> {
+          ValueType valueType = ValueType.fromDexType(captureType);
+          int freshLocal = freshLocalProvider.getFreshLocal(valueType.requiredRegisters());
+          replacement.addFirst(new CfStore(valueType, freshLocal));
+          replacement.addLast(new CfLoad(valueType, freshLocal));
+        });
+    replacement.add(new CfInvoke(Opcodes.INVOKESPECIAL, lambdaClass.constructor, false));
+    return replacement;
+  }
+
+  // Creates a lambda class corresponding to the lambda descriptor and context.
+  private LambdaClass createLambdaClass(
+      CfInvokeDynamic invoke,
+      ProgramMethod context,
+      MethodProcessingContext methodProcessingContext) {
+    LambdaDescriptor descriptor =
+        LambdaDescriptor.tryInfer(invoke.getCallSite(), appView.appInfoForDesugaring(), context);
+    if (descriptor == null) {
+      return null;
+    }
+
+    Box<LambdaClass> box = new Box<>();
+    DexProgramClass clazz =
+        appView
+            .getSyntheticItems()
+            .createClass(
+                SyntheticNaming.SyntheticKind.LAMBDA,
+                methodProcessingContext.createUniqueContext(),
+                appView.dexItemFactory(),
+                builder -> box.set(new LambdaClass(builder, appView, this, context, descriptor)));
+    // Immediately set the actual program class on the lambda.
+    LambdaClass lambdaClass = box.get();
+    lambdaClass.setClass(clazz);
+    return lambdaClass;
+  }
+
+  @Override
+  public boolean needsDesugaring(CfInstruction instruction, ProgramMethod context) {
+    return instruction.isInvokeDynamic()
+        && LambdaDescriptor.tryInfer(
+                instruction.asInvokeDynamic().getCallSite(),
+                appView.appInfoForDesugaring(),
+                context)
+            != null;
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/nest/D8NestBasedAccessDesugaring.java b/src/main/java/com/android/tools/r8/ir/desugar/nest/D8NestBasedAccessDesugaring.java
index 9fcf485..2518219 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/nest/D8NestBasedAccessDesugaring.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/nest/D8NestBasedAccessDesugaring.java
@@ -15,6 +15,8 @@
 import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.ProgramField;
+import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.graph.UseRegistry;
 import com.android.tools.r8.ir.conversion.MethodProcessor;
 import com.android.tools.r8.utils.ThreadUtils;
@@ -30,7 +32,7 @@
  */
 public class D8NestBasedAccessDesugaring extends NestBasedAccessDesugaring {
 
-  public D8NestBasedAccessDesugaring(AppView<?> appView) {
+  D8NestBasedAccessDesugaring(AppView<?> appView) {
     super(appView);
   }
 
@@ -64,30 +66,47 @@
           Iterables.addAll(classpathClassesInNests, nest.getClasspathMembers());
         });
 
-    NestBridgeConsumer bridgeConsumer = NestBridgeConsumer.createForD8(methodProcessor);
+    NestBasedAccessDesugaringEventConsumer eventConsumer =
+        new NestBasedAccessDesugaringEventConsumer() {
+
+          @Override
+          public void acceptNestFieldGetBridge(ProgramField target, ProgramMethod bridge) {
+            methodProcessor.scheduleDesugaredMethodForProcessing(bridge);
+          }
+
+          @Override
+          public void acceptNestFieldPutBridge(ProgramField target, ProgramMethod bridge) {
+            methodProcessor.scheduleDesugaredMethodForProcessing(bridge);
+          }
+
+          @Override
+          public void acceptNestMethodBridge(ProgramMethod target, ProgramMethod bridge) {
+            methodProcessor.scheduleDesugaredMethodForProcessing(bridge);
+          }
+        };
     ThreadUtils.processItems(
         classpathClassesInNests,
-        clazz -> synthesizeBridgesForNestBasedAccessesOnClasspath(clazz, bridgeConsumer),
+        clazz -> synthesizeBridgesForNestBasedAccessesOnClasspath(clazz, eventConsumer),
         executorService);
   }
 
   public void synthesizeBridgesForNestBasedAccessesOnClasspath(
-      DexClasspathClass clazz, NestBridgeConsumer bridgeConsumer) {
+      DexClasspathClass clazz, NestBasedAccessDesugaringEventConsumer eventConsumer) {
     clazz.forEachClasspathMethod(
         method ->
             method.registerCodeReferencesForDesugaring(
-                new NestBasedAccessDesugaringUseRegistry(method, bridgeConsumer)));
+                new NestBasedAccessDesugaringUseRegistry(method, eventConsumer)));
   }
 
   private class NestBasedAccessDesugaringUseRegistry extends UseRegistry {
 
-    private final NestBridgeConsumer bridgeConsumer;
+    private final NestBasedAccessDesugaringEventConsumer eventConsumer;
     private final ClasspathMethod context;
 
     NestBasedAccessDesugaringUseRegistry(
-        ClasspathMethod context, NestBridgeConsumer bridgeConsumer) {
+        ClasspathMethod context, NestBasedAccessDesugaringEventConsumer eventConsumer) {
       super(appView.dexItemFactory());
-      this.bridgeConsumer = bridgeConsumer;
+      this.eventConsumer = eventConsumer;
       this.context = context;
     }
 
@@ -95,7 +114,7 @@
       DexClassAndField field =
           reference.lookupMemberOnClass(appView.definitionForHolder(reference));
       if (field != null && needsDesugaring(field, context)) {
-        ensureFieldAccessBridge(field, isGet, bridgeConsumer);
+        ensureFieldAccessBridge(field, isGet, eventConsumer);
       }
     }
 
@@ -106,7 +125,7 @@
       DexClassAndMethod method =
           reference.lookupMemberOnClass(appView.definitionForHolder(reference));
       if (method != null && needsDesugaring(method, context)) {
-        ensureMethodBridge(method, bridgeConsumer);
+        ensureMethodBridge(method, eventConsumer);
       }
     }
 
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
deleted file mode 100644
index 953801f..0000000
--- a/src/main/java/com/android/tools/r8/ir/desugar/nest/D8NestBridgeConsumer.java
+++ /dev/null
@@ -1,33 +0,0 @@
-// 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.nest;
-
-import com.android.tools.r8.graph.ProgramField;
-import com.android.tools.r8.graph.ProgramMethod;
-import com.android.tools.r8.ir.conversion.MethodProcessor;
-
-public class D8NestBridgeConsumer extends NestBridgeConsumer {
-
-  private final MethodProcessor methodProcessor;
-
-  public D8NestBridgeConsumer(MethodProcessor methodProcessor) {
-    this.methodProcessor = methodProcessor;
-  }
-
-  @Override
-  public void acceptFieldGetBridge(ProgramField target, ProgramMethod bridge) {
-    methodProcessor.scheduleDesugaredMethodForProcessing(bridge);
-  }
-
-  @Override
-  public void acceptFieldPutBridge(ProgramField target, ProgramMethod bridge) {
-    methodProcessor.scheduleDesugaredMethodForProcessing(bridge);
-  }
-
-  @Override
-  public void acceptMethodBridge(ProgramMethod target, ProgramMethod 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 86aba1e..af0a140 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
@@ -10,9 +10,9 @@
 import com.android.tools.r8.cf.code.CfFieldInstruction;
 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.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.DexClass;
 import com.android.tools.r8.graph.DexClassAndField;
@@ -31,13 +31,13 @@
 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.desugar.FreshLocalProvider;
 import com.android.tools.r8.synthesis.SyntheticNaming.SyntheticKind;
 import com.android.tools.r8.utils.BooleanUtils;
-import com.android.tools.r8.utils.ListUtils;
-import com.android.tools.r8.utils.StringDiagnostic;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.Iterables;
 import com.google.common.collect.Sets;
+import java.util.Collection;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
@@ -65,11 +65,20 @@
   private final DexItemFactory dexItemFactory;
   private final Map<DexType, DexType> syntheticNestConstructorTypes = new ConcurrentHashMap<>();
 
-  public NestBasedAccessDesugaring(AppView<?> appView) {
+  NestBasedAccessDesugaring(AppView<?> appView) {
     this.appView = appView;
     this.dexItemFactory = appView.dexItemFactory();
   }
 
+  public static NestBasedAccessDesugaring create(AppView<?> appView) {
+    if (appView.options().shouldDesugarNests()) {
+      return appView.enableWholeProgramOptimizations()
+          ? new NestBasedAccessDesugaring(appView)
+          : new D8NestBasedAccessDesugaring(appView);
+    }
+    return null;
+  }
+
   void forEachNest(Consumer<Nest> consumer) {
     forEachNest(consumer, emptyConsumer());
   }
@@ -133,62 +142,26 @@
         && member.getHolder().getNestHost() == context.getHolder().getNestHost();
   }
 
-  public boolean desugar(ProgramMethod method) {
-    return desugar(method, null);
-  }
-
-  public boolean desugar(ProgramMethod method, NestBridgeConsumer bridgeConsumer) {
-    if (!method.getHolder().isInANest()) {
-      return false;
-    }
-
-    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, method, bridgeConsumer),
-            null);
-    if (desugaredInstructions != null) {
-      cfCode.setInstructions(desugaredInstructions);
-      return true;
-    }
-    return false;
-  }
-
   @Override
-  public List<CfInstruction> desugarInstruction(
+  public Collection<CfInstruction> desugarInstruction(
       CfInstruction instruction,
-      CfInstructionDesugaringEventConsumer consumer,
-      ProgramMethod context) {
-    return desugarInstruction(instruction, context, null);
-  }
-
-  public List<CfInstruction> desugarInstruction(
-      CfInstruction instruction, ProgramMethod context, NestBridgeConsumer bridgeConsumer) {
+      FreshLocalProvider freshLocalProvider,
+      CfInstructionDesugaringEventConsumer eventConsumer,
+      ProgramMethod context,
+      MethodProcessingContext methodProcessingContext) {
     if (instruction.isFieldInstruction()) {
-      return desugarFieldInstruction(instruction.asFieldInstruction(), context, bridgeConsumer);
+      return desugarFieldInstruction(instruction.asFieldInstruction(), context, eventConsumer);
     }
     if (instruction.isInvoke()) {
-      return desugarInvokeInstruction(instruction.asInvoke(), context, bridgeConsumer);
+      return desugarInvokeInstruction(instruction.asInvoke(), context, eventConsumer);
     }
     return null;
   }
 
   private List<CfInstruction> desugarFieldInstruction(
-      CfFieldInstruction instruction, ProgramMethod context, NestBridgeConsumer bridgeConsumer) {
+      CfFieldInstruction instruction,
+      ProgramMethod context,
+      NestBasedAccessDesugaringEventConsumer eventConsumer) {
     // Since we only need to desugar accesses to private fields, and all accesses to private
     // fields must be accessing the private field directly on its holder, we can lookup the
     // field on the holder instead of resolving the field.
@@ -198,13 +171,15 @@
       return null;
     }
 
-    DexMethod bridge = ensureFieldAccessBridge(field, instruction.isFieldGet(), bridgeConsumer);
+    DexMethod bridge = ensureFieldAccessBridge(field, instruction.isFieldGet(), eventConsumer);
     return ImmutableList.of(
         new CfInvoke(Opcodes.INVOKESTATIC, bridge, field.getHolder().isInterface()));
   }
 
   private List<CfInstruction> desugarInvokeInstruction(
-      CfInvoke invoke, ProgramMethod context, NestBridgeConsumer bridgeConsumer) {
+      CfInvoke invoke,
+      ProgramMethod context,
+      NestBasedAccessDesugaringEventConsumer eventConsumer) {
     DexMethod invokedMethod = invoke.getMethod();
     if (!invokedMethod.getHolderType().isClassType()) {
       return null;
@@ -219,7 +194,7 @@
       return null;
     }
 
-    DexMethod bridge = ensureMethodBridge(target, bridgeConsumer);
+    DexMethod bridge = ensureMethodBridge(target, eventConsumer);
     if (target.getDefinition().isInstanceInitializer()) {
       assert !invoke.isInterface();
       return ImmutableList.of(
@@ -236,9 +211,9 @@
   }
 
   DexMethod ensureFieldAccessBridge(
-      DexClassAndField field, boolean isGet, NestBridgeConsumer bridgeConsumer) {
+      DexClassAndField field, boolean isGet, NestBasedAccessDesugaringEventConsumer eventConsumer) {
     if (field.isProgramField()) {
-      return ensureFieldAccessBridge(field.asProgramField(), isGet, bridgeConsumer);
+      return ensureFieldAccessBridge(field.asProgramField(), isGet, eventConsumer);
     }
     if (field.isClasspathField()) {
       return getFieldAccessBridgeReference(field, isGet);
@@ -248,15 +223,19 @@
   }
 
   private DexMethod ensureFieldAccessBridge(
-      ProgramField field, boolean isGet, NestBridgeConsumer bridgeConsumer) {
+      ProgramField field, boolean isGet, NestBasedAccessDesugaringEventConsumer eventConsumer) {
     DexMethod bridgeReference = getFieldAccessBridgeReference(field, isGet);
     synchronized (field.getHolder().getMethodCollection()) {
       ProgramMethod bridge = field.getHolder().lookupProgramMethod(bridgeReference);
       if (bridge == null) {
         bridge = DexEncodedMethod.createFieldAccessorBridge(field, isGet, bridgeReference);
         bridge.getHolder().addDirectMethod(bridge.getDefinition());
-        if (bridgeConsumer != null) {
-          bridgeConsumer.acceptFieldBridge(field, bridge, isGet);
+        if (eventConsumer != null) {
+          if (isGet) {
+            eventConsumer.acceptNestFieldGetBridge(field, bridge);
+          } else {
+            eventConsumer.acceptNestFieldPutBridge(field, bridge);
+          }
         }
       }
       return bridge.getReference();
@@ -293,9 +272,10 @@
     return dexItemFactory.createString(prefix + field.getName().toString());
   }
 
-  DexMethod ensureMethodBridge(DexClassAndMethod method, NestBridgeConsumer bridgeConsumer) {
+  DexMethod ensureMethodBridge(
+      DexClassAndMethod method, NestBasedAccessDesugaringEventConsumer eventConsumer) {
     if (method.isProgramMethod()) {
-      return ensureMethodBridge(method.asProgramMethod(), bridgeConsumer);
+      return ensureMethodBridge(method.asProgramMethod(), eventConsumer);
     }
     if (method.isClasspathMethod()) {
       return getMethodBridgeReference(method);
@@ -304,7 +284,8 @@
     throw reportIncompleteNest(method.asLibraryMethod());
   }
 
-  private DexMethod ensureMethodBridge(ProgramMethod method, NestBridgeConsumer bridgeConsumer) {
+  private DexMethod ensureMethodBridge(
+      ProgramMethod method, NestBasedAccessDesugaringEventConsumer eventConsumer) {
     DexMethod bridgeReference = getMethodBridgeReference(method);
     synchronized (method.getHolder().getMethodCollection()) {
       ProgramMethod bridge = method.getHolder().lookupProgramMethod(bridgeReference);
@@ -317,8 +298,8 @@
                 : definition.toStaticForwardingBridge(
                     method.getHolder(), bridgeReference, dexItemFactory);
         bridge.getHolder().addDirectMethod(bridge.getDefinition());
-        if (bridgeConsumer != null) {
-          bridgeConsumer.acceptMethodBridge(method, bridge);
+        if (eventConsumer != null) {
+          eventConsumer.acceptNestMethodBridge(method, bridge);
         }
       }
     }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/nest/NestBasedAccessDesugaringEventConsumer.java b/src/main/java/com/android/tools/r8/ir/desugar/nest/NestBasedAccessDesugaringEventConsumer.java
new file mode 100644
index 0000000..b5afce1
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/desugar/nest/NestBasedAccessDesugaringEventConsumer.java
@@ -0,0 +1,17 @@
+// 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.nest;
+
+import com.android.tools.r8.graph.ProgramField;
+import com.android.tools.r8.graph.ProgramMethod;
+
+public interface NestBasedAccessDesugaringEventConsumer {
+
+  void acceptNestFieldGetBridge(ProgramField target, ProgramMethod bridge);
+
+  void acceptNestFieldPutBridge(ProgramField target, ProgramMethod bridge);
+
+  void acceptNestMethodBridge(ProgramMethod target, ProgramMethod bridge);
+}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/nest/NestBridgeConsumer.java b/src/main/java/com/android/tools/r8/ir/desugar/nest/NestBridgeConsumer.java
deleted file mode 100644
index e6a8a03..0000000
--- a/src/main/java/com/android/tools/r8/ir/desugar/nest/NestBridgeConsumer.java
+++ /dev/null
@@ -1,30 +0,0 @@
-// 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.nest;
-
-import com.android.tools.r8.graph.ProgramField;
-import com.android.tools.r8.graph.ProgramMethod;
-import com.android.tools.r8.ir.conversion.MethodProcessor;
-
-public abstract class NestBridgeConsumer {
-
-  public static D8NestBridgeConsumer createForD8(MethodProcessor methodProcessor) {
-    return new D8NestBridgeConsumer(methodProcessor);
-  }
-
-  public abstract void acceptFieldGetBridge(ProgramField target, ProgramMethod bridge);
-
-  public abstract void acceptFieldPutBridge(ProgramField target, ProgramMethod bridge);
-
-  public abstract void acceptMethodBridge(ProgramMethod target, ProgramMethod bridge);
-
-  public final void acceptFieldBridge(ProgramField target, ProgramMethod bridge, boolean isGet) {
-    if (isGet) {
-      acceptFieldGetBridge(target, bridge);
-    } else {
-      acceptFieldPutBridge(target, bridge);
-    }
-  }
-}
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 0713d06..d01470e 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
@@ -39,7 +39,6 @@
 import com.android.tools.r8.ir.code.ArrayPut;
 import com.android.tools.r8.ir.code.Assume;
 import com.android.tools.r8.ir.code.BasicBlock;
-import com.android.tools.r8.ir.code.BasicBlock.ThrowingInfo;
 import com.android.tools.r8.ir.code.Binop;
 import com.android.tools.r8.ir.code.CatchHandlers;
 import com.android.tools.r8.ir.code.CheckCast;
@@ -3634,9 +3633,7 @@
   private Value addConstString(IRCode code, InstructionListIterator iterator, String s) {
     TypeElement typeLattice = TypeElement.stringClassType(appView, definitelyNotNull());
     Value value = code.createValue(typeLattice);
-    ThrowingInfo throwingInfo =
-        options.isGeneratingClassFiles() ? ThrowingInfo.NO_THROW : ThrowingInfo.CAN_THROW;
-    iterator.add(new ConstString(value, dexItemFactory.createString(s), throwingInfo));
+    iterator.add(new ConstString(value, dexItemFactory.createString(s)));
     return value;
   }
 
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 61b0f30..687a9bf 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
@@ -22,6 +22,12 @@
 
 public class UtilityMethodsForCodeOptimizations {
 
+  public interface MethodSynthesizerConsumer {
+
+    UtilityMethodForCodeOptimizations synthesizeMethod(
+        AppView<?> appView, MethodProcessingContext methodProcessingContext);
+  }
+
   public static UtilityMethodForCodeOptimizations synthesizeToStringIfNotNullMethod(
       AppView<?> appView, MethodProcessingContext methodProcessingContext) {
     InternalOptions options = appView.options();
@@ -77,6 +83,32 @@
             options, method);
   }
 
+  public static UtilityMethodForCodeOptimizations synthesizeThrowIllegalAccessErrorMethod(
+      AppView<?> appView, MethodProcessingContext methodProcessingContext) {
+    InternalOptions options = appView.options();
+    DexItemFactory dexItemFactory = appView.dexItemFactory();
+    DexProto proto = dexItemFactory.createProto(dexItemFactory.illegalAccessErrorType);
+    SyntheticItems syntheticItems = appView.getSyntheticItems();
+    ProgramMethod syntheticMethod =
+        syntheticItems.createMethod(
+            SyntheticNaming.SyntheticKind.THROW_IAE,
+            methodProcessingContext.createUniqueContext(),
+            dexItemFactory,
+            builder ->
+                builder
+                    .setAccessFlags(MethodAccessFlags.createPublicStaticSynthetic())
+                    .setClassFileVersion(CfVersion.V1_8)
+                    .setCode(method -> getThrowIllegalAccessErrorCodeTemplate(method, options))
+                    .setProto(proto));
+    return new UtilityMethodForCodeOptimizations(syntheticMethod);
+  }
+
+  private static CfCode getThrowIllegalAccessErrorCodeTemplate(
+      DexMethod method, InternalOptions options) {
+    return CfUtilityMethodsForCodeOptimizations
+        .CfUtilityMethodsForCodeOptimizationsTemplates_throwIllegalAccessError(options, method);
+  }
+
   public static UtilityMethodForCodeOptimizations synthesizeThrowIncompatibleClassChangeErrorMethod(
       AppView<?> appView, MethodProcessingContext methodProcessingContext) {
     InternalOptions options = appView.options();
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumValueOptimizer.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumValueOptimizer.java
index 88eedce..7ad0bdb 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumValueOptimizer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumValueOptimizer.java
@@ -22,7 +22,6 @@
 import com.android.tools.r8.ir.analysis.value.SingleStringValue;
 import com.android.tools.r8.ir.code.ArrayGet;
 import com.android.tools.r8.ir.code.BasicBlock;
-import com.android.tools.r8.ir.code.BasicBlock.ThrowingInfo;
 import com.android.tools.r8.ir.code.ConstNumber;
 import com.android.tools.r8.ir.code.ConstString;
 import com.android.tools.r8.ir.code.IRCode;
@@ -120,11 +119,7 @@
         if (isNameInvoke) {
           Value newValue =
               code.createValue(TypeElement.stringClassType(appView, definitelyNotNull()));
-          iterator.replaceCurrentInstruction(
-              new ConstString(
-                  newValue,
-                  nameValue.getDexString(),
-                  ThrowingInfo.defaultForConstString(appView.options())));
+          iterator.replaceCurrentInstruction(new ConstString(newValue, nameValue.getDexString()));
           newValue.addAffectedValuesTo(affectedValues);
           continue;
         }
@@ -155,11 +150,7 @@
 
         Value newValue =
             code.createValue(TypeElement.stringClassType(appView, definitelyNotNull()));
-        iterator.replaceCurrentInstruction(
-            new ConstString(
-                newValue,
-                nameValue.getDexString(),
-                ThrowingInfo.defaultForConstString(appView.options())));
+        iterator.replaceCurrentInstruction(new ConstString(newValue, nameValue.getDexString()));
         newValue.addAffectedValuesTo(affectedValues);
       }
     }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/string/StringBuilderOptimizer.java b/src/main/java/com/android/tools/r8/ir/optimize/string/StringBuilderOptimizer.java
index 3864fb8..5fdaf51 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/string/StringBuilderOptimizer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/string/StringBuilderOptimizer.java
@@ -593,8 +593,7 @@
                 code.createValue(
                     TypeElement.stringClassType(appView, definitelyNotNull()),
                     invoke.getLocalInfo());
-            it.replaceCurrentInstruction(
-                new ConstString(dummy, factory.createString(DUMMY), throwingInfo));
+            it.replaceCurrentInstruction(new ConstString(dummy, factory.createString(DUMMY)));
           } else {
             it.removeOrReplaceByDebugLocalRead();
           }
@@ -614,8 +613,7 @@
             code.createValue(
                 TypeElement.stringClassType(appView, definitelyNotNull()), invoke.getLocalInfo());
         affectedValues.addAll(outValue.affectedValues());
-        it.replaceCurrentInstruction(
-            new ConstString(stringValue, factory.createString(element), throwingInfo));
+        it.replaceCurrentInstruction(new ConstString(stringValue, factory.createString(element)));
         simplifiedBuilders.add(builder);
         numberOfBuildersSimplified++;
       }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/string/StringOptimizer.java b/src/main/java/com/android/tools/r8/ir/optimize/string/StringOptimizer.java
index 3c6c003..48f2199 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/string/StringOptimizer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/string/StringOptimizer.java
@@ -22,7 +22,6 @@
 import com.android.tools.r8.ir.analysis.escape.EscapeAnalysisConfiguration;
 import com.android.tools.r8.ir.analysis.type.TypeAnalysis;
 import com.android.tools.r8.ir.analysis.type.TypeElement;
-import com.android.tools.r8.ir.code.BasicBlock.ThrowingInfo;
 import com.android.tools.r8.ir.code.ConstClass;
 import com.android.tools.r8.ir.code.ConstNumber;
 import com.android.tools.r8.ir.code.ConstString;
@@ -49,7 +48,6 @@
 
   private final AppView<?> appView;
   private final DexItemFactory factory;
-  private final ThrowingInfo throwingInfo;
 
   private int numberOfSimplifiedOperations = 0;
   private final Object2IntMap<ClassNameMapping> numberOfComputedNames;
@@ -61,7 +59,6 @@
   public StringOptimizer(AppView<?> appView) {
     this.appView = appView;
     this.factory = appView.dexItemFactory();
-    this.throwingInfo = ThrowingInfo.defaultForConstString(appView.options());
     if (Log.ENABLED && Log.isLoggingEnabledFor(StringOptimizer.class)) {
       numberOfComputedNames = new Object2IntArrayMap<>();
       numberOfDeferredComputationOfNames = new Object2IntArrayMap<>();
@@ -186,8 +183,7 @@
             code.createValue(
                 TypeElement.stringClassType(appView, definitelyNotNull()), invoke.getLocalInfo());
         affectedValues.addAll(invoke.outValue().affectedValues());
-        it.replaceCurrentInstruction(
-            new ConstString(stringValue, factory.createString(sub), throwingInfo));
+        it.replaceCurrentInstruction(new ConstString(stringValue, factory.createString(sub)));
         numberOfSimplifiedOperations++;
         continue;
       }
@@ -203,7 +199,7 @@
             code.createValue(
                 TypeElement.stringClassType(appView, definitelyNotNull()), invoke.getLocalInfo());
         affectedValues.addAll(invoke.outValue().affectedValues());
-        it.replaceCurrentInstruction(new ConstString(newOutValue, resultString, throwingInfo));
+        it.replaceCurrentInstruction(new ConstString(newOutValue, resultString));
         numberOfSimplifiedOperations++;
         continue;
       }
@@ -379,10 +375,7 @@
         if (mayBeRenamed) {
           deferred =
               new DexItemBasedConstString(
-                  invoke.outValue(),
-                  baseType,
-                  ClassNameComputationInfo.create(NAME, arrayDepth),
-                  throwingInfo);
+                  invoke.outValue(), baseType, ClassNameComputationInfo.create(NAME, arrayDepth));
           logDeferredNameComputation(NAME);
         } else {
           name = NAME.map(descriptor, holder, factory, arrayDepth);
@@ -408,8 +401,7 @@
                 new DexItemBasedConstString(
                     invoke.outValue(),
                     baseType,
-                    ClassNameComputationInfo.create(CANONICAL_NAME, arrayDepth),
-                    throwingInfo);
+                    ClassNameComputationInfo.create(CANONICAL_NAME, arrayDepth));
             logDeferredNameComputation(CANONICAL_NAME);
           } else {
             name = CANONICAL_NAME.map(descriptor, holder, factory, arrayDepth);
@@ -431,8 +423,7 @@
                 new DexItemBasedConstString(
                     invoke.outValue(),
                     baseType,
-                    ClassNameComputationInfo.create(SIMPLE_NAME, arrayDepth),
-                    throwingInfo);
+                    ClassNameComputationInfo.create(SIMPLE_NAME, arrayDepth));
             logDeferredNameComputation(SIMPLE_NAME);
           } else {
             name = SIMPLE_NAME.map(descriptor, holder, factory, arrayDepth);
@@ -445,7 +436,7 @@
         Value stringValue =
             code.createValue(
                 TypeElement.stringClassType(appView, definitelyNotNull()), invoke.getLocalInfo());
-        ConstString constString = new ConstString(stringValue, name, throwingInfo);
+        ConstString constString = new ConstString(stringValue, name);
         it.replaceCurrentInstruction(constString);
         logHistogramOfNames(name);
       } else if (deferred != null) {
@@ -531,8 +522,7 @@
           Value nullStringValue =
               code.createValue(
                   TypeElement.stringClassType(appView, definitelyNotNull()), invoke.getLocalInfo());
-          ConstString nullString =
-              new ConstString(nullStringValue, factory.createString("null"), throwingInfo);
+          ConstString nullString = new ConstString(nullStringValue, factory.createString("null"));
           it.replaceCurrentInstruction(nullString);
           numberOfSimplifiedConversions++;
         } else if (inType.nullability().isDefinitelyNotNull()
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/templates/CfUtilityMethodsForCodeOptimizations.java b/src/main/java/com/android/tools/r8/ir/optimize/templates/CfUtilityMethodsForCodeOptimizations.java
index 831a0b3..33f1c04 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/templates/CfUtilityMethodsForCodeOptimizations.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/templates/CfUtilityMethodsForCodeOptimizations.java
@@ -33,6 +33,7 @@
 
   public static void registerSynthesizedCodeReferences(DexItemFactory factory) {
     factory.createSynthesizedType("Ljava/lang/ClassCastException;");
+    factory.createSynthesizedType("Ljava/lang/IllegalAccessError;");
     factory.createSynthesizedType("Ljava/lang/IncompatibleClassChangeError;");
     factory.createSynthesizedType("Ljava/lang/NoSuchMethodError;");
   }
@@ -75,6 +76,29 @@
         ImmutableList.of());
   }
 
+  public static CfCode CfUtilityMethodsForCodeOptimizationsTemplates_throwIllegalAccessError(
+      InternalOptions options, DexMethod method) {
+    CfLabel label0 = new CfLabel();
+    return new CfCode(
+        method.holder,
+        2,
+        0,
+        ImmutableList.of(
+            label0,
+            new CfNew(options.itemFactory.createType("Ljava/lang/IllegalAccessError;")),
+            new CfStackInstruction(CfStackInstruction.Opcode.Dup),
+            new CfInvoke(
+                183,
+                options.itemFactory.createMethod(
+                    options.itemFactory.createType("Ljava/lang/IllegalAccessError;"),
+                    options.itemFactory.createProto(options.itemFactory.voidType),
+                    options.itemFactory.createString("<init>")),
+                false),
+            new CfThrow()),
+        ImmutableList.of(),
+        ImmutableList.of());
+  }
+
   public static CfCode
       CfUtilityMethodsForCodeOptimizationsTemplates_throwIncompatibleClassChangeError(
           InternalOptions options, DexMethod method) {
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 1c6adbb..3a25d00 100644
--- a/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java
+++ b/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java
@@ -265,9 +265,7 @@
     if (!method.hasClassFileVersion()) {
       // In this case bridges have been introduced for the Cf back-end,
       // which do not have class file version.
-      assert options.testing.enableForceNestBasedAccessDesugaringForTest
-              || options.isDesugaredLibraryCompilation()
-              || options.cfToCfDesugar
+      assert options.isDesugaredLibraryCompilation() || options.cfToCfDesugar
           : "Expected class file version for " + method.method.toSourceString();
       assert MIN_VERSION_FOR_COMPILER_GENERATED_CODE.isLessThan(
           options.classFileVersionAfterDesugaring(InternalOptions.SUPPORTED_CF_VERSION));
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataRewriter.java b/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataRewriter.java
index 44d6ede..fdb9472 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataRewriter.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataRewriter.java
@@ -30,8 +30,7 @@
 public class KotlinMetadataRewriter {
 
   // Due to a bug with nested classes and the lookup of RequirementVersion, we bump all metadata
-  // versions to 1.4 if compiled with kotlin 1.3 (1.1.16). For more information, see b/161885097 for
-  // more information.
+  // versions to 1.4 if compiled with kotlin 1.3 (1.1.16). For more information, see b/161885097.
   private static final int[] METADATA_VERSION_1_4 = new int[] {1, 4, 0};
 
   private static final class WriteMetadataFieldInfo {
diff --git a/src/main/java/com/android/tools/r8/naming/IdentifierNameStringMarker.java b/src/main/java/com/android/tools/r8/naming/IdentifierNameStringMarker.java
index 2168235..2d9a19d 100644
--- a/src/main/java/com/android/tools/r8/naming/IdentifierNameStringMarker.java
+++ b/src/main/java/com/android/tools/r8/naming/IdentifierNameStringMarker.java
@@ -20,7 +20,6 @@
 import com.android.tools.r8.graph.DexValue.DexValueString;
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.code.BasicBlock;
-import com.android.tools.r8.ir.code.BasicBlock.ThrowingInfo;
 import com.android.tools.r8.ir.code.ConstString;
 import com.android.tools.r8.ir.code.DexItemBasedConstString;
 import com.android.tools.r8.ir.code.FieldInstruction;
@@ -54,12 +53,10 @@
 
   private final AppView<AppInfoWithLiveness> appView;
   private final Object2BooleanMap<DexReference> identifierNameStrings;
-  private final ThrowingInfo throwingInfo;
 
   public IdentifierNameStringMarker(AppView<AppInfoWithLiveness> appView) {
     this.appView = appView;
     this.identifierNameStrings = appView.appInfo().identifierNameStrings;
-    this.throwingInfo = ThrowingInfo.defaultForConstString(appView.options());
   }
 
   public void decoupleIdentifierNameStringsInFields(
@@ -161,8 +158,7 @@
     // Prepare $decoupled just before $fieldPut
     Value newIn = code.createValue(in.getType(), in.getLocalInfo());
     DexItemBasedConstString decoupled =
-        new DexItemBasedConstString(
-            newIn, itemBasedString, ClassNameComputationInfo.none(), throwingInfo);
+        new DexItemBasedConstString(newIn, itemBasedString, ClassNameComputationInfo.none());
     decoupled.setPosition(fieldPut.getPosition());
     // If the current block has catch handler, split into two blocks.
     // Because const-string we're about to add is also a throwing instr, we need to split
@@ -227,10 +223,7 @@
       Value newIn = code.createValue(in.getType(), in.getLocalInfo());
       DexItemBasedConstString decoupled =
           new DexItemBasedConstString(
-              newIn,
-              identifierLookupResult.getReference(),
-              ClassNameComputationInfo.none(),
-              throwingInfo);
+              newIn, identifierLookupResult.getReference(), ClassNameComputationInfo.none());
       changes[identifierPosition] = newIn;
 
       if (in.numberOfAllUsers() == 1) {
@@ -295,8 +288,7 @@
         // Prepare $decoupled just before $invoke
         Value newIn = code.createValue(in.getType(), in.getLocalInfo());
         DexItemBasedConstString decoupled =
-            new DexItemBasedConstString(
-                newIn, itemBasedString, ClassNameComputationInfo.none(), throwingInfo);
+            new DexItemBasedConstString(newIn, itemBasedString, ClassNameComputationInfo.none());
         decoupled.setPosition(invoke.getPosition());
         changes[i] = newIn;
         // If the current block has catch handler, split into two blocks.
diff --git a/src/main/java/com/android/tools/r8/repackaging/RepackagingConstraintGraph.java b/src/main/java/com/android/tools/r8/repackaging/RepackagingConstraintGraph.java
index 191dc0a..ca181fd 100644
--- a/src/main/java/com/android/tools/r8/repackaging/RepackagingConstraintGraph.java
+++ b/src/main/java/com/android/tools/r8/repackaging/RepackagingConstraintGraph.java
@@ -72,7 +72,7 @@
   }
 
   private Node createNode(DexDefinition definition) {
-    Node node = new Node();
+    Node node = new Node(definition);
     nodes.put(definition, node);
     return node;
   }
@@ -172,7 +172,12 @@
   public Iterable<DexProgramClass> computeClassesToRepackage() {
     WorkList<Node> worklist = WorkList.newIdentityWorkList(pinnedNodes);
     while (worklist.hasNext()) {
-      worklist.addIfNotSeen(worklist.next().getNeighbors());
+      Node pinnedNode = worklist.next();
+      for (Node neighbor : pinnedNode.getNeighbors()) {
+        // Mark all the immediate neighbors as ineligible for repackaging and continue the tracing
+        // from the neighbors.
+        worklist.addIfNotSeen(neighbor);
+      }
     }
     Set<Node> pinnedNodes = worklist.getSeenSet();
     List<DexProgramClass> classesToRepackage = new ArrayList<>();
@@ -186,8 +191,13 @@
 
   static class Node {
 
+    private final DexDefinition definitionForDebugging;
     private final Set<Node> neighbors = Sets.newConcurrentHashSet();
 
+    Node(DexDefinition definitionForDebugging) {
+      this.definitionForDebugging = definitionForDebugging;
+    }
+
     public void addNeighbor(Node neighbor) {
       neighbors.add(neighbor);
       neighbor.neighbors.add(this);
@@ -196,5 +206,10 @@
     public Set<Node> getNeighbors() {
       return neighbors;
     }
+
+    @Override
+    public String toString() {
+      return "Node(" + definitionForDebugging.getReference().toSourceString() + ")";
+    }
   }
 }
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 e8fed84..2f7edb2 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -53,7 +53,6 @@
 import com.android.tools.r8.graph.FieldAccessInfoImpl;
 import com.android.tools.r8.graph.FieldResolutionResult;
 import com.android.tools.r8.graph.GenericSignatureEnqueuerAnalysis;
-import com.android.tools.r8.graph.GraphLens.NestedGraphLens;
 import com.android.tools.r8.graph.InnerClassAttribute;
 import com.android.tools.r8.graph.LookupLambdaTarget;
 import com.android.tools.r8.graph.LookupTarget;
@@ -91,10 +90,7 @@
 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.kotlin.KotlinMetadataEnqueuerExtension;
 import com.android.tools.r8.logging.Log;
@@ -126,7 +122,6 @@
 import com.android.tools.r8.utils.Visibility;
 import com.android.tools.r8.utils.WorkList;
 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.google.common.base.Equivalence.Wrapper;
 import com.google.common.collect.ImmutableSet;
@@ -308,7 +303,7 @@
    * are required so that invokes can find the method. If a method is only a target but not live,
    * its implementation may be removed and it may be marked abstract.
    */
-  private final SetWithReason<DexEncodedMethod> targetedMethods;
+  private final LiveMethodsSet targetedMethods;
 
   /** Set of methods that have invalid resolutions or lookups. */
   private final Set<DexMethod> failedMethodResolutionTargets;
@@ -370,7 +365,8 @@
    * A map from annotation classes to annotations that need to be processed should the classes ever
    * become live.
    */
-  private final Map<DexType, Set<DexAnnotation>> deferredAnnotations = new IdentityHashMap<>();
+  private final Map<DexType, Map<DexAnnotation, List<ProgramDefinition>>> deferredAnnotations =
+      new IdentityHashMap<>();
 
   /** Map of active if rules to speed up aapt2 generated keep rules. */
   private Map<Wrapper<ProguardIfRule>, Set<ProguardIfRule>> activeIfRules;
@@ -385,21 +381,7 @@
 
   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 TwrCloseResourceRewriter twrCloseResourceRewriter;
@@ -427,7 +409,7 @@
     this.mode = mode;
     this.options = options;
     this.useRegistryFactory = createUseRegistryFactory();
-    this.workList = EnqueuerWorklist.createWorklist();
+    this.workList = EnqueuerWorklist.createWorklist(this);
     this.proguardCompatibilityActionsBuilder =
         mode.isInitialTreeShaking() && options.forceProguardCompatibility
             ? ProguardCompatibilityActions.builder()
@@ -441,7 +423,7 @@
           shrinker -> registerAnalysis(shrinker.createEnqueuerAnalysis()));
     }
 
-    targetedMethods = new SetWithReason<>(graphReporter::registerMethod);
+    targetedMethods = new LiveMethodsSet(graphReporter::registerMethod);
     // This set is only populated in edge cases due to multiple default interface methods.
     // The set is generally expected to be empty and in the unlikely chance it is not, it will
     // likely contain two methods. Thus the default capacity of 2.
@@ -451,9 +433,8 @@
     liveFields = new LiveFieldsSet(graphReporter::registerField);
     desugaring =
         mode.isInitialTreeShaking()
-            ? new NonEmptyCfInstructionDesugaringCollection(appView)
+            ? CfInstructionDesugaringCollection.create(appView)
             : CfInstructionDesugaringCollection.empty();
-    lambdaRewriter = options.desugarState == DesugarState.ON ? new LambdaRewriter(appView) : null;
     backportRewriter =
         options.desugarState == DesugarState.ON ? new BackportedMethodRewriter(appView) : null;
     twrCloseResourceRewriter =
@@ -545,6 +526,14 @@
     deadProtoTypeCandidates.add(clazz);
   }
 
+  public boolean addLiveMethod(ProgramMethod method, KeepReason reason) {
+    return liveMethods.add(method, reason);
+  }
+
+  public boolean addTargetedMethod(ProgramMethod method, KeepReason reason) {
+    return targetedMethods.add(method, reason);
+  }
+
   private void recordCompilerSynthesizedTypeReference(DexType type) {
     DexClass clazz = appInfo().definitionFor(type);
     if (clazz == null) {
@@ -785,7 +774,7 @@
               graphReporter.reportCompatKeepDefaultInitializer(defaultInitializer));
         }
         if (clazz.isExternalizable(appView)) {
-          enqueueMarkMethodLiveAction(defaultInitializer, defaultInitializer, witness);
+          workList.enqueueMarkMethodLiveAction(defaultInitializer, defaultInitializer, witness);
         }
       }
     }
@@ -867,21 +856,10 @@
       clazz = superClass;
     }
     if (clazz.hasDefaultInitializer()) {
-      enqueueMarkMethodLiveAction(clazz.getProgramDefaultInitializer(), clazz, reason);
+      workList.enqueueMarkMethodLiveAction(clazz.getProgramDefaultInitializer(), clazz, reason);
     }
   }
 
-  // Utility to avoid adding to the worklist if already live.
-  private boolean enqueueMarkMethodLiveAction(
-      ProgramMethod method, ProgramDefinition context, KeepReason reason) {
-    if (liveMethods.add(method, reason)) {
-      assert !method.getDefinition().getOptimizationInfo().forceInline();
-      workList.enqueueMarkMethodLiveAction(method, context);
-      return true;
-    }
-    return false;
-  }
-
   private void compatEnqueueHolderIfDependentNonStaticMember(
       DexProgramClass holder, Set<ProguardKeepRuleBase> compatRules) {
     if (!forceProguardCompatibility || compatRules == null) {
@@ -980,16 +958,11 @@
       return;
     }
 
-    if (lambdaRewriter != null) {
-      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);
-      transitionMethodsForInstantiatedLambda(descriptor);
-      callSites.computeIfAbsent(callSite, ignore -> ProgramMethodSet.create()).add(context);
-    }
+    assert options.desugarState.isOff();
+
+    markLambdaAsInstantiated(descriptor, context);
+    transitionMethodsForInstantiatedLambda(descriptor);
+    callSites.computeIfAbsent(callSite, ignore -> ProgramMethodSet.create()).add(context);
 
     // For call sites representing a lambda, we link the targeted method
     // or field as if it were referenced from the current method.
@@ -1441,8 +1414,8 @@
 
     FieldResolutionResult resolutionResult = resolveField(fieldReference, currentMethod);
     if (resolutionResult.isFailedOrUnknownResolution()) {
-      // Must mark the field as targeted even if it does not exist.
-      markFieldAsTargeted(fieldReference, currentMethod);
+      // Must trace the types from the field reference even if it does not exist.
+      traceFieldReference(fieldReference, resolutionResult, currentMethod);
       noClassMerging.add(fieldReference.getHolderType());
       return;
     }
@@ -1473,7 +1446,7 @@
       markTypeAsLive(resolutionResult.getInitialResolutionHolder(), currentMethod);
     }
 
-    workList.enqueueMarkInstanceFieldAsReachableAction(
+    workList.enqueueMarkFieldAsReachableAction(
         field, currentMethod, KeepReason.fieldReferencedIn(currentMethod));
   }
 
@@ -1493,8 +1466,8 @@
 
     FieldResolutionResult resolutionResult = resolveField(fieldReference, currentMethod);
     if (resolutionResult.isFailedOrUnknownResolution()) {
-      // Must mark the field as targeted even if it does not exist.
-      markFieldAsTargeted(fieldReference, currentMethod);
+      // Must trace the types from the field reference even if it does not exist.
+      traceFieldReference(fieldReference, resolutionResult, currentMethod);
       noClassMerging.add(fieldReference.getHolderType());
       return;
     }
@@ -1526,7 +1499,7 @@
     }
 
     KeepReason reason = KeepReason.fieldReferencedIn(currentMethod);
-    workList.enqueueMarkInstanceFieldAsReachableAction(field, currentMethod, reason);
+    workList.enqueueMarkFieldAsReachableAction(field, currentMethod, reason);
   }
 
   void traceStaticFieldRead(DexField field, ProgramMethod currentMethod) {
@@ -1545,8 +1518,8 @@
 
     FieldResolutionResult resolutionResult = resolveField(fieldReference, currentMethod);
     if (resolutionResult.isFailedOrUnknownResolution()) {
-      // Must mark the field as targeted even if it does not exist.
-      markFieldAsTargeted(fieldReference, currentMethod);
+      // Must trace the types from the field reference even if it does not exist.
+      traceFieldReference(fieldReference, resolutionResult, currentMethod);
       noClassMerging.add(fieldReference.getHolderType());
 
       // Record field reference for generated extension registry shrinking.
@@ -1593,7 +1566,7 @@
       markTypeAsLive(resolutionResult.getInitialResolutionHolder(), currentMethod);
     }
 
-    markStaticFieldAsLive(field, currentMethod);
+    markFieldAsLive(field, currentMethod);
   }
 
   void traceStaticFieldWrite(DexField field, ProgramMethod currentMethod) {
@@ -1612,8 +1585,8 @@
 
     FieldResolutionResult resolutionResult = resolveField(fieldReference, currentMethod);
     if (resolutionResult.isFailedOrUnknownResolution()) {
-      // Must mark the field as targeted even if it does not exist.
-      markFieldAsTargeted(fieldReference, currentMethod);
+      // Must trace the types from the field reference even if it does not exist.
+      traceFieldReference(fieldReference, resolutionResult, currentMethod);
       noClassMerging.add(fieldReference.getHolderType());
       return;
     }
@@ -1658,7 +1631,7 @@
       markTypeAsLive(resolutionResult.getInitialResolutionHolder(), currentMethod);
     }
 
-    markStaticFieldAsLive(field, currentMethod);
+    markFieldAsLive(field, currentMethod);
   }
 
   private DexMethod getInvokeSuperTarget(DexMethod method, ProgramMethod currentMethod) {
@@ -1787,8 +1760,8 @@
       if (enclosingMethod != null) {
         recordMethodReference(enclosingMethod, clazz, missingClassConsumer);
       } else {
-        recordTypeReference(
-            enclosingMethodAttribute.getEnclosingClass(), clazz, missingClassConsumer);
+        DexType enclosingClass = enclosingMethodAttribute.getEnclosingClass();
+        recordTypeReference(enclosingClass, clazz, missingClassConsumer);
       }
     }
 
@@ -1838,14 +1811,19 @@
       enqueueFirstNonSerializableClassInitializer(clazz, reason);
     }
 
-    processAnnotations(clazz, clazz);
+    processAnnotations(clazz);
 
     // If this type has deferred annotations, we have to process those now, too.
     if (clazz.isAnnotation()) {
-      Set<DexAnnotation> annotations = deferredAnnotations.remove(clazz.type);
-      if (annotations != null && !annotations.isEmpty()) {
-        assert annotations.stream().allMatch(a -> a.annotation.type == clazz.type);
-        annotations.forEach(annotation -> processAnnotation(clazz, clazz, annotation));
+      Map<DexAnnotation, List<ProgramDefinition>> annotations =
+          deferredAnnotations.remove(clazz.getType());
+      if (annotations != null) {
+        assert annotations.keySet().stream()
+            .allMatch(a -> a.getAnnotationType() == clazz.getType());
+        annotations.forEach(
+            (annotation, annotatedItems) ->
+                annotatedItems.forEach(
+                    annotatedItem -> processAnnotation(annotatedItem, annotation)));
       }
     }
 
@@ -1938,35 +1916,33 @@
     enqueueKeepRuleInstantiatedType(holder, reasons, instanceInitializer.getDefinition());
   }
 
-  private void processAnnotations(DexProgramClass holder, ProgramDefinition annotatedItem) {
-    processAnnotations(holder, annotatedItem, annotatedItem.getDefinition().annotations());
+  private void processAnnotations(ProgramDefinition annotatedItem) {
+    processAnnotations(annotatedItem, annotatedItem.getDefinition().annotations());
   }
 
-  private void processAnnotations(
-      DexProgramClass holder, ProgramDefinition annotatedItem, DexAnnotationSet annotations) {
-    processAnnotations(holder, annotatedItem, annotations.annotations);
+  private void processAnnotations(ProgramDefinition annotatedItem, DexAnnotationSet annotations) {
+    processAnnotations(annotatedItem, annotations.annotations);
   }
 
-  private void processAnnotations(
-      DexProgramClass holder, ProgramDefinition annotatedItem, DexAnnotation[] annotations) {
+  private void processAnnotations(ProgramDefinition annotatedItem, DexAnnotation[] annotations) {
     for (DexAnnotation annotation : annotations) {
-      processAnnotation(holder, annotatedItem, annotation);
+      processAnnotation(annotatedItem, annotation);
     }
   }
 
-  private void processAnnotation(
-      DexProgramClass holder, ProgramDefinition annotatedItem, DexAnnotation annotation) {
-    assert annotatedItem == holder || annotatedItem.asProgramMember().getHolder() == holder;
-    assert !holder.isDexClass() || holder.asDexClass().isProgramClass();
+  private void processAnnotation(ProgramDefinition annotatedItem, DexAnnotation annotation) {
     DexType type = annotation.getAnnotationType();
-    recordTypeReference(type, annotatedItem);
-    DexClass clazz = appView.definitionFor(type);
+    DexClass clazz = definitionFor(type, annotatedItem);
     boolean annotationTypeIsLibraryClass = clazz == null || clazz.isNotProgramClass();
     boolean isLive = annotationTypeIsLibraryClass || liveTypes.contains(clazz.asProgramClass());
     if (!shouldKeepAnnotation(appView, annotatedItem.getDefinition(), annotation, isLive)) {
       // Remember this annotation for later.
       if (!annotationTypeIsLibraryClass) {
-        deferredAnnotations.computeIfAbsent(type, ignore -> new HashSet<>()).add(annotation);
+        Map<DexAnnotation, List<ProgramDefinition>> deferredAnnotationsForAnnotationType =
+            deferredAnnotations.computeIfAbsent(type, ignore -> new IdentityHashMap<>());
+        deferredAnnotationsForAnnotationType
+            .computeIfAbsent(annotation, ignore -> new ArrayList<>())
+            .add(annotatedItem);
       }
       return;
     }
@@ -2171,7 +2147,7 @@
     // Method). In a class, that would lead to a verification error.
     if (encodedMethod.isNonPrivateVirtualMethod()
         && virtualMethodsTargetedByInvokeDirect.add(encodedMethod.method)) {
-      enqueueMarkMethodLiveAction(method, context, reason);
+      workList.enqueueMarkMethodLiveAction(method, context, reason);
     }
   }
 
@@ -2277,30 +2253,6 @@
     missingClassesBuilder.legacyAddNewMissingClass(clazz);
   }
 
-  private void markMethodAsTargeted(ProgramMethod method, KeepReason reason) {
-    DexEncodedMethod definition = method.getDefinition();
-    DexProgramClass holder = method.getHolder();
-    if (!targetedMethods.add(definition, reason)) {
-      // Already targeted.
-      return;
-    }
-    markReferencedTypesAsLive(method);
-    processAnnotations(holder, method);
-    definition.parameterAnnotationsList.forEachAnnotation(
-        annotation -> processAnnotation(holder, method, annotation));
-
-    if (Log.ENABLED) {
-      Log.verbose(getClass(), "Method `%s` is targeted.", method);
-    }
-    if (forceProguardCompatibility) {
-      // Keep targeted default methods in compatibility mode. The tree pruner will otherwise make
-      // these methods abstract, whereas Proguard does not (seem to) touch their code.
-      if (!definition.isAbstract() && holder.isInterface()) {
-        markMethodAsLiveWithCompatRule(method);
-      }
-    }
-  }
-
   /**
    * Adds the class to the set of instantiated classes and marks its fields and methods live
    * depending on the currently seen invokes and field reads.
@@ -2611,7 +2563,7 @@
         // TODO(b/120959039): Should the reason this field is reachable come from the set?
         KeepReason reason = KeepReason.reachableFromLiveType(clazz.type);
         for (ProgramField field : reachableFields) {
-          markInstanceFieldAsLive(field, clazz, reason);
+          markFieldAsLive(field, clazz, reason);
         }
       }
       clazz = getProgramClassOrNull(clazz.superType, clazz);
@@ -2655,94 +2607,95 @@
     }
   }
 
-  private void markFieldAsTargeted(ProgramField field) {
-    markTypeAsLive(field.getHolder(), field);
-    markTypeAsLive(field.getType(), field);
+  private void markFieldAsLive(ProgramField field, ProgramMethod context) {
+    markFieldAsLive(field, context, KeepReason.fieldReferencedIn(context));
   }
 
-  private void markFieldAsTargeted(DexField field, ProgramMethod context) {
+  private void markFieldAsLive(ProgramField field, ProgramDefinition context, KeepReason reason) {
+    // This field might be an instance field reachable from a static context, e.g. a getStatic that
+    // resolves to an instance field. We have to keep the instance field nonetheless, as otherwise
+    // we might unmask a shadowed static field and hence change semantics.
+    if (!liveFields.add(field, reason)) {
+      // Already live.
+      return;
+    }
+
+    // Mark the field as targeted.
+    if (field.getAccessFlags().isStatic()) {
+      traceFieldDefinition(field);
+      markDirectAndIndirectClassInitializersAsLive(field.getHolder());
+    } else if (!reachableInstanceFields
+        .getOrDefault(field.getHolder(), ProgramFieldSet.empty())
+        .contains(field)) {
+      traceFieldDefinition(field);
+    }
+
+    // Add all dependent members to the workqueue.
+    enqueueRootItems(rootSet.getDependentItems(field.getDefinition()));
+
+    checkMemberForSoftPinning(field);
+
+    // Notify analyses.
+    analyses.forEach(analysis -> analysis.processNewlyLiveField(field, context));
+  }
+
+  // Package protected due to entry point from worklist.
+  void markFieldAsReachable(ProgramField field, ProgramDefinition context, KeepReason reason) {
+    // We might have a instance field access that is dispatched to a static field. In such case,
+    // we have to keep the static field, so that the dispatch fails at runtime in the same way that
+    // it did before. We have to keep the field even if the receiver has no live inhabitants, as
+    // field resolution happens before the receiver is inspected.
+    if (field.getDefinition().isStatic()
+        || objectAllocationInfoCollection.isInstantiatedDirectlyOrHasInstantiatedSubtype(
+            field.getHolder())) {
+      markFieldAsLive(field, context, reason);
+    }
+
+    if (liveFields.contains(field)
+        || !reachableInstanceFields
+            .computeIfAbsent(field.getHolder(), ignore -> ProgramFieldSet.create())
+            .add(field)) {
+      // Already reachable.
+      graphReporter.registerField(field.getDefinition(), reason);
+      return;
+    }
+
+    traceFieldDefinition(field);
+  }
+
+  private void traceFieldDefinition(ProgramField field) {
+    markTypeAsLive(field.getHolder(), field);
+    markTypeAsLive(field.getType(), field);
+    processAnnotations(field);
+  }
+
+  private void traceFieldReference(
+      DexField field, FieldResolutionResult resolutionResult, ProgramMethod context) {
+    assert resolutionResult.isFailedOrUnknownResolution();
     markTypeAsLive(field.getHolderType(), context);
     markTypeAsLive(field.getType(), context);
   }
 
-  private void markStaticFieldAsLive(ProgramField field, ProgramMethod context) {
-    markStaticFieldAsLive(field, context, KeepReason.fieldReferencedIn(context));
-  }
-
-  private void markStaticFieldAsLive(
-      ProgramField field, ProgramDefinition context, KeepReason reason) {
-    // Mark the field type and holder live here, so that they exist at runtime.
-    markFieldAsTargeted(field);
-
-    markDirectAndIndirectClassInitializersAsLive(field.getHolder());
-
-    // This field might be an instance field reachable from a static context, e.g. a getStatic that
-    // resolves to an instance field. We have to keep the instance field nonetheless, as otherwise
-    // we might unmask a shadowed static field and hence change semantics.
-    if (field.getDefinition().isStatic()) {
-      if (Log.ENABLED) {
-        Log.verbose(getClass(), "Adding static field `%s` to live set.", field);
-      }
-    } else {
-      if (Log.ENABLED) {
-        Log.verbose(getClass(), "Adding instance field `%s` to live set (static context).", field);
-      }
-    }
-    processAnnotations(field.getHolder(), field);
-    liveFields.add(field, reason);
-
-    // Add all dependent members to the workqueue.
-    enqueueRootItems(rootSet.getDependentItems(field.getDefinition()));
-
-    checkMemberForSoftPinning(field);
-
-    // Notify analyses.
-    analyses.forEach(analysis -> analysis.processNewlyLiveField(field, context));
-  }
-
-  private void markInstanceFieldAsLive(
-      ProgramField field, ProgramDefinition context, KeepReason reason) {
-    markFieldAsTargeted(field);
-
-    if (Log.ENABLED) {
-      Log.verbose(getClass(), "Adding instance field `%s` to live set.", field);
-    }
-    processAnnotations(field.getHolder(), field);
-    liveFields.add(field, reason);
-
-    // Add all dependent members to the workqueue.
-    enqueueRootItems(rootSet.getDependentItems(field.getDefinition()));
-
-    checkMemberForSoftPinning(field);
-
-    // Notify analyses.
-    analyses.forEach(analysis -> analysis.processNewlyLiveField(field, context));
-  }
-
   private void markDirectStaticOrConstructorMethodAsLive(ProgramMethod method, KeepReason reason) {
-    if (!enqueueMarkMethodLiveAction(method, method, reason)) {
-      // Already marked live.
-      return;
-    }
-    // Should already have marked the type live previously.
-    assert method.getDefinition().isClassInitializer() || verifyMethodIsTargeted(method);
-    assert verifyTypeIsLive(method.getHolder());
-    if (Log.ENABLED) {
-      Log.verbose(getClass(), "Method `%s` has become live due to direct invoke", method);
+    if (workList.enqueueMarkMethodLiveAction(method, method, reason)) {
+      assert workList.enqueueAssertAction(
+          () -> {
+            // Should have marked the holder type live.
+            assert method.getDefinition().isClassInitializer() || verifyMethodIsTargeted(method);
+            assert verifyTypeIsLive(method.getHolder());
+          });
+    } else {
+      assert method.getDefinition().isClassInitializer() || verifyMethodIsTargeted(method);
+      assert workList.enqueueAssertAction(() -> verifyTypeIsLive(method.getHolder()));
     }
   }
 
   private void markVirtualMethodAsLive(ProgramMethod method, KeepReason reason) {
-    assert method != null;
     // Only explicit keep rules or reflective use should make abstract methods live.
     assert !method.getDefinition().isAbstract()
         || reason.isDueToKeepRule()
         || reason.isDueToReflectiveUse();
-    if (enqueueMarkMethodLiveAction(method, method, reason)) {
-      if (Log.ENABLED) {
-        Log.verbose(getClass(), "Adding virtual method `%s` to live set.", method);
-      }
-    }
+    workList.enqueueMarkMethodLiveAction(method, method, reason);
   }
 
   public boolean isFieldReferenced(DexEncodedField field) {
@@ -2795,6 +2748,10 @@
     return targetedMethods.contains(method);
   }
 
+  public boolean isMethodTargeted(ProgramMethod method) {
+    return isMethodTargeted(method.getDefinition());
+  }
+
   public boolean isTypeLive(DexClass clazz) {
     return clazz.isProgramClass()
         ? isTypeLive(clazz.asProgramClass())
@@ -2814,32 +2771,6 @@
     liveTypes.items.forEach(consumer);
   }
 
-  // Package protected due to entry point from worklist.
-  void markInstanceFieldAsReachable(
-      ProgramField field, ProgramDefinition context, KeepReason reason) {
-    if (Log.ENABLED) {
-      Log.verbose(getClass(), "Marking instance field `%s` as reachable.", field);
-    }
-
-    // We might have a instance field access that is dispatched to a static field. In such case,
-    // we have to keep the static field, so that the dispatch fails at runtime in the same way that
-    // it did before. We have to keep the field even if the receiver has no live inhabitants, as
-    // field resolution happens before the receiver is inspected.
-    if (field.getDefinition().isStatic()) {
-      markStaticFieldAsLive(field, context, reason);
-    } else if (objectAllocationInfoCollection.isInstantiatedDirectlyOrHasInstantiatedSubtype(
-        field.getHolder())) {
-      markInstanceFieldAsLive(field, context, reason);
-    } else {
-      markFieldAsTargeted(field);
-
-      // Add the field to the reachable set if the type later becomes instantiated.
-      reachableInstanceFields
-          .computeIfAbsent(field.getHolder(), ignore -> ProgramFieldSet.create())
-          .add(field);
-    }
-  }
-
   private void markVirtualMethodAsReachable(
       DexMethod method, boolean interfaceInvoke, ProgramDefinition context, KeepReason reason) {
     if (method.holder.isArrayType()) {
@@ -2939,7 +2870,7 @@
       LookupLambdaTarget target, Function<ProgramMethod, KeepReasonWitness> reason) {
     ProgramMethod implementationMethod = target.getImplementationMethod().asProgramMethod();
     if (implementationMethod != null) {
-      enqueueMarkMethodLiveAction(
+      workList.enqueueMarkMethodLiveAction(
           implementationMethod, implementationMethod, reason.apply(implementationMethod));
     }
   }
@@ -3051,11 +2982,8 @@
     return builder.build(previousMainDexInfo);
   }
 
-  public AppInfoWithLiveness traceApplication(
-      RootSet rootSet,
-      ExecutorService executorService,
-      Timing timing)
-      throws ExecutionException {
+  public EnqueuerResult traceApplication(
+      RootSet rootSet, ExecutorService executorService, Timing timing) throws ExecutionException {
     this.rootSet = rootSet;
     // Translate the result of root-set computation into enqueuer actions.
     if (appView.options().getProguardConfiguration() != null
@@ -3119,15 +3047,7 @@
       // no AppInfo is returned.
       return null;
     }
-    AppInfoWithLiveness appInfoWithLiveness = createAppInfo(appInfo);
-    if (options.testing.enqueuerInspector != null) {
-      options.testing.enqueuerInspector.accept(appInfoWithLiveness, mode);
-    }
-    return appInfoWithLiveness;
-  }
-
-  public NestedGraphLens buildGraphLens() {
-    return lambdaRewriter != null ? lambdaRewriter.fixup() : null;
+    return createEnqueuerResult(appInfo);
   }
 
   private void keepClassWithRules(DexProgramClass clazz, Set<ProguardKeepRuleBase> rules) {
@@ -3174,12 +3094,6 @@
 
     List<ProgramMethod> desugaredMethods = new LinkedList<>();
 
-    Map<DexType, Pair<DexProgramClass, ProgramMethod>> syntheticInstantiations =
-        new IdentityHashMap<>();
-
-    ProgramMethodMap<Set<DexField>> syntheticStaticFieldReadsByContext =
-        ProgramMethodMap.createLinked();
-
     Map<DexMethod, ProgramMethod> liveMethods = new IdentityHashMap<>();
 
     Map<DexType, DexClasspathClass> syntheticClasspathClasses = new IdentityHashMap<>();
@@ -3188,9 +3102,6 @@
     List<Pair<ProgramMethod, Consumer<KeepMethodInfo.Joiner>>> liveMethodsWithKeepActions =
         new ArrayList<>();
 
-    // 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;
     }
@@ -3203,18 +3114,12 @@
     boolean isEmpty() {
       boolean empty =
           desugaredMethods.isEmpty()
-              && syntheticInstantiations.isEmpty()
               && liveMethods.isEmpty()
               && syntheticClasspathClasses.isEmpty();
-      assert !empty || (liveMethodsWithKeepActions.isEmpty() && mainDexTypes.isEmpty());
+      assert !empty || liveMethodsWithKeepActions.isEmpty();
       return empty;
     }
 
-    void addInstantiatedClass(DexProgramClass clazz, ProgramMethod context) {
-      assert !syntheticInstantiations.containsKey(clazz.type);
-      syntheticInstantiations.put(clazz.type, new Pair<>(clazz, context));
-    }
-
     void addClasspathClass(DexClasspathClass clazz) {
       DexClasspathClass old = syntheticClasspathClasses.put(clazz.type, clazz);
       assert old == null;
@@ -3237,11 +3142,6 @@
       appBuilder.addClasspathClasses(syntheticClasspathClasses.values());
     }
 
-    void amendMainDexClasses(MainDexInfo mainDexInfo) {
-      assert !isEmpty();
-      mainDexTypes.forEach(mainDexInfo::addSyntheticClass);
-    }
-
     void enqueueWorkItems(Enqueuer enqueuer) {
       assert !isEmpty();
       assert enqueuer.mode.isInitialTreeShaking();
@@ -3255,45 +3155,13 @@
 
       liveMethodsWithKeepActions.forEach(
           item -> enqueuer.keepInfo.joinMethod(item.getFirst(), item.getSecond()));
-      for (Pair<DexProgramClass, ProgramMethod> clazzAndContext :
-          syntheticInstantiations.values()) {
-        enqueuer.workList.enqueueMarkInstantiatedAction(
-            clazzAndContext.getFirst(),
-            clazzAndContext.getSecond(),
-            InstantiationReason.SYNTHESIZED_CLASS,
-            fakeReason);
-      }
-      syntheticStaticFieldReadsByContext.forEach(
-          (context, fields) ->
-              fields.forEach(
-                  field -> enqueuer.workList.enqueueTraceStaticFieldRead(field, context)));
       for (ProgramMethod liveMethod : liveMethods.values()) {
         assert !enqueuer.targetedMethods.contains(liveMethod.getDefinition());
         enqueuer.markMethodAsTargeted(liveMethod, fakeReason);
-        enqueuer.enqueueMarkMethodLiveAction(liveMethod, liveMethod, fakeReason);
+        enqueuer.workList.enqueueMarkMethodLiveAction(liveMethod, liveMethod, fakeReason);
       }
       enqueuer.liveNonProgramTypes.addAll(syntheticClasspathClasses.values());
     }
-
-    void registerStatelessLambdaInstanceFieldReads(
-        ProgramMethodMap<Map<DexCallSite, LambdaClass>> lambdaCallSites) {
-      lambdaCallSites.forEach(this::registerStatelessLambdaInstanceFieldReads);
-    }
-
-    private void registerStatelessLambdaInstanceFieldReads(
-        ProgramMethod context, Map<DexCallSite, LambdaClass> callSites) {
-      Set<DexField> syntheticStaticFieldReadsInContext = null;
-      for (LambdaClass lambdaClass : callSites.values()) {
-        if (lambdaClass.isStateless()) {
-          if (syntheticStaticFieldReadsInContext == null) {
-            syntheticStaticFieldReadsInContext =
-                syntheticStaticFieldReadsByContext.computeIfAbsent(
-                    context, ignore -> Sets.newLinkedHashSet());
-          }
-          syntheticStaticFieldReadsInContext.add(lambdaClass.lambdaField);
-        }
-      }
-    }
   }
 
   private void synthesize() throws ExecutionException {
@@ -3306,7 +3174,6 @@
     SyntheticAdditions additions = new SyntheticAdditions(appView.createProcessorContext());
     desugar(additions);
     synthesizeInterfaceMethodBridges(additions);
-    synthesizeLambdas(additions);
     synthesizeLibraryConversionWrappers(additions);
     synthesizeBackports(additions);
     synthesizeTwrCloseResource(additions);
@@ -3322,7 +3189,6 @@
               additions.amendApplication(appBuilder);
               return appBuilder.build();
             });
-    additions.amendMainDexClasses(appInfo.getMainDexInfo());
     appView.setAppInfo(appInfo);
     subtypingInfo = new SubtypingInfo(appView);
 
@@ -3332,13 +3198,17 @@
   }
 
   private void desugar(SyntheticAdditions additions) throws ExecutionException {
+    if (pendingDesugaring.isEmpty()) {
+      return;
+    }
     R8CfInstructionDesugaringEventConsumer desugaringEventConsumer =
-        CfInstructionDesugaringEventConsumer.createForR8();
+        CfInstructionDesugaringEventConsumer.createForR8(appView);
     ThreadUtils.processItems(
         pendingDesugaring,
-        method -> desugaring.desugar(method, desugaringEventConsumer),
+        method ->
+            desugaring.desugar(method, additions.getMethodContext(method), desugaringEventConsumer),
         executorService);
-    desugaringEventConsumer.finalizeDesugaring(appView);
+    desugaringEventConsumer.finalizeDesugaring();
     Iterables.addAll(additions.desugaredMethods, pendingDesugaring);
     pendingDesugaring.clear();
   }
@@ -3367,50 +3237,6 @@
     }
   }
 
-  private void synthesizeLambdas(SyntheticAdditions additions) {
-    if (lambdasForDesugaring.isEmpty()) {
-      return;
-    }
-    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.
-      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.
-      DexEncodedMethod constructor = programClass.lookupDirectMethod(lambdaClass.constructor);
-      KeepReason reason = KeepReason.instantiatedIn(context);
-      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.
-    lambdaCallSites.forEach(this::rewriteLambdaCallSites);
-    additions.registerStatelessLambdaInstanceFieldReads(lambdaCallSites);
-
-    // Remove all '$deserializeLambda$' methods which are not supported by desugaring.
-    for (DexProgramClass clazz : classesWithSerializableLambdas) {
-      clazz.removeMethod(appView.dexItemFactory().deserializeLambdaMethod);
-    }
-
-    // Clear state before next fixed point iteration.
-    lambdasForDesugaring.clear();
-  }
-
   private void finalizeLibraryMethodOverrideInformation() {
     for (DexProgramClass liveType : liveTypes.getItems()) {
       for (DexEncodedMethod method : liveType.virtualMethods()) {
@@ -3430,13 +3256,7 @@
     return true;
   }
 
-  private AppInfoWithLiveness createAppInfo(AppInfoWithClassHierarchy appInfo) {
-    // Once all tracing is done, we generate accessor methods for lambdas.
-    // These are assumed to be simple forwarding or access flag updates, thus no further tracing
-    // is needed. These cannot be generated as part of lambda synthesis as changing a direct method
-    // to a static method will invalidate the reachable method sets for tracing methods.
-    ensureLambdaAccessibility();
-
+  private EnqueuerResult createEnqueuerResult(AppInfoWithClassHierarchy appInfo) {
     // Compute the set of dead proto types.
     deadProtoTypeCandidates.removeIf(this::isTypeLive);
     Set<DexType> deadProtoTypes =
@@ -3532,42 +3352,10 @@
             lockCandidates,
             initClassReferences);
     appInfo.markObsolete();
-    return appInfoWithLiveness;
-  }
-
-  private void ensureLambdaAccessibility() {
-    if (lambdaRewriter == null) {
-      return;
+    if (options.testing.enqueuerInspector != null) {
+      options.testing.enqueuerInspector.accept(appInfoWithLiveness, mode);
     }
-    lambdaRewriter
-        .getKnownLambdaClasses()
-        .forEach(
-            lambda -> {
-              DexProgramClass synthesizedClass = lambda.getLambdaProgramClass();
-              assert synthesizedClass != null;
-              assert liveTypes.contains(synthesizedClass);
-              if (synthesizedClass == null) {
-                return;
-              }
-              DexMethod method = lambda.descriptor.getMainMethod();
-              if (!liveMethods.contains(synthesizedClass.lookupMethod(method))) {
-                return;
-              }
-              ProgramMethod accessor = lambda.target.ensureAccessibilityIfNeeded(false);
-              if (accessor != null) {
-                liveMethods.add(accessor, graphReporter.fakeReportShouldNotBeUsed());
-              }
-            });
-    unpinLambdaMethods();
-  }
-
-  // TODO(b/157700141): Determine if this is the right way to allow modification of pinned lambdas.
-  private void unpinLambdaMethods() {
-    assert lambdaRewriter != null;
-    for (DexMethod method : lambdaRewriter.getForcefullyMovedMethods()) {
-      keepInfo.unsafeUnpinMethod(method);
-      rootSet.prune(method);
-    }
+    return new EnqueuerResult(appInfoWithLiveness);
   }
 
   private boolean verifyReferences(DexApplication app) {
@@ -3647,12 +3435,6 @@
     desugaredLibraryWrapperAnalysis.generateWrappers(additions::addClasspathClass);
   }
 
-  private void rewriteLambdaCallSites(
-      ProgramMethod context, Map<DexCallSite, LambdaClass> callSites) {
-    assert !callSites.isEmpty();
-    int replaced = LambdaRewriter.desugarLambdas(context, callSites::get);
-    assert replaced == callSites.size();
-  }
 
   private static <D extends DexEncodedMember<D, R>, R extends DexMember<D, R>>
       Set<R> toDescriptorSet(Set<D> set) {
@@ -3878,7 +3660,7 @@
           singleTargetHolder.isInterface(),
           singleTarget,
           graphReporter.fakeReportShouldNotBeUsed());
-      enqueueMarkMethodLiveAction(
+      workList.enqueueMarkMethodLiveAction(
           singleTarget, singleTarget, graphReporter.fakeReportShouldNotBeUsed());
     }
     action.getAction().accept(builder);
@@ -3930,9 +3712,9 @@
   // Package protected due to entry point from worklist.
   void markFieldAsKept(ProgramField field, KeepReason reason) {
     if (field.getDefinition().isStatic()) {
-      markStaticFieldAsLive(field, field, reason);
+      markFieldAsLive(field, field, reason);
     } else {
-      markInstanceFieldAsReachable(field, field, reason);
+      workList.enqueueMarkFieldAsReachableAction(field, field, reason);
     }
   }
 
@@ -4004,15 +3786,17 @@
 
   // Package protected due to entry point from worklist.
   void markMethodAsLive(ProgramMethod method, ProgramDefinition context) {
-    DexProgramClass holder = method.getHolder();
-    DexEncodedMethod definition = method.getDefinition();
+    assert liveMethods.contains(method);
 
-    assert liveMethods.contains(definition);
+    DexEncodedMethod definition = method.getDefinition();
+    assert !definition.getOptimizationInfo().forceInline();
 
     if (definition.isStatic()) {
       markDirectAndIndirectClassInitializersAsLive(method.getHolder());
     }
 
+    traceNonDesugaredCode(method);
+
     ProgramMethodSet superCallTargets = superInvokeDependencies.get(method.getDefinition());
     if (superCallTargets != null) {
       for (ProgramMethod superCallTarget : superCallTargets) {
@@ -4023,12 +3807,6 @@
         markVirtualMethodAsLive(superCallTarget, KeepReason.invokedViaSuperFrom(method));
       }
     }
-    markParameterAndReturnTypesAsLive(method);
-    processAnnotations(holder, method);
-    definition.parameterAnnotationsList.forEachAnnotation(
-        annotation -> processAnnotation(holder, method, annotation));
-
-    traceNonDesugaredCode(method);
 
     // Add all dependent members to the workqueue.
     enqueueRootItems(rootSet.getDependentItems(definition));
@@ -4039,6 +3817,34 @@
     analyses.forEach(analysis -> analysis.processNewlyLiveMethod(method, context));
   }
 
+  private void markMethodAsTargeted(ProgramMethod method, KeepReason reason) {
+    if (!targetedMethods.add(method, reason)) {
+      // Already targeted.
+      return;
+    }
+
+    if (!liveMethods.contains(method)) {
+      traceMethodDefinitionExcludingCode(method);
+    }
+
+    if (forceProguardCompatibility) {
+      // Keep targeted default methods in compatibility mode. The tree pruner will otherwise make
+      // these methods abstract, whereas Proguard does not (seem to) touch their code.
+      if (!method.getAccessFlags().isAbstract() && method.getHolder().isInterface()) {
+        markMethodAsLiveWithCompatRule(method);
+      }
+    }
+  }
+
+  void traceMethodDefinitionExcludingCode(ProgramMethod method) {
+    markReferencedTypesAsLive(method);
+    processAnnotations(method);
+    method
+        .getDefinition()
+        .getParameterAnnotations()
+        .forEachAnnotation(annotation -> processAnnotation(method, annotation));
+  }
+
   private void traceNonDesugaredCode(ProgramMethod method) {
     if (getMode().isInitialTreeShaking() && desugaring.needsDesugaring(method)) {
       pendingDesugaring.add(method);
@@ -4069,7 +3875,7 @@
   }
 
   private void markReferencedTypesAsLive(ProgramMethod method) {
-    markTypeAsLive(method.getHolderType(), method);
+    markTypeAsLive(method.getHolder(), method);
     markParameterAndReturnTypesAsLive(method);
   }
 
@@ -4134,7 +3940,8 @@
   }
 
   private void markMethodAsLiveWithCompatRule(ProgramMethod method) {
-    enqueueMarkMethodLiveAction(method, method, graphReporter.reportCompatKeepMethod(method));
+    workList.enqueueMarkMethodLiveAction(
+        method, method, graphReporter.reportCompatKeepMethod(method));
   }
 
   private void handleReflectiveBehavior(ProgramMethod method) {
@@ -4652,7 +4459,7 @@
                 : fieldAccessInfoCollection.extend(
                     fieldReference, new FieldAccessInfoImpl(fieldReference));
         fieldAccessInfo.setReadFromAnnotation();
-        markStaticFieldAsLive(field, context, KeepReason.referencedInAnnotation(annotationHolder));
+        markFieldAsLive(field, context, KeepReason.referencedInAnnotation(annotationHolder));
         // When an annotation has a field of an enum type the JVM will use the values() method on
         // that enum class if the field is referenced.
         if (options.isGeneratingClassFiles() && field.getHolder().isEnum()) {
@@ -4661,7 +4468,7 @@
         }
       } else {
         // There is no dispatch on annotations, so only keep what is directly referenced.
-        markInstanceFieldAsReachable(
+        workList.enqueueMarkFieldAsReachableAction(
             field, context, KeepReason.referencedInAnnotation(annotationHolder));
       }
       return false;
diff --git a/src/main/java/com/android/tools/r8/shaking/EnqueuerResult.java b/src/main/java/com/android/tools/r8/shaking/EnqueuerResult.java
new file mode 100644
index 0000000..746cf59
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/shaking/EnqueuerResult.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 com.android.tools.r8.shaking;
+
+public class EnqueuerResult {
+
+  private final AppInfoWithLiveness appInfo;
+
+  EnqueuerResult(AppInfoWithLiveness appInfo) {
+    this.appInfo = appInfo;
+  }
+
+  public AppInfoWithLiveness getAppInfo() {
+    return appInfo;
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/shaking/EnqueuerWorklist.java b/src/main/java/com/android/tools/r8/shaking/EnqueuerWorklist.java
index 01e415b..65081d5 100644
--- a/src/main/java/com/android/tools/r8/shaking/EnqueuerWorklist.java
+++ b/src/main/java/com/android/tools/r8/shaking/EnqueuerWorklist.java
@@ -12,6 +12,8 @@
 import com.android.tools.r8.graph.ProgramField;
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.shaking.GraphReporter.KeepReasonWitness;
+import com.android.tools.r8.utils.Action;
+import com.android.tools.r8.utils.InternalOptions;
 import java.util.ArrayDeque;
 import java.util.Queue;
 
@@ -21,6 +23,19 @@
     public abstract void run(Enqueuer enqueuer);
   }
 
+  static class AssertAction extends EnqueuerAction {
+    private final Action assertion;
+
+    AssertAction(Action assertion) {
+      this.assertion = assertion;
+    }
+
+    @Override
+    public void run(Enqueuer enqueuer) {
+      assertion.execute();
+    }
+  }
+
   static class MarkReachableDirectAction extends EnqueuerAction {
     private final DexMethod target;
     // TODO(b/175854431): Avoid pushing context on worklist.
@@ -55,13 +70,13 @@
     }
   }
 
-  static class MarkInstanceFieldAsReachableAction extends EnqueuerAction {
+  static class MarkFieldAsReachableAction extends EnqueuerAction {
     private final ProgramField field;
     // TODO(b/175854431): Avoid pushing context on worklist.
     private final ProgramDefinition context;
     private final KeepReason reason;
 
-    public MarkInstanceFieldAsReachableAction(
+    public MarkFieldAsReachableAction(
         ProgramField field, ProgramDefinition context, KeepReason reason) {
       this.field = field;
       this.context = context;
@@ -70,7 +85,7 @@
 
     @Override
     public void run(Enqueuer enqueuer) {
-      enqueuer.markInstanceFieldAsReachable(field, context, reason);
+      enqueuer.markFieldAsReachable(field, context, reason);
     }
   }
 
@@ -219,6 +234,19 @@
     }
   }
 
+  static class TraceMethodDefinitionExcludingCodeAction extends EnqueuerAction {
+    private final ProgramMethod method;
+
+    TraceMethodDefinitionExcludingCodeAction(ProgramMethod method) {
+      this.method = method;
+    }
+
+    @Override
+    public void run(Enqueuer enqueuer) {
+      enqueuer.traceMethodDefinitionExcludingCode(method);
+    }
+  }
+
   static class TraceNewInstanceAction extends EnqueuerAction {
     private final DexType type;
     // TODO(b/175854431): Avoid pushing context on worklist.
@@ -251,12 +279,15 @@
     }
   }
 
+  private final Enqueuer enqueuer;
   private final Queue<EnqueuerAction> queue = new ArrayDeque<>();
 
-  private EnqueuerWorklist() {}
+  private EnqueuerWorklist(Enqueuer enqueuer) {
+    this.enqueuer = enqueuer;
+  }
 
-  public static EnqueuerWorklist createWorklist() {
-    return new EnqueuerWorklist();
+  public static EnqueuerWorklist createWorklist(Enqueuer enqueuer) {
+    return new EnqueuerWorklist(enqueuer);
   }
 
   public boolean isEmpty() {
@@ -267,6 +298,13 @@
     return queue.poll();
   }
 
+  boolean enqueueAssertAction(Action assertion) {
+    if (InternalOptions.assertionsEnabled()) {
+      queue.add(new AssertAction(assertion));
+    }
+    return true;
+  }
+
   void enqueueMarkReachableDirectAction(
       DexMethod method, ProgramDefinition context, KeepReason reason) {
     queue.add(new MarkReachableDirectAction(method, context, reason));
@@ -276,9 +314,9 @@
     queue.add(new MarkReachableSuperAction(method, from));
   }
 
-  public void enqueueMarkInstanceFieldAsReachableAction(
+  public void enqueueMarkFieldAsReachableAction(
       ProgramField field, ProgramDefinition context, KeepReason reason) {
-    queue.add(new MarkInstanceFieldAsReachableAction(field, context, reason));
+    queue.add(new MarkFieldAsReachableAction(field, context, reason));
   }
 
   // TODO(b/142378367): Context is the containing method that is cause of the instantiation.
@@ -305,8 +343,16 @@
     queue.add(new MarkInterfaceInstantiatedAction(clazz, reason));
   }
 
-  void enqueueMarkMethodLiveAction(ProgramMethod method, ProgramDefinition context) {
-    queue.add(new MarkMethodLiveAction(method, context));
+  boolean enqueueMarkMethodLiveAction(
+      ProgramMethod method, ProgramDefinition context, KeepReason reason) {
+    if (enqueuer.addLiveMethod(method, reason)) {
+      queue.add(new MarkMethodLiveAction(method, context));
+      if (!enqueuer.isMethodTargeted(method)) {
+        queue.add(new TraceMethodDefinitionExcludingCodeAction(method));
+      }
+      return true;
+    }
+    return false;
   }
 
   void enqueueMarkMethodKeptAction(ProgramMethod method, KeepReason reason) {
diff --git a/src/main/java/com/android/tools/r8/shaking/MissingClasses.java b/src/main/java/com/android/tools/r8/shaking/MissingClasses.java
index dee668f..aa49727 100644
--- a/src/main/java/com/android/tools/r8/shaking/MissingClasses.java
+++ b/src/main/java/com/android/tools/r8/shaking/MissingClasses.java
@@ -8,6 +8,8 @@
 import static com.android.tools.r8.ir.desugar.DesugaredLibraryRetargeter.getRetargetPackageAndClassPrefixDescriptor;
 import static com.android.tools.r8.ir.desugar.InterfaceMethodRewriter.EMULATE_LIBRARY_CLASS_NAME_SUFFIX;
 
+import com.android.tools.r8.diagnostic.MissingDefinitionsDiagnostic;
+import com.android.tools.r8.diagnostic.internal.MissingDefinitionsDiagnosticImpl;
 import com.android.tools.r8.errors.dontwarn.DontWarnConfiguration;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexItemFactory;
@@ -123,8 +125,8 @@
       Map<DexType, Set<DexReference>> missingClassesToBeReported =
           getMissingClassesToBeReported(appView);
       if (!missingClassesToBeReported.isEmpty()) {
-        MissingClassesDiagnostic diagnostic =
-            new MissingClassesDiagnostic.Builder()
+        MissingDefinitionsDiagnostic diagnostic =
+            MissingDefinitionsDiagnosticImpl.builder()
                 .addMissingClasses(missingClassesToBeReported)
                 .setFatal(!options.ignoreMissingClasses)
                 .build();
diff --git a/src/main/java/com/android/tools/r8/shaking/MissingClassesDiagnostic.java b/src/main/java/com/android/tools/r8/shaking/MissingClassesDiagnostic.java
deleted file mode 100644
index 0f4e97b..0000000
--- a/src/main/java/com/android/tools/r8/shaking/MissingClassesDiagnostic.java
+++ /dev/null
@@ -1,228 +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.shaking;
-
-import static com.android.tools.r8.utils.PredicateUtils.not;
-
-import com.android.tools.r8.Diagnostic;
-import com.android.tools.r8.Keep;
-import com.android.tools.r8.graph.DexField;
-import com.android.tools.r8.graph.DexMethod;
-import com.android.tools.r8.graph.DexReference;
-import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.origin.Origin;
-import com.android.tools.r8.position.Position;
-import com.android.tools.r8.references.ClassReference;
-import com.android.tools.r8.references.FieldReference;
-import com.android.tools.r8.references.MethodReference;
-import com.android.tools.r8.references.Reference;
-import com.android.tools.r8.utils.FieldReferenceUtils;
-import com.android.tools.r8.utils.MethodReferenceUtils;
-import com.google.common.collect.ImmutableSet;
-import com.google.common.collect.ImmutableSortedMap;
-import com.google.common.collect.Sets;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Comparator;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Map;
-import java.util.Map.Entry;
-import java.util.Optional;
-import java.util.Set;
-import java.util.SortedMap;
-import java.util.function.Function;
-
-@Keep
-public class MissingClassesDiagnostic implements Diagnostic {
-
-  private static class MissingClassAccessContexts {
-
-    private ImmutableSet<ClassReference> classContexts;
-    private ImmutableSet<FieldReference> fieldContexts;
-    private ImmutableSet<MethodReference> methodContexts;
-
-    private MissingClassAccessContexts(
-        ImmutableSet<ClassReference> classContexts,
-        ImmutableSet<FieldReference> fieldContexts,
-        ImmutableSet<MethodReference> methodContexts) {
-      this.classContexts = classContexts;
-      this.fieldContexts = fieldContexts;
-      this.methodContexts = methodContexts;
-    }
-
-    static Builder builder() {
-      return new Builder();
-    }
-
-    String getReferencedFromMessageSuffix(ClassReference missingClass) {
-      if (!fieldContexts.isEmpty()) {
-        return " (referenced from: "
-            + FieldReferenceUtils.toSourceString(fieldContexts.iterator().next())
-            + ")";
-      }
-      if (!methodContexts.isEmpty()) {
-        return " (referenced from: "
-            + MethodReferenceUtils.toSourceString(methodContexts.iterator().next())
-            + ")";
-      }
-      // TODO(b/175543745): The legacy reporting is context insensitive, and therefore uses the
-      //  missing classes as their own context. Once legacy reporting is removed, this should be
-      //  simplified to taking the first context.
-      Optional<ClassReference> classContext =
-          classContexts.stream().filter(not(missingClass::equals)).findFirst();
-      return classContext
-          .map(classReference -> " (referenced from: " + classReference.getTypeName() + ")")
-          .orElse("");
-    }
-
-    static class Builder {
-
-      private final Set<DexReference> contexts = Sets.newIdentityHashSet();
-
-      Builder addAll(Set<DexReference> contexts) {
-        this.contexts.addAll(contexts);
-        return this;
-      }
-
-      // TODO(b/179249745): Sort on demand in getReferencedFromMessageSuffix() instead.
-      MissingClassAccessContexts build() {
-        // Sort the contexts for deterministic reporting.
-        List<DexType> classContexts = new ArrayList<>();
-        List<DexField> fieldContexts = new ArrayList<>();
-        List<DexMethod> methodContexts = new ArrayList<>();
-        contexts.forEach(
-            context -> context.apply(classContexts::add, fieldContexts::add, methodContexts::add));
-        Collections.sort(classContexts);
-        Collections.sort(fieldContexts);
-        Collections.sort(methodContexts);
-
-        // Build immutable sets (which preserve insertion order) from the sorted lists, mapping each
-        // DexType, DexField, and DexMethod to ClassReference, FieldReference, and MethodReference,
-        // respectively.
-        return new MissingClassAccessContexts(
-            toImmutableSet(classContexts, DexType::asClassReference),
-            toImmutableSet(fieldContexts, DexField::asFieldReference),
-            toImmutableSet(methodContexts, DexMethod::asMethodReference));
-      }
-
-      private <S, T> ImmutableSet<T> toImmutableSet(List<S> list, Function<S, T> fn) {
-        ImmutableSet.Builder<T> builder = ImmutableSet.builder();
-        list.forEach(element -> builder.add(fn.apply(element)));
-        return builder.build();
-      }
-    }
-  }
-
-  private final boolean fatal;
-  private final SortedMap<ClassReference, MissingClassAccessContexts> missingClasses;
-
-  private MissingClassesDiagnostic(
-      boolean fatal, SortedMap<ClassReference, MissingClassAccessContexts> missingClasses) {
-    assert !missingClasses.isEmpty();
-    this.fatal = fatal;
-    this.missingClasses = missingClasses;
-  }
-
-  public Set<ClassReference> getMissingClasses() {
-    return missingClasses.keySet();
-  }
-
-  /** A missing class(es) failure can generally not be attributed to a single origin. */
-  @Override
-  public Origin getOrigin() {
-    return Origin.unknown();
-  }
-
-  /** A missing class(es) failure can generally not be attributed to a single position. */
-  @Override
-  public Position getPosition() {
-    return Position.UNKNOWN;
-  }
-
-  @Override
-  public String getDiagnosticMessage() {
-    return fatal ? getFatalDiagnosticMessage() : getNonFatalDiagnosticMessage();
-  }
-
-  private String getFatalDiagnosticMessage() {
-    if (missingClasses.size() == 1) {
-      StringBuilder builder =
-          new StringBuilder(
-              "Compilation can't be completed because the following class is missing: ");
-      writeMissingClass(builder, missingClasses.entrySet().iterator().next());
-      return builder.append(".").toString();
-    }
-
-    StringBuilder builder =
-        new StringBuilder("Compilation can't be completed because the following ")
-            .append(missingClasses.size())
-            .append(" classes are missing:");
-    missingClasses.forEach(
-        (missingClass, contexts) ->
-            writeMissingClass(
-                builder.append(System.lineSeparator()).append("- "), missingClass, contexts));
-    return builder.toString();
-  }
-
-  private String getNonFatalDiagnosticMessage() {
-    StringBuilder builder = new StringBuilder();
-    Iterator<Entry<ClassReference, MissingClassAccessContexts>> missingClassesIterator =
-        missingClasses.entrySet().iterator();
-
-    // The diagnostic is always non-empty.
-    assert missingClassesIterator.hasNext();
-
-    // Write first line.
-    writeMissingClass(builder.append("Missing class "), missingClassesIterator.next());
-
-    // Write remaining lines with line separator before.
-    missingClassesIterator.forEachRemaining(
-        missingClassInfo ->
-            writeMissingClass(
-                builder.append(System.lineSeparator()).append("Missing class "), missingClassInfo));
-
-    return builder.toString();
-  }
-
-  private static void writeMissingClass(
-      StringBuilder builder, Entry<ClassReference, MissingClassAccessContexts> missingClassInfo) {
-    writeMissingClass(builder, missingClassInfo.getKey(), missingClassInfo.getValue());
-  }
-
-  private static void writeMissingClass(
-      StringBuilder builder, ClassReference missingClass, MissingClassAccessContexts contexts) {
-    builder
-        .append(missingClass.getTypeName())
-        .append(contexts.getReferencedFromMessageSuffix(missingClass));
-  }
-
-  public static class Builder {
-
-    private boolean fatal;
-    private ImmutableSortedMap.Builder<ClassReference, MissingClassAccessContexts>
-        missingClassesBuilder =
-            ImmutableSortedMap.orderedBy(Comparator.comparing(ClassReference::getDescriptor));
-
-    public MissingClassesDiagnostic.Builder addMissingClasses(
-        Map<DexType, Set<DexReference>> missingClasses) {
-      missingClasses.forEach(
-          (missingClass, contexts) ->
-              missingClassesBuilder.put(
-                  Reference.classFromDescriptor(missingClass.toDescriptorString()),
-                  MissingClassAccessContexts.builder().addAll(contexts).build()));
-      return this;
-    }
-
-    public MissingClassesDiagnostic.Builder setFatal(boolean fatal) {
-      this.fatal = fatal;
-      return this;
-    }
-
-    public MissingClassesDiagnostic build() {
-      return new MissingClassesDiagnostic(fatal, missingClassesBuilder.build());
-    }
-  }
-}
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 6c07af2..73b61b3 100644
--- a/src/main/java/com/android/tools/r8/synthesis/SyntheticItems.java
+++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticItems.java
@@ -4,7 +4,6 @@
 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;
 import com.android.tools.r8.graph.DexApplication;
@@ -12,6 +11,7 @@
 import com.android.tools.r8.graph.DexClasspathClass;
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.DexReference;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.GraphLens.NonIdentityGraphLens;
 import com.android.tools.r8.graph.ProgramDefinition;
@@ -137,16 +137,6 @@
     appView.setAppInfo(new AppInfo(commit, appView.appInfo().getMainDexInfo()));
   }
 
-  // Internal synthetic id creation helpers.
-
-  private synchronized String getNextSyntheticId() {
-    if (nextSyntheticId == INVALID_ID_AFTER_SYNTHETIC_FINALIZATION) {
-      throw new InternalCompilerError(
-          "Unexpected attempt to synthesize classes after synthetic finalization.");
-    }
-    return Integer.toString(nextSyntheticId++);
-  }
-
   // Predicates and accessors.
 
   @Override
@@ -225,6 +215,13 @@
     return isSyntheticClass(clazz.type);
   }
 
+  // TODO(b/180091213): Implement this and remove client provided the oracle.
+  public Set<DexReference> getSynthesizingContexts(
+      DexProgramClass clazz, Function<DexProgramClass, Set<DexReference>> oracle) {
+    assert isSyntheticClass(clazz);
+    return oracle.apply(clazz);
+  }
+
   // The compiler should not inspect the kind of a synthetic, so this provided only as a assertion
   // utility.
   public boolean verifySyntheticLambdaProperty(
diff --git a/src/main/java/com/android/tools/r8/synthesis/SyntheticMethodReference.java b/src/main/java/com/android/tools/r8/synthesis/SyntheticMethodReference.java
index 0b53962..930443a 100644
--- a/src/main/java/com/android/tools/r8/synthesis/SyntheticMethodReference.java
+++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticMethodReference.java
@@ -54,7 +54,7 @@
     if (method != rewritten && !lens.isSimpleRenaming(method.holder, rewritten.holder)) {
       // If the referenced item is rewritten, it should be moved to another holder as the
       // synthetic holder is no longer part of the synthetic collection.
-      assert method.holder != rewritten.holder;
+      assert method.holder != rewritten.holder : "The synthetic method reference should have moved";
       assert SyntheticNaming.verifyNotInternalSynthetic(rewritten.holder);
       return null;
     }
diff --git a/src/main/java/com/android/tools/r8/synthesis/SyntheticNaming.java b/src/main/java/com/android/tools/r8/synthesis/SyntheticNaming.java
index c433ec0..63772a2 100644
--- a/src/main/java/com/android/tools/r8/synthesis/SyntheticNaming.java
+++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticNaming.java
@@ -30,6 +30,7 @@
     STATIC_INTERFACE_CALL("StaticInterfaceCall", true),
     TO_STRING_IF_NOT_NULL("ToStringIfNotNull", true),
     THROW_CCE_IF_NOT_NULL("ThrowCCEIfNotNull", true),
+    THROW_IAE("ThrowIAE", true),
     THROW_ICCE("ThrowICCE", true),
     THROW_NSME("ThrowNSME", true),
     TWR_CLOSE_RESOURCE("TwrCloseResource", true),
diff --git a/src/main/java/com/android/tools/r8/utils/ForEachable.java b/src/main/java/com/android/tools/r8/utils/ForEachable.java
index 4c8ae53..63ed027 100644
--- a/src/main/java/com/android/tools/r8/utils/ForEachable.java
+++ b/src/main/java/com/android/tools/r8/utils/ForEachable.java
@@ -8,4 +8,9 @@
 public interface ForEachable<T> {
 
   void forEach(Consumer<T> consumer);
+
+  default void forEachWithIndex(IntObjConsumer<T> consumer) {
+    IntBox index = new IntBox();
+    forEach(element -> consumer.accept(index.getAndIncrement(), element));
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/utils/IntBox.java b/src/main/java/com/android/tools/r8/utils/IntBox.java
index ebb92ca..9a31ebd 100644
--- a/src/main/java/com/android/tools/r8/utils/IntBox.java
+++ b/src/main/java/com/android/tools/r8/utils/IntBox.java
@@ -14,19 +14,52 @@
     set(initialValue);
   }
 
+  public void decrement(int i) {
+    assert i > 0;
+    value -= i;
+  }
+
+  public int decrementAndGet(int i) {
+    decrement(i);
+    return get();
+  }
+
   public int get() {
     return value;
   }
 
   public int getAndIncrement() {
-    return value++;
+    return getAndIncrement(1);
+  }
+
+  public int getAndIncrement(int i) {
+    int previous = value;
+    increment(i);
+    return previous;
+  }
+
+  public int getAndSet(int value) {
+    int previous = this.value;
+    set(value);
+    return previous;
   }
 
   public void increment() {
-    value++;
+    increment(1);
+  }
+
+  public void increment(int i) {
+    assert i >= 0;
+    value += i;
   }
 
   public void set(int value) {
     this.value = value;
   }
+
+  public void setMax(int value) {
+    if (value > get()) {
+      set(value);
+    }
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/utils/IntConsumerUtils.java b/src/main/java/com/android/tools/r8/utils/IntConsumerUtils.java
new file mode 100644
index 0000000..c701cdd
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/utils/IntConsumerUtils.java
@@ -0,0 +1,14 @@
+// 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.utils;
+
+import java.util.function.IntConsumer;
+
+public class IntConsumerUtils {
+
+  public static IntConsumer emptyIntConsumer() {
+    return ignore -> {};
+  }
+}
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 afbafe0..d515b20 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -76,6 +76,7 @@
 import java.util.Comparator;
 import java.util.Deque;
 import java.util.HashMap;
+import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
@@ -486,7 +487,7 @@
   }
 
   public boolean shouldDesugarNests() {
-    return testing.enableForceNestBasedAccessDesugaringForTest || !canUseNestBasedAccess();
+    return !canUseNestBasedAccess();
   }
 
   public boolean canUseRecords() {
@@ -1323,6 +1324,7 @@
         System.getProperty("com.android.tools.r8.allowInvalidCfAccessFlags") != null;
     // TODO(b/177333791): Set to true
     public boolean checkForNotExpandingMainDexTracingResult = false;
+    public Set<String> allowedUnusedDontWarnPatterns = new HashSet<>();
 
     public boolean allowConflictingSyntheticTypes = false;
 
@@ -1333,8 +1335,6 @@
     // TODO(b/144781417): This is disabled by default as some test apps appear to have such classes.
     public boolean allowNonAbstractClassesWithAbstractMethods = true;
 
-    // Flag to turn on/off JDK11+ nest-access control even when not required (Cf backend)
-    public boolean enableForceNestBasedAccessDesugaringForTest = false;
     public boolean verifyKeptGraphInfo = false;
 
     public boolean readInputStackMaps = true;
@@ -1395,6 +1395,15 @@
   }
 
   /**
+   * Allow access modification of synthetic lambda implementation methods in D8 to avoid generating
+   * an excessive amount of accessibility bridges. In R8, the lambda implementation methods are
+   * inlined into the synthesized accessibility bridges, thus we don't allow access modification.
+   */
+  public boolean canAccessModifyLambdaImplementationMethods(AppView<?> appView) {
+    return !appView.enableWholeProgramOptimizations();
+  }
+
+  /**
    * Dex2Oat issues a warning for abstract methods on non-abstract classes, so we never allow this.
    *
    * <p>Note that having an invoke instruction that targets an abstract method on a non-abstract
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 a6f800b..10cd22a 100644
--- a/src/main/java/com/android/tools/r8/utils/ListUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/ListUtils.java
@@ -22,11 +22,12 @@
    * as the singleton list containing {@code v} (i.e., no changes should be made to the given
    * element).
    */
-  public static <T> List<T> flatMap(List<T> list, Function<T, List<T>> fn, List<T> defaultValue) {
+  public static <T> List<T> flatMap(
+      List<T> list, Function<T, Collection<T>> fn, List<T> defaultValue) {
     List<T> result = null;
     for (int i = 0; i < list.size(); i++) {
       T element = list.get(i);
-      List<T> replacement = fn.apply(element);
+      Collection<T> replacement = fn.apply(element);
       if (replacement == null) {
         if (result != null) {
           result.add(element);
diff --git a/src/main/java/com/android/tools/r8/utils/collections/BidirectionalManyToManyMap.java b/src/main/java/com/android/tools/r8/utils/collections/BidirectionalManyToManyMap.java
index 09b3c27..8f12b15 100644
--- a/src/main/java/com/android/tools/r8/utils/collections/BidirectionalManyToManyMap.java
+++ b/src/main/java/com/android/tools/r8/utils/collections/BidirectionalManyToManyMap.java
@@ -19,6 +19,8 @@
 
   void forEachKey(Consumer<? super K> consumer);
 
+  void forEachValue(Consumer<? super V> consumer);
+
   Set<K> getKeys(V value);
 
   Set<V> getValues(K key);
diff --git a/src/main/java/com/android/tools/r8/utils/collections/BidirectionalManyToOneHashMap.java b/src/main/java/com/android/tools/r8/utils/collections/BidirectionalManyToOneHashMap.java
index c217e91..8f29df9 100644
--- a/src/main/java/com/android/tools/r8/utils/collections/BidirectionalManyToOneHashMap.java
+++ b/src/main/java/com/android/tools/r8/utils/collections/BidirectionalManyToOneHashMap.java
@@ -58,6 +58,11 @@
   }
 
   @Override
+  public void forEachValue(Consumer<? super V> consumer) {
+    inverse.keySet().forEach(consumer);
+  }
+
+  @Override
   public V get(Object key) {
     return backing.get(key);
   }
diff --git a/src/main/java/com/android/tools/r8/utils/collections/BidirectionalOneToManyHashMap.java b/src/main/java/com/android/tools/r8/utils/collections/BidirectionalOneToManyHashMap.java
index 552a406..66b2375 100644
--- a/src/main/java/com/android/tools/r8/utils/collections/BidirectionalOneToManyHashMap.java
+++ b/src/main/java/com/android/tools/r8/utils/collections/BidirectionalOneToManyHashMap.java
@@ -58,6 +58,11 @@
   }
 
   @Override
+  public void forEachValue(Consumer<? super V> consumer) {
+    inverse.keySet().forEach(consumer);
+  }
+
+  @Override
   public Set<V> get(Object key) {
     return backing.get(key);
   }
diff --git a/src/main/java/com/android/tools/r8/utils/collections/BidirectionalOneToOneHashMap.java b/src/main/java/com/android/tools/r8/utils/collections/BidirectionalOneToOneHashMap.java
index 8e2edb5..830138a 100644
--- a/src/main/java/com/android/tools/r8/utils/collections/BidirectionalOneToOneHashMap.java
+++ b/src/main/java/com/android/tools/r8/utils/collections/BidirectionalOneToOneHashMap.java
@@ -61,6 +61,11 @@
   }
 
   @Override
+  public void forEachValue(Consumer<? super V> consumer) {
+    backing.values().forEach(consumer);
+  }
+
+  @Override
   public V get(Object key) {
     return backing.get(key);
   }
diff --git a/src/main/java/com/android/tools/r8/utils/collections/EmptyBidirectionalOneToOneMap.java b/src/main/java/com/android/tools/r8/utils/collections/EmptyBidirectionalOneToOneMap.java
index f2c10c3..f91f7df 100644
--- a/src/main/java/com/android/tools/r8/utils/collections/EmptyBidirectionalOneToOneMap.java
+++ b/src/main/java/com/android/tools/r8/utils/collections/EmptyBidirectionalOneToOneMap.java
@@ -42,6 +42,11 @@
   }
 
   @Override
+  public void forEachValue(Consumer<? super V> consumer) {
+    // Intentionally empty.
+  }
+
+  @Override
   public V get(Object key) {
     return null;
   }
diff --git a/src/test/examples/shaking1/print-mapping-cf.ref b/src/test/examples/shaking1/print-mapping-cf.ref
index 87a7bc0..a7e0b8e 100644
--- a/src/test/examples/shaking1/print-mapping-cf.ref
+++ b/src/test/examples/shaking1/print-mapping-cf.ref
@@ -3,3 +3,4 @@
     1:1:java.lang.String method():17:17 -> a
     1:1:void main(java.lang.String[]):8:8 -> main
     1:1:void <init>(java.lang.String):12:12 -> <init>
+    1:1:java.lang.String aMethodThatIsNotUsedButKept():21:21 -> aMethodThatIsNotUsedButKept
\ No newline at end of file
diff --git a/src/test/java/com/android/tools/r8/KotlinCompilerTool.java b/src/test/java/com/android/tools/r8/KotlinCompilerTool.java
index bee542f..8e94e5e 100644
--- a/src/test/java/com/android/tools/r8/KotlinCompilerTool.java
+++ b/src/test/java/com/android/tools/r8/KotlinCompilerTool.java
@@ -27,22 +27,37 @@
 
 public class KotlinCompilerTool {
 
+  public enum KotlinCompilerVersion {
+    KOTLINC_1_3_72("kotlin-compiler-1.3.72"),
+    KOTLINC_1_4_20("kotlin-compiler-1.4.20");
+
+    private final String folder;
+
+    KotlinCompilerVersion(String folder) {
+      this.folder = folder;
+    }
+  }
+
   public static final class KotlinCompiler {
 
     private final String name;
     private final Path lib;
     private final Path compiler;
+    private final KotlinCompilerVersion compilerVersion;
 
-    public KotlinCompiler(String name) {
-      this.name = name;
-      this.lib = Paths.get(ToolHelper.THIRD_PARTY_DIR, "kotlin", name, "kotlinc", "lib");
+    public KotlinCompiler(KotlinCompilerVersion compilerVersion) {
+      this.lib =
+          Paths.get(ToolHelper.THIRD_PARTY_DIR, "kotlin", compilerVersion.folder, "kotlinc", "lib");
       this.compiler = lib.resolve("kotlin-compiler.jar");
+      this.compilerVersion = compilerVersion;
+      this.name = compilerVersion.name();
     }
 
-    public KotlinCompiler(String name, Path compiler) {
-      this.name = name;
+    public KotlinCompiler(String name, Path compiler, KotlinCompilerVersion compilerVersion) {
       this.compiler = compiler;
       this.lib = null;
+      this.compilerVersion = compilerVersion;
+      this.name = name;
     }
 
     public Path getCompiler() {
@@ -53,6 +68,14 @@
       return lib;
     }
 
+    public boolean is(KotlinCompilerVersion version) {
+      return compilerVersion == version;
+    }
+
+    public KotlinCompilerVersion getCompilerVersion() {
+      return compilerVersion;
+    }
+
     @Override
     public String toString() {
       return name;
diff --git a/src/test/java/com/android/tools/r8/KotlinTestBase.java b/src/test/java/com/android/tools/r8/KotlinTestBase.java
index 38202a6..f018f74 100644
--- a/src/test/java/com/android/tools/r8/KotlinTestBase.java
+++ b/src/test/java/com/android/tools/r8/KotlinTestBase.java
@@ -3,6 +3,7 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8;
 
+import static com.android.tools.r8.DiagnosticsMatcher.diagnosticMessage;
 import static org.hamcrest.CoreMatchers.containsString;
 
 import com.android.tools.r8.KotlinCompilerTool.KotlinCompiler;
@@ -111,6 +112,22 @@
         sharedFolder, ignore -> new KotlinCompileMemoizer(sources));
   }
 
+  public ThrowableConsumer<R8TestCompileResult> assertUnusedKeepRuleForKotlinMetadata(
+      boolean condition) {
+    return compileResult -> {
+      if (!condition) {
+        return;
+      }
+      compileResult
+          .getDiagnosticMessages()
+          .assertInfoThatMatches(
+              diagnosticMessage(
+                  containsString(
+                      "Proguard configuration rule does not match anything: `-keep class"
+                          + " kotlin.Metadata")));
+    };
+  }
+
   public static class KotlinCompileMemoizer {
 
     private final Collection<Path> sources;
diff --git a/src/test/java/com/android/tools/r8/R8RunExamplesAndroidOTest.java b/src/test/java/com/android/tools/r8/R8RunExamplesAndroidOTest.java
index 389b0fa..e169df0 100644
--- a/src/test/java/com/android/tools/r8/R8RunExamplesAndroidOTest.java
+++ b/src/test/java/com/android/tools/r8/R8RunExamplesAndroidOTest.java
@@ -167,7 +167,7 @@
         .withBuilderTransformation(ToolHelper::allowTestProguardOptions)
         .withBuilderTransformation(
             b -> b.addProguardConfiguration(PROGUARD_OPTIONS_N_PLUS, Origin.unknown()))
-        .withDexCheck(inspector -> checkLambdaCount(inspector, 35, "lambdadesugaringnplus"))
+        .withDexCheck(inspector -> checkLambdaCount(inspector, 37, "lambdadesugaringnplus"))
         .run();
 
     test("lambdadesugaringnplus", "lambdadesugaringnplus", "LambdasWithStaticAndDefaultMethods")
@@ -176,7 +176,7 @@
         .withBuilderTransformation(ToolHelper::allowTestProguardOptions)
         .withBuilderTransformation(
             b -> b.addProguardConfiguration(PROGUARD_OPTIONS_N_PLUS, Origin.unknown()))
-        .withDexCheck(inspector -> checkLambdaCount(inspector, 3, "lambdadesugaringnplus"))
+        .withDexCheck(inspector -> checkLambdaCount(inspector, 6, "lambdadesugaringnplus"))
         .run();
   }
 
@@ -190,7 +190,7 @@
         .withBuilderTransformation(ToolHelper::allowTestProguardOptions)
         .withBuilderTransformation(
             b -> b.addProguardConfiguration(PROGUARD_OPTIONS_N_PLUS, Origin.unknown()))
-        .withDexCheck(inspector -> checkLambdaCount(inspector, 35, "lambdadesugaringnplus"))
+        .withDexCheck(inspector -> checkLambdaCount(inspector, 36, "lambdadesugaringnplus"))
         .run();
 
     test("lambdadesugaringnplus", "lambdadesugaringnplus", "LambdasWithStaticAndDefaultMethods")
diff --git a/src/test/java/com/android/tools/r8/R8TestBuilder.java b/src/test/java/com/android/tools/r8/R8TestBuilder.java
index 83313a3..e1d8658 100644
--- a/src/test/java/com/android/tools/r8/R8TestBuilder.java
+++ b/src/test/java/com/android/tools/r8/R8TestBuilder.java
@@ -309,6 +309,20 @@
     return self();
   }
 
+  public T allowUnusedDontWarnKotlinReflectJvmInternal() {
+    addOptionsModification(
+        options ->
+            options.testing.allowedUnusedDontWarnPatterns.add("kotlin.reflect.jvm.internal.**"));
+    return self();
+  }
+
+  public T allowUnusedDontWarnKotlinReflectJvmInternal(boolean condition) {
+    if (condition) {
+      allowUnusedDontWarnKotlinReflectJvmInternal();
+    }
+    return self();
+  }
+
   public T allowUnusedDontWarnPatterns() {
     return addOptionsModification(options -> options.testing.allowUnusedDontWarnRules = true);
   }
diff --git a/src/test/java/com/android/tools/r8/TestBase.java b/src/test/java/com/android/tools/r8/TestBase.java
index cbd2755..1b4ca35 100644
--- a/src/test/java/com/android/tools/r8/TestBase.java
+++ b/src/test/java/com/android/tools/r8/TestBase.java
@@ -46,6 +46,7 @@
 import com.android.tools.r8.references.TypeReference;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.shaking.EnqueuerFactory;
+import com.android.tools.r8.shaking.EnqueuerResult;
 import com.android.tools.r8.shaking.MainDexInfo;
 import com.android.tools.r8.shaking.NoHorizontalClassMergingRule;
 import com.android.tools.r8.shaking.NoVerticalClassMergingRule;
@@ -778,12 +779,12 @@
                 appView, subtypingInfo, appView.options().getProguardConfiguration().getRules())
             .build(executor);
     appView.setRootSet(rootSet);
-    AppInfoWithLiveness appInfoWithLiveness =
+    EnqueuerResult enqueuerResult =
         EnqueuerFactory.createForInitialTreeShaking(appView, executor, subtypingInfo)
             .traceApplication(rootSet, executor, Timing.empty());
     // We do not run the tree pruner to ensure that the hierarchy is as designed and not modified
     // due to liveness.
-    return appView.setAppInfo(appInfoWithLiveness);
+    return appView.setAppInfo(enqueuerResult.getAppInfo());
   }
 
   protected static DexType buildType(Class<?> clazz, DexItemFactory factory) {
diff --git a/src/test/java/com/android/tools/r8/TestRunResult.java b/src/test/java/com/android/tools/r8/TestRunResult.java
index 4a7d5c7..34cc0b0 100644
--- a/src/test/java/com/android/tools/r8/TestRunResult.java
+++ b/src/test/java/com/android/tools/r8/TestRunResult.java
@@ -3,6 +3,7 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8;
 
+import static com.android.tools.r8.utils.ConsumerUtils.emptyThrowingConsumer;
 import static org.hamcrest.CoreMatchers.containsString;
 import static org.hamcrest.CoreMatchers.is;
 
@@ -44,6 +45,22 @@
     return self();
   }
 
+  public <T extends Throwable> RR applyIf(boolean condition, ThrowingConsumer<RR, T> thenConsumer)
+      throws T {
+    return applyIf(condition, thenConsumer, emptyThrowingConsumer());
+  }
+
+  public <S extends Throwable, T extends Throwable> RR applyIf(
+      boolean condition, ThrowingConsumer<RR, S> thenConsumer, ThrowingConsumer<RR, T> elseConsumer)
+      throws S, T {
+    if (condition) {
+      thenConsumer.accept(self());
+    } else {
+      elseConsumer.accept(self());
+    }
+    return self();
+  }
+
   public <S> S map(Function<RR, S> fn) {
     return fn.apply(self());
   }
diff --git a/src/test/java/com/android/tools/r8/TestShrinkerBuilder.java b/src/test/java/com/android/tools/r8/TestShrinkerBuilder.java
index 816e207..c0cd551 100644
--- a/src/test/java/com/android/tools/r8/TestShrinkerBuilder.java
+++ b/src/test/java/com/android/tools/r8/TestShrinkerBuilder.java
@@ -4,7 +4,6 @@
 
 package com.android.tools.r8;
 
-import static com.android.tools.r8.ir.desugar.InterfaceMethodRewriter.COMPANION_CLASS_NAME_SUFFIX;
 
 import com.android.tools.r8.TestBase.Backend;
 import com.android.tools.r8.dexsplitter.SplitterTestBase.RunInterface;
@@ -106,8 +105,11 @@
     return addKeepRules(Arrays.asList(rules));
   }
 
-  public T addDontWarn(Class<?> clazz) {
-    return addDontWarn(clazz.getTypeName());
+  public T addDontWarn(Class<?>... classes) {
+    for (Class<?> clazz : classes) {
+      addDontWarn(clazz.getTypeName());
+    }
+    return self();
   }
 
   public T addDontWarn(String className) {
@@ -125,11 +127,6 @@
     return addDontWarn(Arrays.asList(classes));
   }
 
-  @Deprecated
-  public T addDontWarnCompanionClass(Class<?> clazz) {
-    return addDontWarn(clazz.getTypeName() + COMPANION_CLASS_NAME_SUFFIX);
-  }
-
   public T addDontWarnGoogle() {
     return addDontWarn("com.google.**");
   }
@@ -247,7 +244,11 @@
   }
 
   public T addKeepPackageNamesRule(Package pkg) {
-    return addKeepRules("-keeppackagenames " + pkg.getName());
+    return addKeepPackageNamesRule(pkg.getName());
+  }
+
+  public T addKeepPackageNamesRule(String packageName) {
+    return addKeepRules("-keeppackagenames " + packageName);
   }
 
   public T addKeepMainRule(Class<?> mainClass) {
diff --git a/src/test/java/com/android/tools/r8/ToolHelper.java b/src/test/java/com/android/tools/r8/ToolHelper.java
index c906281..6c395bf 100644
--- a/src/test/java/com/android/tools/r8/ToolHelper.java
+++ b/src/test/java/com/android/tools/r8/ToolHelper.java
@@ -3,6 +3,8 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8;
 
+import static com.android.tools.r8.KotlinCompilerTool.KotlinCompilerVersion.KOTLINC_1_3_72;
+import static com.android.tools.r8.KotlinCompilerTool.KotlinCompilerVersion.KOTLINC_1_4_20;
 import static com.android.tools.r8.utils.FileUtils.CLASS_EXTENSION;
 import static com.android.tools.r8.utils.FileUtils.isDexFile;
 import static org.junit.Assert.assertEquals;
@@ -135,7 +137,8 @@
   public static final String DEFAULT_PROGUARD_MAP_FILE = "proguard.map";
 
   public static final String JAVA_8_RUNTIME = "third_party/openjdk/openjdk-rt-1.8/rt.jar";
-  public static final String DESUGAR_JDK_LIBS = "third_party/openjdk/desugar_jdk_libs/libjava.jar";
+  public static final String DESUGAR_JDK_LIBS =
+      System.getProperty("desugar_jdk_libs", "third_party/openjdk/desugar_jdk_libs/libjava.jar");
   public static final String CORE_LAMBDA_STUBS =
       "third_party/core-lambda-stubs/core-lambda-stubs.jar";
   public static final String JSR223_RI_JAR = "third_party/jsr223-api-1.0/jsr223-api-1.0.jar";
@@ -2133,11 +2136,15 @@
   }
 
   public static KotlinCompiler getKotlinC_1_3_72() {
-    return new KotlinCompiler("kotlin-compiler-1.3.72");
+    return new KotlinCompiler(KOTLINC_1_3_72);
+  }
+
+  public static KotlinCompiler getKotlinC_1_4_20() {
+    return new KotlinCompiler(KOTLINC_1_4_20);
   }
 
   public static KotlinCompiler[] getKotlinCompilers() {
-    return new KotlinCompiler[] {getKotlinC_1_3_72()};
+    return new KotlinCompiler[] {getKotlinC_1_3_72(), getKotlinC_1_4_20()};
   }
 
   public static void disassemble(AndroidApp app, PrintStream ps)
diff --git a/src/test/java/com/android/tools/r8/annotations/SourceDebugExtensionTest.java b/src/test/java/com/android/tools/r8/annotations/SourceDebugExtensionTest.java
index 98421ae..d6a696c 100644
--- a/src/test/java/com/android/tools/r8/annotations/SourceDebugExtensionTest.java
+++ b/src/test/java/com/android/tools/r8/annotations/SourceDebugExtensionTest.java
@@ -7,11 +7,13 @@
 import static com.android.tools.r8.ToolHelper.getFilesInTestFolderRelativeToClass;
 import static com.android.tools.r8.ToolHelper.getKotlinCompilers;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.CoreMatchers.not;
 import static org.hamcrest.MatcherAssert.assertThat;
 
 import com.android.tools.r8.CompilationFailedException;
 import com.android.tools.r8.CompilationMode;
 import com.android.tools.r8.KotlinCompilerTool.KotlinCompiler;
+import com.android.tools.r8.KotlinCompilerTool.KotlinCompilerVersion;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestRuntime;
@@ -78,6 +80,11 @@
     assertThat(clazz, isPresent());
     AnnotationSubject sourceDebugExtensions =
         clazz.annotation("dalvik.annotation.SourceDebugExtension");
-    assertThat(sourceDebugExtensions, isPresent());
+    // TODO(b/179866574): This is somehow not present
+    if (kotlinCompiler.is(KotlinCompilerVersion.KOTLINC_1_4_20)) {
+      assertThat(sourceDebugExtensions, not(isPresent()));
+    } else {
+      assertThat(sourceDebugExtensions, isPresent());
+    }
   }
 }
diff --git a/src/test/java/com/android/tools/r8/cf/bootstrap/KotlinCompilerTreeShakingTest.java b/src/test/java/com/android/tools/r8/cf/bootstrap/KotlinCompilerTreeShakingTest.java
index 7aa4205..3dd6878 100644
--- a/src/test/java/com/android/tools/r8/cf/bootstrap/KotlinCompilerTreeShakingTest.java
+++ b/src/test/java/com/android/tools/r8/cf/bootstrap/KotlinCompilerTreeShakingTest.java
@@ -119,7 +119,8 @@
     Path classPathAfter =
         kotlinc(
                 parameters.getRuntime().asCf(),
-                new KotlinCompiler("r8ProcessedKotlinc", r8ProcessedKotlinc),
+                new KotlinCompiler(
+                    "r8ProcessedKotlinc", r8ProcessedKotlinc, kotlinCompiler.getCompilerVersion()),
                 KotlinTargetVersion.JAVA_8)
             .addSourceFiles(HELLO_KT)
             .setOutputPath(temp.newFolder().toPath())
diff --git a/src/test/java/com/android/tools/r8/d8/ThrowingConstStringTest.java b/src/test/java/com/android/tools/r8/d8/ThrowingConstStringTest.java
new file mode 100644
index 0000000..314d32d
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/d8/ThrowingConstStringTest.java
@@ -0,0 +1,73 @@
+// 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.d8;
+
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.codeinspector.InstructionSubject;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import java.nio.file.Path;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class ThrowingConstStringTest extends TestBase {
+
+  static final String EXPECTED = StringUtils.lines("Hello!");
+
+  private final TestParameters parameters;
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withDexRuntimes().withAllApiLevels().build();
+  }
+
+  public ThrowingConstStringTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void test() throws Exception {
+    Path cfout =
+        testForD8(Backend.CF)
+            .addInnerClasses(ThrowingConstStringTest.class)
+            .setMinApi(parameters.getApiLevel())
+            .addOptionsModification(o -> o.testing.forceIRForCfToCfDesugar = true)
+            .compile()
+            .inspect(
+                inspector -> {
+                  MethodSubject method = inspector.clazz(TestClass.class).mainMethod();
+                  InstructionSubject constString =
+                      method.iterateInstructions(InstructionSubject::isConstString).next();
+                  assertTrue(
+                      "ConstString is not covered by try range",
+                      method
+                          .streamTryCatches()
+                          .anyMatch(
+                              tryCatch ->
+                                  tryCatch.getRange().includes(constString.getOffset(method))));
+                })
+            .writeToZip();
+    testForD8(parameters.getBackend())
+        .addProgramFiles(cfout)
+        .setMinApi(parameters.getApiLevel())
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutput(EXPECTED);
+  }
+
+  static class TestClass {
+
+    public static void main(String[] args) {
+      synchronized (TestClass.class) {
+        String constant = "Hello!";
+        System.out.println(constant);
+      }
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/debug/DebugTestBase.java b/src/test/java/com/android/tools/r8/debug/DebugTestBase.java
index a5a1a8a..6dc126c 100644
--- a/src/test/java/com/android/tools/r8/debug/DebugTestBase.java
+++ b/src/test/java/com/android/tools/r8/debug/DebugTestBase.java
@@ -2188,7 +2188,7 @@
 
       private static boolean isLambdaMethod(VmMirror mirror, Location location) {
         String methodName = mirror.getMethodName(location.classID, location.methodID);
-        return methodName.startsWith("lambda$");
+        return methodName.startsWith("lambda$") || methodName.startsWith("$private$lambda$");
       }
     }
 
diff --git a/src/test/java/com/android/tools/r8/debug/LambdaTest.java b/src/test/java/com/android/tools/r8/debug/LambdaTest.java
index f9e6c34..749c4b4 100644
--- a/src/test/java/com/android/tools/r8/debug/LambdaTest.java
+++ b/src/test/java/com/android/tools/r8/debug/LambdaTest.java
@@ -84,7 +84,6 @@
         checkMethod(debuggeeClass, initialMethodName),
         checkLine(SOURCE_FILE, 20),
         stepInto(INTELLIJ_FILTER),
-        config.isCfRuntime() ? LambdaTest::doNothing : stepInto(INTELLIJ_FILTER),
         checkMethod(debuggeeClass, "returnOne"),
         checkLine(SOURCE_FILE, 28),
         checkNoLocal(),
diff --git a/src/test/java/com/android/tools/r8/desugar/DefaultLambdaWithSelfReferenceTestRunner.java b/src/test/java/com/android/tools/r8/desugar/DefaultLambdaWithSelfReferenceTestRunner.java
index 7a97972..477af87 100644
--- a/src/test/java/com/android/tools/r8/desugar/DefaultLambdaWithSelfReferenceTestRunner.java
+++ b/src/test/java/com/android/tools/r8/desugar/DefaultLambdaWithSelfReferenceTestRunner.java
@@ -51,7 +51,7 @@
     this.parameters = parameters;
   }
 
-  private void runDebugger(DebugTestConfig config) throws Throwable {
+  private void runDebugger(DebugTestConfig config, boolean isR8) throws Throwable {
     MethodReference main = Reference.methodFromMethod(CLASS.getMethod("main", String[].class));
     Command checkThisLambda =
         conditional(
@@ -83,7 +83,9 @@
         stepInto(INTELLIJ_FILTER),
         checkLine(17),
         // When desugaring, the LambdaClass will change this to a static (later moved to companion).
-        checkThisLambda,
+        parameters.canUseDefaultAndStaticInterfaceMethods() && isR8
+            ? checkThisDefaultMethod
+            : checkThisLambda,
         run());
   }
 
@@ -92,7 +94,7 @@
     assumeTrue(parameters.isCfRuntime());
     JvmTestBuilder builder = testForJvm().addTestClasspath();
     builder.run(parameters.getRuntime(), CLASS).assertSuccessWithOutput(EXPECTED);
-    runDebugger(builder.debugConfig());
+    runDebugger(builder.debugConfig(), false);
   }
 
   @Test
@@ -111,7 +113,7 @@
         .run(parameters.getRuntime(), CLASS)
         .assertSuccessWithOutput(EXPECTED)
         .inspect(inspector -> assertThat(inspector.clazz(CLASS), isPresent()));
-    runDebugger(compileResult.debugConfig());
+    runDebugger(compileResult.debugConfig(), true);
   }
 
   @Test
@@ -168,7 +170,7 @@
         .run(parameters.getRuntime(), CLASS)
         .assertSuccessWithOutput(EXPECTED);
 
-    runDebugger(compiledResult.debugConfig());
+    runDebugger(compiledResult.debugConfig(), false);
 
     Path dissasemble1 = temp.newFolder().toPath().resolve("disassemble1.txt");
     Path dissasemble2 = temp.newFolder().toPath().resolve("disassemble2.txt");
diff --git a/src/test/java/com/android/tools/r8/desugar/DesugarInstanceLambdaWithReadsTest.java b/src/test/java/com/android/tools/r8/desugar/DesugarInstanceLambdaWithReadsTest.java
index bf507de..de06aad 100644
--- a/src/test/java/com/android/tools/r8/desugar/DesugarInstanceLambdaWithReadsTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/DesugarInstanceLambdaWithReadsTest.java
@@ -3,12 +3,15 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.desugar;
 
+import static com.android.tools.r8.ir.desugar.LambdaClass.JAVAC_EXPECTED_LAMBDA_METHOD_PREFIX;
+import static com.android.tools.r8.ir.desugar.LambdaClass.R8_LAMBDA_ACCESSOR_METHOD_PREFIX;
 import static org.junit.Assert.assertEquals;
 
 import com.android.tools.r8.NeverInline;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.BooleanUtils;
 import com.android.tools.r8.utils.StringUtils;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.android.tools.r8.utils.codeinspector.FoundMethodSubject;
@@ -41,7 +44,7 @@
         .addProgramClasses(Main.class, A.class, B.class, Consumer.class)
         .run(parameters.getRuntime(), Main.class)
         .assertSuccessWithOutput(EXPECTED)
-        .inspect(this::checkJustOneLambdaImplementationMethod);
+        .inspect(inspector -> checkNumberOfLambdaMethods(inspector, false));
   }
 
   @Test
@@ -54,13 +57,27 @@
         .setMinApi(parameters.getApiLevel())
         .run(parameters.getRuntime(), Main.class)
         .assertSuccessWithOutput(EXPECTED)
-        .inspect(this::checkJustOneLambdaImplementationMethod);
+        .inspect(inspector -> checkNumberOfLambdaMethods(inspector, true));
   }
 
-  private void checkJustOneLambdaImplementationMethod(CodeInspector inspector) {
+  private void checkNumberOfLambdaMethods(CodeInspector inspector, boolean isR8) {
+    // When generating DEX, only R8 synthesizes an accessor for the javac-generated lambda$ method.
+    List<FoundMethodSubject> lambdaAccessorMethods =
+        inspector
+            .clazz(Main.class)
+            .allMethods(m -> m.getOriginalName().startsWith(R8_LAMBDA_ACCESSOR_METHOD_PREFIX));
+    assertEquals(
+        BooleanUtils.intValue(parameters.isDexRuntime() && isR8), lambdaAccessorMethods.size());
+
+    // When generating DEX, R8 will inline the javac-generated lambda$ method into the synthesized
+    // $r8$lambda$ accessor method.
     List<FoundMethodSubject> lambdaImplementationMethods =
-        inspector.clazz(Main.class).allMethods(m -> m.getOriginalName().startsWith("lambda$"));
-    assertEquals(1, lambdaImplementationMethods.size());
+        inspector
+            .clazz(Main.class)
+            .allMethods(m -> m.getOriginalName().startsWith(JAVAC_EXPECTED_LAMBDA_METHOD_PREFIX));
+    assertEquals(
+        1 - BooleanUtils.intValue(parameters.isDexRuntime() && isR8),
+        lambdaImplementationMethods.size());
   }
 
   private interface Consumer {
diff --git a/src/test/java/com/android/tools/r8/desugar/DesugarLambdaWithAnonymousClass.java b/src/test/java/com/android/tools/r8/desugar/DesugarLambdaWithAnonymousClass.java
index 5d84439..72549c5 100644
--- a/src/test/java/com/android/tools/r8/desugar/DesugarLambdaWithAnonymousClass.java
+++ b/src/test/java/com/android/tools/r8/desugar/DesugarLambdaWithAnonymousClass.java
@@ -32,7 +32,7 @@
   private List<String> EXPECTED_JAVAC_RESULT =
       ImmutableList.of("Hello from inside lambda$test$0", "Hello from inside lambda$testStatic$1");
 
-  private List<String> EXPECTED_DESUGARED_RESULT =
+  private List<String> EXPECTED_D8_DESUGARED_RESULT =
       ImmutableList.of(
           "Hello from inside lambda$test$0$DesugarLambdaWithAnonymousClass$TestClass",
           "Hello from inside lambda$testStatic$1");
@@ -106,7 +106,7 @@
             r -> r.assertSuccessWithOutputLines(EXPECTED_JAVAC_RESULT))
         .applyIf(
             DesugarTestConfiguration::isDesugared,
-            r -> r.assertSuccessWithOutputLines(EXPECTED_DESUGARED_RESULT));
+            r -> r.assertSuccessWithOutputLines(EXPECTED_D8_DESUGARED_RESULT));
   }
 
   @Test
@@ -124,8 +124,7 @@
           .addKeepMainRule(TestClass.class)
           .run(parameters.getRuntime(), TestClass.class)
           .inspect(this::checkEnclosingMethod)
-          .assertSuccessWithOutputLines(
-              parameters.isCfRuntime() ? EXPECTED_JAVAC_RESULT : EXPECTED_DESUGARED_RESULT);
+          .assertSuccessWithOutputLines(EXPECTED_JAVAC_RESULT);
       assertFalse(parameters.isDexRuntime());
     } catch (AssertionError e) {
       assertTrue(parameters.isDexRuntime());
diff --git a/src/test/java/com/android/tools/r8/desugar/DesugarLambdaWithLocalClass.java b/src/test/java/com/android/tools/r8/desugar/DesugarLambdaWithLocalClass.java
index f272f26..62c36fd 100644
--- a/src/test/java/com/android/tools/r8/desugar/DesugarLambdaWithLocalClass.java
+++ b/src/test/java/com/android/tools/r8/desugar/DesugarLambdaWithLocalClass.java
@@ -31,7 +31,7 @@
   private List<String> EXPECTED_JAVAC_RESULT =
       ImmutableList.of("Hello from inside lambda$test$0", "Hello from inside lambda$testStatic$1");
 
-  private List<String> EXPECTED_DESUGARED_RESULT =
+  private List<String> EXPECTED_D8_DESUGARED_RESULT =
       ImmutableList.of(
           "Hello from inside lambda$test$0$DesugarLambdaWithLocalClass$TestClass",
           "Hello from inside lambda$testStatic$1");
@@ -104,7 +104,7 @@
             r -> r.assertSuccessWithOutputLines(EXPECTED_JAVAC_RESULT))
         .applyIf(
             DesugarTestConfiguration::isDesugared,
-            r -> r.assertSuccessWithOutputLines(EXPECTED_DESUGARED_RESULT));
+            r -> r.assertSuccessWithOutputLines(EXPECTED_D8_DESUGARED_RESULT));
   }
 
   @Test
@@ -120,8 +120,7 @@
         .addKeepMainRule(TestClass.class)
         .run(parameters.getRuntime(), TestClass.class)
         .inspect(this::checkEnclosingMethod)
-        .assertSuccessWithOutputLines(
-            parameters.isCfRuntime() ? EXPECTED_JAVAC_RESULT : EXPECTED_JAVAC_RESULT);
+        .assertSuccessWithOutputLines(EXPECTED_JAVAC_RESULT);
   }
 
   public interface MyConsumer<T> {
diff --git a/src/test/java/com/android/tools/r8/desugar/LambdaMissingInterfaceTest.java b/src/test/java/com/android/tools/r8/desugar/LambdaMissingInterfaceTest.java
new file mode 100644
index 0000000..bdcfd8a
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/LambdaMissingInterfaceTest.java
@@ -0,0 +1,71 @@
+// 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.desugar;
+
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class LambdaMissingInterfaceTest extends TestBase {
+
+  private final TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withDexRuntimes().withAllApiLevels().build();
+  }
+
+  public LambdaMissingInterfaceTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    testForR8(parameters.getBackend())
+        .addProgramClasses(ClassWithLambda.class, Main.class)
+        .addKeepMainRule(Main.class)
+        .setMinApi(parameters.getApiLevel())
+        .addDontWarn(MissingInterface.class)
+        .enableInliningAnnotations()
+        .compile()
+        .addRunClasspathClasses(MissingInterface.class)
+        .run(parameters.getRuntime(), Main.class)
+        .assertFailureWithErrorThatThrows(AbstractMethodError.class);
+  }
+
+  interface MissingInterface {
+
+    void bar(int x);
+  }
+
+  public static class ClassWithLambda {
+
+    @NeverInline
+    public static void callWithLambda() {
+      Main.foo(System.out::println);
+    }
+  }
+
+  public static class Main {
+
+    private static int argCount;
+
+    @NeverInline
+    public static void foo(MissingInterface i) {
+      i.bar(argCount);
+    }
+
+    public static void main(String[] args) {
+      argCount = args.length;
+      ClassWithLambda.callWithLambda();
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/gson/ConcurrentHashMapFileSerializationTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/gson/ConcurrentHashMapFileSerializationTest.java
new file mode 100644
index 0000000..b38eac1
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/gson/ConcurrentHashMapFileSerializationTest.java
@@ -0,0 +1,124 @@
+// 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.desugar.desugaredlibrary.gson;
+
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.ToolHelper.DexVm.Version;
+import com.android.tools.r8.desugar.desugaredlibrary.DesugaredLibraryTestBase;
+import com.android.tools.r8.utils.BooleanUtils;
+import com.android.tools.r8.utils.StringUtils;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.util.List;
+import java.util.concurrent.ConcurrentHashMap;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class ConcurrentHashMapFileSerializationTest extends DesugaredLibraryTestBase {
+
+  private static final String EXPECTED_RESULT = StringUtils.lines("2", "2", "v1", "v1", "v2", "v2");
+
+  private final TestParameters parameters;
+  private final boolean shrinkDesugaredLibrary;
+
+  @Parameters(name = "{1}, shrinkDesugaredLibrary: {0}")
+  public static List<Object[]> data() {
+    // TODO(b/134732760): Skip Android 4.4.4 due to missing libjavacrypto.
+    return buildParameters(
+        BooleanUtils.values(),
+        getTestParameters()
+            .withDexRuntime(Version.V4_0_4)
+            .withDexRuntimesStartingFromIncluding(Version.V5_1_1)
+            .withAllApiLevels()
+            .build());
+  }
+
+  public ConcurrentHashMapFileSerializationTest(
+      boolean shrinkDesugaredLibrary, TestParameters parameters) {
+    this.shrinkDesugaredLibrary = shrinkDesugaredLibrary;
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testMapSerializationD8() throws Exception {
+    KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters);
+    testForD8()
+        .addInnerClasses(ConcurrentHashMapFileSerializationTest.class)
+        .setMinApi(parameters.getApiLevel())
+        .enableCoreLibraryDesugaring(parameters.getApiLevel(), keepRuleConsumer)
+        .compile()
+        .withArt6Plus64BitsLib()
+        .withArtFrameworks()
+        .addDesugaredCoreLibraryRunClassPath(
+            this::buildDesugaredLibrary,
+            parameters.getApiLevel(),
+            keepRuleConsumer.get(),
+            shrinkDesugaredLibrary)
+        .run(parameters.getRuntime(), Executor.class)
+        .assertSuccessWithOutput(EXPECTED_RESULT);
+  }
+
+  @Test
+  public void testMapSerializationR8() throws Exception {
+    KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters);
+    testForR8(Backend.DEX)
+        .addInnerClasses(ConcurrentHashMapFileSerializationTest.class)
+        .addKeepMainRule(Executor.class)
+        .noMinification()
+        .setMinApi(parameters.getApiLevel())
+        .enableCoreLibraryDesugaring(parameters.getApiLevel(), keepRuleConsumer)
+        .compile()
+        .withArt6Plus64BitsLib()
+        .withArtFrameworks()
+        .addDesugaredCoreLibraryRunClassPath(
+            this::buildDesugaredLibrary,
+            parameters.getApiLevel(),
+            keepRuleConsumer.get(),
+            shrinkDesugaredLibrary)
+        .run(parameters.getRuntime(), Executor.class)
+        .assertSuccessWithOutput(EXPECTED_RESULT);
+  }
+
+  @SuppressWarnings("MismatchedQueryAndUpdateOfCollection")
+  static class Executor {
+    public static void main(String[] args) throws Exception {
+      chmTest();
+    }
+
+    private static void chmTest() throws IOException, ClassNotFoundException {
+      ConcurrentHashMap<String, String> map = new ConcurrentHashMap<>();
+      map.put("k1", "v1");
+      map.put("k2", "v2");
+      File file = new File("testTemp");
+
+      FileOutputStream fos = new FileOutputStream(file);
+      ObjectOutputStream oos = new ObjectOutputStream(fos);
+      oos.writeObject(map);
+      oos.close();
+      fos.close();
+
+      FileInputStream fis = new FileInputStream(file);
+      ObjectInputStream ois = new ObjectInputStream(fis);
+      ConcurrentHashMap<String, String> newMap =
+          (ConcurrentHashMap<String, String>) ois.readObject();
+      fis.close();
+      ois.close();
+
+      System.out.println(map.size());
+      System.out.println(newMap.size());
+      System.out.println(map.get("k1"));
+      System.out.println(newMap.get("k1"));
+      System.out.println(map.get("k2"));
+      System.out.println(newMap.get("k2"));
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/gson/MyMapFileSerializationTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/gson/MyMapFileSerializationTest.java
new file mode 100644
index 0000000..623cfd0
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/gson/MyMapFileSerializationTest.java
@@ -0,0 +1,119 @@
+// 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.desugar.desugaredlibrary.gson;
+
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.ToolHelper.DexVm.Version;
+import com.android.tools.r8.desugar.desugaredlibrary.DesugaredLibraryTestBase;
+import com.android.tools.r8.utils.BooleanUtils;
+import com.android.tools.r8.utils.StringUtils;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.util.List;
+import java.util.concurrent.ConcurrentHashMap;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class MyMapFileSerializationTest extends DesugaredLibraryTestBase {
+
+  private static final String EXPECTED_RESULT = StringUtils.lines("2", "2", "v1", "v1", "v2", "v2");
+
+  private final TestParameters parameters;
+  private final boolean shrinkDesugaredLibrary;
+
+  @Parameters(name = "{1}, shrinkDesugaredLibrary: {0}")
+  public static List<Object[]> data() {
+    // TODO(b/134732760): Skip Android 4.4.4 due to missing libjavacrypto.
+    return buildParameters(
+        BooleanUtils.values(),
+        getTestParameters()
+            .withDexRuntime(Version.V4_0_4)
+            .withDexRuntimesStartingFromIncluding(Version.V5_1_1)
+            .withAllApiLevels()
+            .build());
+  }
+
+  public MyMapFileSerializationTest(boolean shrinkDesugaredLibrary, TestParameters parameters) {
+    this.shrinkDesugaredLibrary = shrinkDesugaredLibrary;
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testMapSerializationD8() throws Exception {
+    KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters);
+    testForD8()
+        .addInnerClasses(MyMapFileSerializationTest.class)
+        .setMinApi(parameters.getApiLevel())
+        .enableCoreLibraryDesugaring(parameters.getApiLevel(), keepRuleConsumer)
+        .compile()
+        .withArt6Plus64BitsLib()
+        .withArtFrameworks()
+        .addDesugaredCoreLibraryRunClassPath(
+            this::buildDesugaredLibrary,
+            parameters.getApiLevel(),
+            keepRuleConsumer.get(),
+            shrinkDesugaredLibrary)
+        .run(parameters.getRuntime(), Executor.class)
+        .assertSuccessWithOutput(EXPECTED_RESULT);
+  }
+
+  @Test
+  public void testMapSerializationR8() throws Exception {
+    KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters);
+    testForR8(Backend.DEX)
+        .addInnerClasses(MyMapFileSerializationTest.class)
+        .addKeepMainRule(Executor.class)
+        .noMinification()
+        .setMinApi(parameters.getApiLevel())
+        .enableCoreLibraryDesugaring(parameters.getApiLevel(), keepRuleConsumer)
+        .compile()
+        .withArt6Plus64BitsLib()
+        .withArtFrameworks()
+        .addDesugaredCoreLibraryRunClassPath(
+            this::buildDesugaredLibrary,
+            parameters.getApiLevel(),
+            keepRuleConsumer.get(),
+            shrinkDesugaredLibrary)
+        .run(parameters.getRuntime(), Executor.class)
+        .assertSuccessWithOutput(EXPECTED_RESULT);
+  }
+
+  @SuppressWarnings("MismatchedQueryAndUpdateOfCollection")
+  static class Executor {
+    public static void main(String[] args) throws Exception {
+      MyMap<String, String> map = new MyMap<>();
+      map.put("k1", "v1");
+      map.put("k2", "v2");
+      File file = new File("testTemp");
+
+      FileOutputStream fos = new FileOutputStream(file);
+      ObjectOutputStream oos = new ObjectOutputStream(fos);
+      oos.writeObject(map);
+      oos.close();
+      fos.close();
+
+      FileInputStream fis = new FileInputStream(file);
+      ObjectInputStream ois = new ObjectInputStream(fis);
+      MyMap<String, String> newMap = (MyMap<String, String>) ois.readObject();
+      fis.close();
+      ois.close();
+
+      System.out.println(map.size());
+      System.out.println(newMap.size());
+      System.out.println(map.get("k1"));
+      System.out.println(newMap.get("k1"));
+      System.out.println(map.get("k2"));
+      System.out.println(newMap.get("k2"));
+    }
+  }
+
+  static class MyMap<R, T> extends ConcurrentHashMap<R, T> {}
+}
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 4ac6ddf..2d3c243 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
@@ -4,10 +4,10 @@
 
 package com.android.tools.r8.desugar.desugaredlibrary.kotlin;
 
+import static com.android.tools.r8.KotlinCompilerTool.KotlinCompilerVersion.KOTLINC_1_4_20;
 import static com.android.tools.r8.KotlinTestBase.getCompileMemoizer;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
 import static org.hamcrest.CoreMatchers.containsString;
-import static org.hamcrest.CoreMatchers.equalTo;
 import static org.hamcrest.CoreMatchers.not;
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.junit.Assert.assertNotNull;
@@ -24,6 +24,7 @@
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.desugar.desugaredlibrary.DesugaredLibraryTestBase;
 import com.android.tools.r8.kotlin.KotlinMetadataWriter;
+import com.android.tools.r8.kotlin.metadata.KotlinMetadataTestBase;
 import com.android.tools.r8.shaking.ProguardKeepAttributes;
 import com.android.tools.r8.utils.BooleanUtils;
 import com.android.tools.r8.utils.DescriptorUtils;
@@ -129,17 +130,18 @@
             .addKeepAllClassesRule()
             .addKeepAttributes(ProguardKeepAttributes.RUNTIME_VISIBLE_ANNOTATIONS)
             .setMinApi(parameters.getApiLevel())
-            .allowDiagnosticWarningMessages();
+            .allowDiagnosticWarningMessages()
+            .allowUnusedDontWarnKotlinReflectJvmInternal(
+                kotlinParameters.getCompiler().is(KOTLINC_1_4_20));
     KeepRuleConsumer keepRuleConsumer = null;
     if (desugarLibrary) {
       keepRuleConsumer = createKeepRuleConsumer(parameters);
       testBuilder.enableCoreLibraryDesugaring(parameters.getApiLevel(), keepRuleConsumer);
     }
-    final R8TestCompileResult compileResult =
+    R8TestCompileResult compileResult =
         testBuilder
             .compile()
-            .assertAllWarningMessagesMatch(
-                equalTo("Resource 'META-INF/MANIFEST.MF' already exists."));
+            .apply(KotlinMetadataTestBase::verifyExpectedWarningsFromKotlinReflectAndStdLib);
     if (desugarLibrary) {
       assertNotNull(keepRuleConsumer);
       compileResult.addDesugaredCoreLibraryRunClassPath(
diff --git a/src/test/java/com/android/tools/r8/desugar/lambdas/ExplicitCallToJavacGeneratedInstanceLambdaMethodTest.java b/src/test/java/com/android/tools/r8/desugar/lambdas/ExplicitCallToJavacGeneratedInstanceLambdaMethodTest.java
new file mode 100644
index 0000000..721e41d
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/lambdas/ExplicitCallToJavacGeneratedInstanceLambdaMethodTest.java
@@ -0,0 +1,115 @@
+// 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.desugar.lambdas;
+
+import static com.android.tools.r8.ir.desugar.LambdaClass.JAVAC_EXPECTED_LAMBDA_METHOD_PREFIX;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.TestRuntime.CfVm;
+import com.android.tools.r8.cf.CfVersion;
+import java.io.IOException;
+import java.lang.reflect.Method;
+import java.util.stream.Stream;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.objectweb.asm.Opcodes;
+
+@RunWith(Parameterized.class)
+public class ExplicitCallToJavacGeneratedInstanceLambdaMethodTest extends TestBase {
+
+  private final TestParameters parameters;
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters()
+        .withCfRuntimesStartingFromIncluding(CfVm.JDK9)
+        .withDexRuntimes()
+        .withAllApiLevels()
+        .build();
+  }
+
+  public ExplicitCallToJavacGeneratedInstanceLambdaMethodTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testRuntime() throws Exception {
+    testForRuntime(parameters)
+        .addProgramClasses(Main.class, A.class, FunctionalInterface.class)
+        .addProgramClassFileData(getProgramClassFileData())
+        .run(parameters.getRuntime(), Main.class)
+        .applyIf(
+            parameters.isCfRuntime(),
+            result -> result.assertSuccessWithOutputLines("Hello world!", "Hello world!"),
+            result -> result.assertFailureWithErrorThatThrows(NoSuchMethodError.class));
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    testForR8(parameters.getBackend())
+        .addProgramClasses(Main.class, A.class, FunctionalInterface.class)
+        .addProgramClassFileData(getProgramClassFileData())
+        .addKeepMainRule(Main.class)
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines("Hello world!", "Hello world!");
+  }
+
+  private byte[] getProgramClassFileData() throws IOException {
+    Method lambdaMethod =
+        Stream.of(I.class.getDeclaredMethods())
+            .filter(x -> x.getName().contains(JAVAC_EXPECTED_LAMBDA_METHOD_PREFIX))
+            .findFirst()
+            .get();
+    return transformer(I.class)
+        .transformMethodInsnInMethod(
+            "test",
+            (opcode, owner, name, descriptor, isInterface, visitor) -> {
+              if (name.equals("lambdaMethod")) {
+                visitor.visitMethodInsn(
+                    Opcodes.INVOKESPECIAL, owner, lambdaMethod.getName(), descriptor, isInterface);
+              } else {
+                visitor.visitMethodInsn(opcode, owner, name, descriptor, isInterface);
+              }
+            })
+        .removeMethodsWithName("lambdaMethod")
+        .setVersion(CfVersion.V9)
+        .transform();
+  }
+
+  static class Main {
+
+    public static void main(String[] args) {
+      new A().test();
+    }
+  }
+
+  interface I {
+
+    default void test() {
+      FunctionalInterface f = () -> greet();
+      f.m();
+      lambdaMethod(); // Changed to lambda$test$0() by transformer.
+    }
+
+    default void greet() {
+      System.out.println("Hello world!");
+    }
+
+    // Removed by transformer.
+    default void lambdaMethod() {}
+  }
+
+  static class A implements I {}
+
+  interface FunctionalInterface {
+
+    void m();
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/desugar/lambdas/ExplicitCallToJavacGeneratedStaticLambdaMethodTest.java b/src/test/java/com/android/tools/r8/desugar/lambdas/ExplicitCallToJavacGeneratedStaticLambdaMethodTest.java
new file mode 100644
index 0000000..f57ef59
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/lambdas/ExplicitCallToJavacGeneratedStaticLambdaMethodTest.java
@@ -0,0 +1,107 @@
+// 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.desugar.lambdas;
+
+import static com.android.tools.r8.ir.desugar.LambdaClass.JAVAC_EXPECTED_LAMBDA_METHOD_PREFIX;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.TestRuntime.CfVm;
+import com.android.tools.r8.cf.CfVersion;
+import java.io.IOException;
+import java.lang.reflect.Method;
+import java.util.stream.Stream;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class ExplicitCallToJavacGeneratedStaticLambdaMethodTest extends TestBase {
+
+  private final TestParameters parameters;
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters()
+        .withCfRuntimesStartingFromIncluding(CfVm.JDK9)
+        .withDexRuntimes()
+        .withAllApiLevels()
+        .build();
+  }
+
+  public ExplicitCallToJavacGeneratedStaticLambdaMethodTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testRuntime() throws Exception {
+    testForRuntime(parameters)
+        .addProgramClasses(Main.class, A.class, FunctionalInterface.class)
+        .addProgramClassFileData(getProgramClassFileData())
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines("Hello world!", "Hello world!");
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    testForR8(parameters.getBackend())
+        .addProgramClasses(Main.class, A.class, FunctionalInterface.class)
+        .addProgramClassFileData(getProgramClassFileData())
+        .addKeepMainRule(Main.class)
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines("Hello world!", "Hello world!");
+  }
+
+  private byte[] getProgramClassFileData() throws IOException {
+    Method lambdaMethod =
+        Stream.of(I.class.getDeclaredMethods())
+            .filter(x -> x.getName().contains(JAVAC_EXPECTED_LAMBDA_METHOD_PREFIX))
+            .findFirst()
+            .get();
+    return transformer(I.class)
+        .transformMethodInsnInMethod(
+            "test",
+            (opcode, owner, name, descriptor, isInterface, visitor) -> {
+              if (name.equals("lambdaMethod")) {
+                visitor.visitMethodInsn(
+                    opcode, owner, lambdaMethod.getName(), descriptor, isInterface);
+              } else {
+                visitor.visitMethodInsn(opcode, owner, name, descriptor, isInterface);
+              }
+            })
+        .removeMethodsWithName("lambdaMethod")
+        .setVersion(CfVersion.V9)
+        .transform();
+  }
+
+  static class Main {
+
+    public static void main(String[] args) {
+      new A().test();
+    }
+  }
+
+  interface I {
+
+    default void test() {
+      FunctionalInterface f = () -> System.out.println("Hello world!");
+      f.m();
+      lambdaMethod(); // Changed to lambda$test$0() by transformer.
+    }
+
+    // Removed by transformer.
+    static void lambdaMethod() {}
+  }
+
+  static class A implements I {}
+
+  interface FunctionalInterface {
+
+    void m();
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/desugar/lambdas/LambdaPrivateInstanceInterfaceMethodWithNonLambdaCallSiteTest.java b/src/test/java/com/android/tools/r8/desugar/lambdas/LambdaPrivateInstanceInterfaceMethodWithNonLambdaCallSiteTest.java
new file mode 100644
index 0000000..7d0b2fa
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/lambdas/LambdaPrivateInstanceInterfaceMethodWithNonLambdaCallSiteTest.java
@@ -0,0 +1,114 @@
+// 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.desugar.lambdas;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.TestRuntime.CfVm;
+import com.android.tools.r8.cf.CfVersion;
+import java.io.IOException;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+import org.objectweb.asm.Handle;
+import org.objectweb.asm.Opcodes;
+
+@RunWith(Parameterized.class)
+public class LambdaPrivateInstanceInterfaceMethodWithNonLambdaCallSiteTest extends TestBase {
+
+  private final TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters()
+        .withCfRuntimesStartingFromIncluding(CfVm.JDK9)
+        .withDexRuntimes()
+        .withAllApiLevels()
+        .build();
+  }
+
+  public LambdaPrivateInstanceInterfaceMethodWithNonLambdaCallSiteTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testRuntime() throws Exception {
+    testForRuntime(parameters)
+        .addProgramClasses(Main.class, A.class, FunctionalInterface.class)
+        .addProgramClassFileData(getProgramClassFileData())
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines("Hello world!", "Hello world!");
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    testForR8(parameters.getBackend())
+        .addProgramClasses(Main.class, A.class, FunctionalInterface.class)
+        .addProgramClassFileData(getProgramClassFileData())
+        .addKeepMainRule(Main.class)
+        .setMinApi(parameters.getApiLevel())
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines("Hello world!", "Hello world!");
+  }
+
+  private byte[] getProgramClassFileData() throws IOException, NoSuchMethodException {
+    return transformer(I.class)
+        .transformInvokeDynamicInsnInMethod(
+            "test",
+            (name, descriptor, bootstrapMethodHandle, bootstrapMethodArguments, visitor) -> {
+              visitor.visitInvokeDynamicInsn(
+                  name,
+                  descriptor,
+                  bootstrapMethodHandle,
+                  bootstrapMethodArguments.get(0),
+                  new Handle(
+                      Opcodes.H_INVOKESPECIAL, binaryName(I.class), "privateMethod", "()V", true),
+                  bootstrapMethodArguments.get(2));
+            })
+        .transformMethodInsnInMethod(
+            "test",
+            (opcode, owner, name, descriptor, isInterface, visitor) -> {
+              if (name.equals("privateMethod")) {
+                assert opcode == Opcodes.INVOKEINTERFACE;
+                visitor.visitMethodInsn(
+                    Opcodes.INVOKESPECIAL, owner, name, descriptor, isInterface);
+              } else {
+                visitor.visitMethodInsn(opcode, owner, name, descriptor, isInterface);
+              }
+            })
+        .setPrivate(I.class.getDeclaredMethod("privateMethod"))
+        .setVersion(CfVersion.V9)
+        .transform();
+  }
+
+  static class Main {
+
+    public static void main(String[] args) {
+      new A().test();
+    }
+  }
+
+  interface I {
+
+    default void test() {
+      FunctionalInterface f = this::privateMethod;
+      f.m();
+      privateMethod();
+    }
+
+    default void privateMethod() {
+      System.out.println("Hello world!");
+    }
+  }
+
+  static class A implements I {}
+
+  interface FunctionalInterface {
+
+    void m();
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/Java11R8BootstrapTest.java b/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/Java11R8BootstrapTest.java
index 5a788bf..a756d95 100644
--- a/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/Java11R8BootstrapTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/Java11R8BootstrapTest.java
@@ -15,6 +15,7 @@
 import com.android.tools.r8.TestRuntime.CfVm;
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.cf.bootstrap.BootstrapCurrentEqualityTest;
+import com.android.tools.r8.utils.InternalOptions.DesugarState;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.google.common.collect.ImmutableList;
 import java.nio.file.Path;
@@ -66,8 +67,14 @@
     return testForR8(TestBase.getStaticTemp(), Backend.CF)
         .addProgramFiles(ToolHelper.R8_WITH_RELOCATED_DEPS_11_JAR)
         .addKeepRuleFiles(MAIN_KEEP)
-        .addOptionsModification(
-            options -> options.testing.enableForceNestBasedAccessDesugaringForTest = desugar)
+        .applyIf(
+            desugar,
+            builder ->
+                builder.addOptionsModification(
+                    options -> {
+                      options.desugarState = DesugarState.ON;
+                      options.cfToCfDesugar = true;
+                    }))
         .compile()
         .inspect(inspector -> assertNests(inspector, desugar))
         .writeToZip();
diff --git a/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/NestCompilationExceptionTest.java b/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/NestCompilationExceptionTest.java
index a9e05a3..78904fd 100644
--- a/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/NestCompilationExceptionTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/NestCompilationExceptionTest.java
@@ -25,10 +25,10 @@
 import com.android.tools.r8.TestRuntime.CfVm;
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.ToolHelper.DexVm;
+import com.android.tools.r8.diagnostic.internal.MissingDefinitionsDiagnosticImpl;
 import com.android.tools.r8.errors.IncompleteNestNestDesugarDiagnosic;
 import com.android.tools.r8.errors.InterfaceDesugarMissingTypeDiagnostic;
 import com.android.tools.r8.errors.MissingNestHostNestDesugarDiagnostic;
-import com.android.tools.r8.shaking.MissingClassesDiagnostic;
 import java.nio.file.Path;
 import java.util.List;
 import org.hamcrest.Matcher;
@@ -128,10 +128,10 @@
                 } else {
                   diagnostics
                       .assertOnlyErrors()
-                      .assertErrorsMatch(diagnosticType(MissingClassesDiagnostic.class));
+                      .assertErrorsMatch(diagnosticType(MissingDefinitionsDiagnosticImpl.class));
 
-                  MissingClassesDiagnostic diagnostic =
-                      (MissingClassesDiagnostic) diagnostics.getErrors().get(0);
+                  MissingDefinitionsDiagnosticImpl diagnostic =
+                      (MissingDefinitionsDiagnosticImpl) diagnostics.getErrors().get(0);
                   assertEquals(1, diagnostic.getMissingClasses().size());
                   assertEquals(
                       "nesthostexample.BasicNestHostWithInnerClassMethods",
@@ -167,10 +167,10 @@
                 } else {
                   diagnostics
                       .assertOnlyErrors()
-                      .assertErrorsMatch(diagnosticType(MissingClassesDiagnostic.class));
+                      .assertErrorsMatch(diagnosticType(MissingDefinitionsDiagnosticImpl.class));
 
-                  MissingClassesDiagnostic diagnostic =
-                      (MissingClassesDiagnostic) diagnostics.getErrors().get(0);
+                  MissingDefinitionsDiagnosticImpl diagnostic =
+                      (MissingDefinitionsDiagnosticImpl) diagnostics.getErrors().get(0);
                   assertEquals(1, diagnostic.getMissingClasses().size());
                   assertEquals(
                       "nesthostexample.BasicNestHostWithInnerClassMethods$BasicNestedClass",
@@ -201,15 +201,16 @@
           diagnostics -> {
             diagnostics.assertOnlyWarnings();
             if (parameters.isCfRuntime() || parameters.canUseDefaultAndStaticInterfaceMethods()) {
-              diagnostics.assertWarningsMatch(diagnosticType(MissingClassesDiagnostic.class));
+              diagnostics.assertWarningsMatch(
+                  diagnosticType(MissingDefinitionsDiagnosticImpl.class));
             } else {
               diagnostics.assertWarningsMatch(
-                  diagnosticType(MissingClassesDiagnostic.class),
+                  diagnosticType(MissingDefinitionsDiagnosticImpl.class),
                   diagnosticType(InterfaceDesugarMissingTypeDiagnostic.class));
             }
 
-            MissingClassesDiagnostic diagnostic =
-                (MissingClassesDiagnostic) diagnostics.getWarnings().get(0);
+            MissingDefinitionsDiagnosticImpl diagnostic =
+                (MissingDefinitionsDiagnosticImpl) diagnostics.getWarnings().get(0);
             assertEquals(1, diagnostic.getMissingClasses().size());
             assertEquals(
                 "nesthostexample.BasicNestHostWithInnerClassMethods",
@@ -234,15 +235,16 @@
           diagnostics -> {
             diagnostics.assertOnlyWarnings();
             if (parameters.isCfRuntime() || parameters.canUseDefaultAndStaticInterfaceMethods()) {
-              diagnostics.assertWarningsMatch(diagnosticType(MissingClassesDiagnostic.class));
+              diagnostics.assertWarningsMatch(
+                  diagnosticType(MissingDefinitionsDiagnosticImpl.class));
             } else {
               diagnostics.assertWarningsMatch(
-                  diagnosticType(MissingClassesDiagnostic.class),
+                  diagnosticType(MissingDefinitionsDiagnosticImpl.class),
                   diagnosticType(InterfaceDesugarMissingTypeDiagnostic.class));
             }
 
-            MissingClassesDiagnostic diagnostic =
-                (MissingClassesDiagnostic) diagnostics.getWarnings().get(0);
+            MissingDefinitionsDiagnosticImpl diagnostic =
+                (MissingDefinitionsDiagnosticImpl) diagnostics.getWarnings().get(0);
             assertNotNull(diagnostic);
             assertEquals(1, diagnostic.getMissingClasses().size());
             assertEquals(
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 ccd5d40..c68ec40 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
@@ -55,7 +55,7 @@
             .setMinApi(parameters.getApiLevel())
             .addKeepRules(RECORD_KEEP_RULE)
             .addKeepMainRule(MAIN_TYPE)
-            .apply(builder -> RecordTestUtils.setJdk15Library(builder, temp))
+            .addLibraryFiles(RecordTestUtils.getJdk15LibraryFiles(temp))
             .addOptionsModification(TestingOptions::allowExperimentClassFileVersion)
             .addOptionsModification(opt -> opt.testing.canUseRecords = true)
             .compile()
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 b2fb0f5..811d24a 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
@@ -55,7 +55,7 @@
             .setMinApi(parameters.getApiLevel())
             .addKeepRules(RECORD_KEEP_RULE)
             .addKeepMainRule(MAIN_TYPE)
-            .apply(builder -> RecordTestUtils.setJdk15Library(builder, temp))
+            .addLibraryFiles(RecordTestUtils.getJdk15LibraryFiles(temp))
             .addOptionsModification(TestingOptions::allowExperimentClassFileVersion)
             .addOptionsModification(opt -> opt.testing.canUseRecords = true)
             .compile()
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 02e75d3..fc7a247 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
@@ -68,7 +68,7 @@
             .setMinApi(parameters.getApiLevel())
             .addKeepRules(RECORD_KEEP_RULE)
             .addKeepMainRule(MAIN_TYPE)
-            .apply(builder -> RecordTestUtils.setJdk15Library(builder, temp))
+            .addLibraryFiles(RecordTestUtils.getJdk15LibraryFiles(temp))
             .addOptionsModification(TestingOptions::allowExperimentClassFileVersion)
             .addOptionsModification(opt -> opt.testing.canUseRecords = true)
             .compile()
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 d567625..eca975d 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
@@ -65,7 +65,7 @@
             .setMinApi(parameters.getApiLevel())
             .addKeepRules(RECORD_KEEP_RULE)
             .addKeepMainRule(MAIN_TYPE)
-            .apply(builder -> RecordTestUtils.setJdk15Library(builder, temp))
+            .addLibraryFiles(RecordTestUtils.getJdk15LibraryFiles(temp))
             .addOptionsModification(TestingOptions::allowExperimentClassFileVersion)
             .addOptionsModification(opt -> opt.testing.canUseRecords = true)
             .compile()
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 6e5cc6b..245607b 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
@@ -8,7 +8,6 @@
 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;
@@ -43,8 +42,7 @@
   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 {
+  public static Path[] getJdk15LibraryFiles(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.
@@ -57,7 +55,7 @@
             .addSourceFiles(Paths.get("src", "test", "javaStubs", "TypeDescriptor.java"))
             .addSourceFiles(Paths.get("src", "test", "javaStubs", "RecordComponent.java"))
             .compile();
-    builder.addLibraryFiles(ToolHelper.getJava8RuntimeJar()).addLibraryFiles(recordStubs);
+    return new Path[] {recordStubs, ToolHelper.getJava8RuntimeJar()};
   }
 
   public static byte[][] getProgramData(String mainClassSimpleName) {
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 e8ef3a4..6d97f29 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
@@ -57,7 +57,7 @@
             .setMinApi(parameters.getApiLevel())
             .addKeepRules(RECORD_KEEP_RULE)
             .addKeepMainRule(MAIN_TYPE)
-            .apply(builder -> RecordTestUtils.setJdk15Library(builder, temp))
+            .addLibraryFiles(RecordTestUtils.getJdk15LibraryFiles(temp))
             .addOptionsModification(TestingOptions::allowExperimentClassFileVersion)
             .addOptionsModification(opt -> opt.testing.canUseRecords = true)
             .compile()
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 39e503c..ff540d1 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
@@ -56,7 +56,7 @@
             .setMinApi(parameters.getApiLevel())
             .addKeepRules(RECORD_KEEP_RULE)
             .addKeepMainRule(MAIN_TYPE)
-            .apply(builder -> RecordTestUtils.setJdk15Library(builder, temp))
+            .addLibraryFiles(RecordTestUtils.getJdk15LibraryFiles(temp))
             .addOptionsModification(TestingOptions::allowExperimentClassFileVersion)
             .addOptionsModification(opt -> opt.testing.canUseRecords = true)
             .compile()
diff --git a/src/test/java/com/android/tools/r8/diagnostics/ModifyDiagnosticsLevelTest.java b/src/test/java/com/android/tools/r8/diagnostics/ModifyDiagnosticsLevelTest.java
index 4b1bb9e..9f20b7d 100644
--- a/src/test/java/com/android/tools/r8/diagnostics/ModifyDiagnosticsLevelTest.java
+++ b/src/test/java/com/android/tools/r8/diagnostics/ModifyDiagnosticsLevelTest.java
@@ -12,7 +12,7 @@
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
-import com.android.tools.r8.shaking.MissingClassesDiagnostic;
+import com.android.tools.r8.diagnostic.internal.MissingDefinitionsDiagnosticImpl;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
@@ -40,7 +40,7 @@
         .setDiagnosticsLevelModifier(
             (level, diagnostic) -> {
               if (level == DiagnosticsLevel.WARNING
-                  && diagnostic instanceof MissingClassesDiagnostic
+                  && diagnostic instanceof MissingDefinitionsDiagnosticImpl
                   && diagnostic.getDiagnosticMessage().startsWith(MISSING_CLASS_MESSAGE_PREFIX)) {
                 return DiagnosticsLevel.INFO;
               }
@@ -65,7 +65,7 @@
           .setDiagnosticsLevelModifier(
               (level, diagnostic) -> {
                 if (level == DiagnosticsLevel.WARNING
-                    && diagnostic instanceof MissingClassesDiagnostic
+                    && diagnostic instanceof MissingDefinitionsDiagnosticImpl
                     && diagnostic.getDiagnosticMessage().startsWith(MISSING_CLASS_MESSAGE_PREFIX)) {
                   return DiagnosticsLevel.ERROR;
                 }
diff --git a/src/test/java/com/android/tools/r8/graph/MissingClassThrowingTest.java b/src/test/java/com/android/tools/r8/graph/MissingClassThrowingTest.java
index ae244b5..2eb88a1 100644
--- a/src/test/java/com/android/tools/r8/graph/MissingClassThrowingTest.java
+++ b/src/test/java/com/android/tools/r8/graph/MissingClassThrowingTest.java
@@ -13,7 +13,7 @@
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.TestRuntime.CfVm;
-import com.android.tools.r8.shaking.MissingClassesDiagnostic;
+import com.android.tools.r8.diagnostic.internal.MissingDefinitionsDiagnosticImpl;
 import com.android.tools.r8.utils.InternalOptions.TestingOptions;
 import com.android.tools.r8.utils.codeinspector.AssertUtils;
 import java.io.IOException;
@@ -82,10 +82,11 @@
                     diagnostics -> {
                       diagnostics
                           .assertOnlyErrors()
-                          .assertErrorsMatch(diagnosticType(MissingClassesDiagnostic.class));
+                          .assertErrorsMatch(
+                              diagnosticType(MissingDefinitionsDiagnosticImpl.class));
 
-                      MissingClassesDiagnostic diagnostic =
-                          (MissingClassesDiagnostic) diagnostics.getErrors().get(0);
+                      MissingDefinitionsDiagnosticImpl diagnostic =
+                          (MissingDefinitionsDiagnosticImpl) diagnostics.getErrors().get(0);
                       assertEquals(1, diagnostic.getMissingClasses().size());
                       assertEquals(
                           MissingException.class.getTypeName(),
diff --git a/src/test/java/com/android/tools/r8/graph/genericsignature/GenericSignatureCorrectnessHelperTests.java b/src/test/java/com/android/tools/r8/graph/genericsignature/GenericSignatureCorrectnessHelperTests.java
new file mode 100644
index 0000000..db76bf9
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/graph/genericsignature/GenericSignatureCorrectnessHelperTests.java
@@ -0,0 +1,244 @@
+// 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.graph.genericsignature;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.GenericSignatureCorrectnessHelper;
+import com.android.tools.r8.graph.GenericSignatureCorrectnessHelper.SignatureEvaluationResult;
+import com.android.tools.r8.transformers.ClassFileTransformer.MethodPredicate;
+import com.android.tools.r8.utils.DescriptorUtils;
+import com.google.common.collect.ImmutableList;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class GenericSignatureCorrectnessHelperTests extends TestBase {
+
+  private final TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withNoneRuntime().build();
+  }
+
+  public GenericSignatureCorrectnessHelperTests(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testAllValid() throws Exception {
+    AppView<AppInfoWithClassHierarchy> appView =
+        computeAppViewWithClassHierachy(
+            buildInnerClasses(GenericSignatureCorrectnessHelperTests.class)
+                .addLibraryFile(ToolHelper.getJava8RuntimeJar())
+                .build());
+    GenericSignatureCorrectnessHelper check =
+        GenericSignatureCorrectnessHelper.createForVerification(appView);
+    check.run();
+  }
+
+  @Test
+  public void testMissingTypeArgumentInClassBound() throws Exception {
+    runTest(
+        ImmutableList.of(Base.class),
+        ImmutableList.of(
+            transformer(ClassWithClassBound.class)
+                .setGenericSignature(
+                    existing -> {
+                      // Replace the generic type parameter T with R.
+                      return existing.replace("<T:", "<R:");
+                    })
+                .transform()),
+        ClassWithClassBound.class,
+        SignatureEvaluationResult.INVALID_TYPE_VARIABLE_UNDEFINED);
+  }
+
+  @Test
+  public void testMissingTypeArgumentInInterfaceBound() throws Exception {
+    runTest(
+        ImmutableList.of(I.class, J.class),
+        ImmutableList.of(
+            transformer(ClassWithInterfaceBound.class)
+                .setGenericSignature(
+                    existing -> {
+                      // Replace the generic type parameter T with R.
+                      return existing.replace("<T:", "<R:");
+                    })
+                .transform()),
+        ClassWithInterfaceBound.class,
+        SignatureEvaluationResult.INVALID_TYPE_VARIABLE_UNDEFINED);
+  }
+
+  @Test
+  public void testMembersHavingInvalidTypeReference() throws Exception {
+    runTest(
+        ImmutableList.of(),
+        ImmutableList.of(
+            transformer(ClassWithMembersHavingInvalidTypeReference.class)
+                .setGenericSignature(
+                    existing -> {
+                      // Replace the generic type parameter T with R.
+                      return existing.replace("<T:", "<R:");
+                    })
+                .transform()),
+        ClassWithMembersHavingInvalidTypeReference.class,
+        SignatureEvaluationResult.INVALID_TYPE_VARIABLE_UNDEFINED);
+  }
+
+  @Test
+  public void testMethodHavingInvalidTypeReferences() throws Exception {
+    runTest(
+        ImmutableList.of(),
+        ImmutableList.of(
+            transformer(ClassWithMethodMissingTypeParameters.class)
+                .setGenericSignature(
+                    MethodPredicate.onName("test"),
+                    existing -> {
+                      // Replace the generic type parameter T with R.
+                      return existing.replace("<T:", "<R:");
+                    })
+                .transform()),
+        ClassWithMethodMissingTypeParameters.class,
+        SignatureEvaluationResult.INVALID_TYPE_VARIABLE_UNDEFINED);
+  }
+
+  @Test
+  public void testIncorrectNumberOfSuperInterfaces() throws Exception {
+    runTest(
+        ImmutableList.of(),
+        ImmutableList.of(
+            transformer(ClassWithInvalidNumberOfSuperInterfaces.class)
+                .setImplements(I.class)
+                .transform()),
+        ClassWithInvalidNumberOfSuperInterfaces.class,
+        SignatureEvaluationResult.INVALID_INTERFACE_COUNT);
+  }
+
+  @Test
+  public void testMissingArgument() throws Exception {
+    runTest(
+        ImmutableList.of(J.class),
+        ImmutableList.of(
+            transformer(ClassWithInvalidArgumentCount.class)
+                .setGenericSignature(
+                    existing -> {
+                      // Replace the generic type argument <TT;> with nothing
+                      return existing.replace("<TT;>", "");
+                    })
+                .transform()),
+        ClassWithInvalidArgumentCount.class,
+        SignatureEvaluationResult.INVALID_APPLICATION_COUNT);
+  }
+
+  @Test
+  public void testTooManyArguments() throws Exception {
+    runTest(
+        ImmutableList.of(J.class),
+        ImmutableList.of(
+            transformer(ClassWithInvalidArgumentCount.class)
+                .setGenericSignature(
+                    existing -> {
+                      // Replace the generic type argument <TT;> with nothing
+                      return existing.replace("<TT;>", "<TT;TT;>");
+                    })
+                .transform()),
+        ClassWithInvalidArgumentCount.class,
+        SignatureEvaluationResult.INVALID_APPLICATION_COUNT);
+  }
+
+  @Test
+  public void testClassWithInvalidSuperType() throws Exception {
+    runTest(
+        ImmutableList.of(Base.class, OtherBase.class),
+        ImmutableList.of(
+            transformer(ClassWithInvalidSuperType.class)
+                .setSuper(DescriptorUtils.javaTypeToDescriptor(OtherBase.class.getTypeName()))
+                .transform()),
+        ClassWithInvalidSuperType.class,
+        SignatureEvaluationResult.INVALID_SUPER_TYPE);
+  }
+
+  private void runTest(
+      List<Class<?>> classes,
+      List<byte[]> transformations,
+      Class<?> classToVerify,
+      SignatureEvaluationResult expected)
+      throws Exception {
+    AppView<AppInfoWithClassHierarchy> appView =
+        computeAppViewWithClassHierachy(
+            buildClasses(classes)
+                .addClassProgramData(transformations)
+                .addLibraryFile(ToolHelper.getJava8RuntimeJar())
+                .build());
+    GenericSignatureCorrectnessHelper check =
+        GenericSignatureCorrectnessHelper.createForInitialCheck(appView);
+    DexProgramClass clazz =
+        appView
+            .definitionFor(
+                appView
+                    .dexItemFactory()
+                    .createType(DescriptorUtils.javaTypeToDescriptor(classToVerify.getTypeName())))
+            .asProgramClass();
+    assertNotNull(clazz);
+    assertEquals(expected, check.evaluateSignaturesForClass(clazz));
+  }
+
+  public interface I {}
+
+  public interface J<T> {
+    <R extends Object & I & J<Integer>> R foo(T foo) throws CustomException;
+  }
+
+  public static class Base<T> {}
+
+  public static class CustomException extends Exception {}
+
+  public static class Empty {}
+
+  public static class ClassWithClassBound<T extends Base<T /* R */>> {}
+
+  public static class ClassWithInterfaceBound<T extends I & J<T /* R */>> {}
+
+  public abstract static class ClassWithMembersHavingInvalidTypeReference<T /* R */> {
+
+    T t;
+
+    public abstract T testReturn();
+
+    public abstract void testParameter(T t);
+  }
+
+  public abstract static class ClassOverridingTypeArgument<T> {
+
+    public abstract <T> T test();
+  }
+
+  public abstract static class ClassWithMethodMissingTypeParameters {
+
+    public abstract <T /* R */> T test(T foo);
+  }
+
+  public abstract static class ClassWithInvalidNumberOfSuperInterfaces<T>
+      implements I, J<T> /* I */ {}
+
+  public abstract static class ClassWithInvalidArgumentCount<T>
+      implements J<T> /* J and J<T,T> */ {}
+
+  public static class OtherBase<T> {}
+
+  public abstract static class ClassWithInvalidSuperType<T> extends Base<T> /* OtherBase<T> */ {}
+}
diff --git a/src/test/java/com/android/tools/r8/ir/InlineTest.java b/src/test/java/com/android/tools/r8/ir/InlineTest.java
index 73f1561..c141654 100644
--- a/src/test/java/com/android/tools/r8/ir/InlineTest.java
+++ b/src/test/java/com/android/tools/r8/ir/InlineTest.java
@@ -21,6 +21,7 @@
 import com.android.tools.r8.ir.code.InstructionListIterator;
 import com.android.tools.r8.shaking.Enqueuer;
 import com.android.tools.r8.shaking.EnqueuerFactory;
+import com.android.tools.r8.shaking.EnqueuerResult;
 import com.android.tools.r8.shaking.ProguardConfiguration;
 import com.android.tools.r8.shaking.ProguardKeepRule;
 import com.android.tools.r8.shaking.RootSetUtils.RootSet;
@@ -76,7 +77,9 @@
     Timing timing = Timing.empty();
     Enqueuer enqueuer =
         EnqueuerFactory.createForInitialTreeShaking(appView, executorService, subtypingInfo);
-    appView.setAppInfo(enqueuer.traceApplication(appView.rootSet(), executorService, timing));
+    EnqueuerResult enqueuerResult =
+        enqueuer.traceApplication(appView.rootSet(), executorService, timing);
+    appView.setAppInfo(enqueuerResult.getAppInfo());
     return new TestApplication(appView, method, additionalCode);
   }
 
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/templates/CfUtilityMethodsForCodeOptimizationsTemplates.java b/src/test/java/com/android/tools/r8/ir/optimize/templates/CfUtilityMethodsForCodeOptimizationsTemplates.java
index c1b1f8b..b8ad8d9 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/templates/CfUtilityMethodsForCodeOptimizationsTemplates.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/templates/CfUtilityMethodsForCodeOptimizationsTemplates.java
@@ -18,6 +18,10 @@
     }
   }
 
+  public static IllegalAccessError throwIllegalAccessError() {
+    throw new IllegalAccessError();
+  }
+
   public static IncompatibleClassChangeError throwIncompatibleClassChangeError() {
     throw new IncompatibleClassChangeError();
   }
diff --git a/src/test/java/com/android/tools/r8/kotlin/AbstractR8KotlinTestBase.java b/src/test/java/com/android/tools/r8/kotlin/AbstractR8KotlinTestBase.java
index 0fb02d7..33848bb 100644
--- a/src/test/java/com/android/tools/r8/kotlin/AbstractR8KotlinTestBase.java
+++ b/src/test/java/com/android/tools/r8/kotlin/AbstractR8KotlinTestBase.java
@@ -18,6 +18,7 @@
 import com.android.tools.r8.KotlinTestParameters;
 import com.android.tools.r8.R8FullTestBuilder;
 import com.android.tools.r8.R8TestRunResult;
+import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.ThrowableConsumer;
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.graph.Code;
@@ -49,14 +50,20 @@
   private final List<Path> classpath = new ArrayList<>();
   private final List<Path> extraClasspath = new ArrayList<>();
 
+  protected final TestParameters testParameters;
+
   // Some tests defined in subclasses, e.g., Metadata tests, don't care about access relaxation.
-  protected AbstractR8KotlinTestBase(KotlinTestParameters kotlinParameters) {
-    this(kotlinParameters, false);
+  protected AbstractR8KotlinTestBase(
+      TestParameters parameters, KotlinTestParameters kotlinParameters) {
+    this(parameters, kotlinParameters, false);
   }
 
   protected AbstractR8KotlinTestBase(
-      KotlinTestParameters kotlinParameters, boolean allowAccessModification) {
+      TestParameters parameters,
+      KotlinTestParameters kotlinParameters,
+      boolean allowAccessModification) {
     super(kotlinParameters);
+    this.testParameters = parameters;
     this.allowAccessModification = allowAccessModification;
   }
 
@@ -237,18 +244,19 @@
     }
 
     // Build with R8
-    return testForR8(Backend.DEX)
+    return testForR8(testParameters.getBackend())
         .addProgramFiles(classpath)
         .addKeepMainRule(mainClass)
         .allowAccessModification(allowAccessModification)
         .allowDiagnosticWarningMessages()
         .enableProguardTestOptions()
         .noMinification()
+        .setMinApi(testParameters.getApiLevel())
         .apply(configuration)
         .compile()
         .assertAllWarningMessagesMatch(
             containsString("Resource 'META-INF/MANIFEST.MF' already exists."))
-        .run(mainClass)
+        .run(testParameters.getRuntime(), mainClass)
         .assertSuccessWithOutput(javaResult.stdout);
   }
 
diff --git a/src/test/java/com/android/tools/r8/kotlin/KotlinClassInlinerTest.java b/src/test/java/com/android/tools/r8/kotlin/KotlinClassInlinerTest.java
index 608b92b..3b554fd 100644
--- a/src/test/java/com/android/tools/r8/kotlin/KotlinClassInlinerTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/KotlinClassInlinerTest.java
@@ -4,13 +4,15 @@
 
 package com.android.tools.r8.kotlin;
 
+import static com.android.tools.r8.KotlinCompilerTool.KotlinCompilerVersion.KOTLINC_1_3_72;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
+import static org.junit.Assume.assumeTrue;
 
 import com.android.tools.r8.KotlinTestParameters;
-import com.android.tools.r8.KotlinTestParametersCollection;
+import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestShrinkerBuilder;
 import com.android.tools.r8.code.NewInstance;
 import com.android.tools.r8.code.SgetObject;
@@ -37,13 +39,15 @@
 @RunWith(Parameterized.class)
 public class KotlinClassInlinerTest extends AbstractR8KotlinTestBase {
 
-  @Parameterized.Parameters(name = "{0}")
-  public static KotlinTestParametersCollection data() {
-    return getKotlinTestParameters().withAllCompilersAndTargetVersions().build();
+  @Parameterized.Parameters(name = "{0}, {1}")
+  public static List<Object[]> data() {
+    return buildParameters(
+        getTestParameters().withAllRuntimesAndApiLevels().build(),
+        getKotlinTestParameters().withAllCompilersAndTargetVersions().build());
   }
 
-  public KotlinClassInlinerTest(KotlinTestParameters kotlinParameters) {
-    super(kotlinParameters, true);
+  public KotlinClassInlinerTest(TestParameters parameters, KotlinTestParameters kotlinParameters) {
+    super(parameters, kotlinParameters, true);
   }
 
   private static boolean isLambda(DexClass clazz) {
@@ -203,6 +207,8 @@
 
   @Test
   public void testDataClass() throws Exception {
+    // TODO(b/179866251): Update tests.
+    assumeTrue(kotlinc.is(KOTLINC_1_3_72) && testParameters.isDexRuntime());
     String mainClassName = "class_inliner_data_class.MainKt";
     runTest(
             "class_inliner_data_class",
@@ -234,6 +240,7 @@
       String... params) {
     assertNotNull(clazz);
     MethodSignature signature = new MethodSignature(methodName, "void", params);
+    // TODO(b/179866251): Allow for CF code here.
     DexCode code = clazz.method(signature).getMethod().getCode().asDexCode();
     return Stream.concat(
         filterInstructionKind(code, NewInstance.class)
diff --git a/src/test/java/com/android/tools/r8/kotlin/KotlinClassStaticizerTest.java b/src/test/java/com/android/tools/r8/kotlin/KotlinClassStaticizerTest.java
index 1598bb8..ecf67ae 100644
--- a/src/test/java/com/android/tools/r8/kotlin/KotlinClassStaticizerTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/KotlinClassStaticizerTest.java
@@ -12,6 +12,7 @@
 
 import com.android.tools.r8.KotlinTestParameters;
 import com.android.tools.r8.R8TestRunResult;
+import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.utils.BooleanUtils;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.FoundMethodSubject;
@@ -24,16 +25,19 @@
 @RunWith(Parameterized.class)
 public class KotlinClassStaticizerTest extends AbstractR8KotlinTestBase {
 
-  @Parameterized.Parameters(name = "{0}, allowAccessModification: {1}")
+  @Parameterized.Parameters(name = "{0}, {1}, allowAccessModification: {2}")
   public static Collection<Object[]> data() {
     return buildParameters(
+        getTestParameters().withAllRuntimesAndApiLevels().build(),
         getKotlinTestParameters().withAllCompilersAndTargetVersions().build(),
         BooleanUtils.values());
   }
 
   public KotlinClassStaticizerTest(
-      KotlinTestParameters kotlinParameters, boolean allowAccessModification) {
-    super(kotlinParameters, allowAccessModification);
+      TestParameters parameters,
+      KotlinTestParameters kotlinParameters,
+      boolean allowAccessModification) {
+    super(parameters, kotlinParameters, allowAccessModification);
   }
 
   @Test
@@ -64,7 +68,10 @@
 
               ClassSubject utilClass = inspector.clazz("class_staticizer.Util");
               assertThat(utilClass, isPresent());
-              assertTrue(utilClass.allMethods().stream().allMatch(FoundMethodSubject::isStatic));
+              // TODO(b/179951488): The <init> is not removed in CF
+              if (testParameters.isDexRuntime()) {
+                assertTrue(utilClass.allMethods().stream().allMatch(FoundMethodSubject::isStatic));
+              }
             });
   }
 
diff --git a/src/test/java/com/android/tools/r8/kotlin/KotlinDuplicateAnnotationTest.java b/src/test/java/com/android/tools/r8/kotlin/KotlinDuplicateAnnotationTest.java
index e74d082..b7b1f1b 100644
--- a/src/test/java/com/android/tools/r8/kotlin/KotlinDuplicateAnnotationTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/KotlinDuplicateAnnotationTest.java
@@ -9,6 +9,7 @@
 import static org.junit.Assume.assumeTrue;
 
 import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.KotlinCompilerTool.KotlinCompilerVersion;
 import com.android.tools.r8.KotlinTestParameters;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.utils.BooleanUtils;
@@ -22,6 +23,7 @@
 
 @RunWith(Parameterized.class)
 public class KotlinDuplicateAnnotationTest extends AbstractR8KotlinTestBase {
+
   private static final String FOLDER = "duplicate_annotation";
   private static final String MAIN = FOLDER + ".MainKt";
   private static final String KEEP_RULES = StringUtils.lines(
@@ -37,7 +39,7 @@
   @Parameterized.Parameters(name = "{0}, {1}, allowAccessModification: {2}")
   public static Collection<Object[]> data() {
     return buildParameters(
-        getTestParameters().withAllRuntimes().build(),
+        getTestParameters().withAllRuntimesAndApiLevels().build(),
         getKotlinTestParameters().withAllCompilersAndTargetVersions().build(),
         BooleanUtils.values());
   }
@@ -48,7 +50,7 @@
       TestParameters parameters,
       KotlinTestParameters kotlinParameters,
       boolean allowAccessModification) {
-    super(kotlinParameters, allowAccessModification);
+    super(parameters, kotlinParameters, allowAccessModification);
     this.parameters = parameters;
   }
 
@@ -58,6 +60,8 @@
 
   @Test
   public void test_dex() {
+    // TODO(b/179860027): Make it run on all tests.
+    assumeTrue(kotlinc.is(KotlinCompilerVersion.KOTLINC_1_3_72));
     assumeTrue("test DEX", parameters.isDexRuntime());
     try {
       testForR8(parameters.getBackend())
@@ -76,6 +80,8 @@
 
   @Test
   public void test_cf() throws Exception {
+    // TODO(b/179860027): Make it run on all tests.
+    assumeTrue(kotlinc.is(KotlinCompilerVersion.KOTLINC_1_3_72));
     assumeTrue("test CF", parameters.isCfRuntime());
     testForR8(parameters.getBackend())
         .addProgramFiles(compiledJars.getForConfiguration(kotlinc, targetVersion))
diff --git a/src/test/java/com/android/tools/r8/kotlin/KotlinIntrinsicsInlineTest.java b/src/test/java/com/android/tools/r8/kotlin/KotlinIntrinsicsInlineTest.java
index dae8a7d..068ac79 100644
--- a/src/test/java/com/android/tools/r8/kotlin/KotlinIntrinsicsInlineTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/KotlinIntrinsicsInlineTest.java
@@ -3,11 +3,13 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.kotlin;
 
+import static com.android.tools.r8.KotlinCompilerTool.KotlinCompilerVersion.KOTLINC_1_3_72;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assume.assumeTrue;
 
+import com.android.tools.r8.KotlinTestBase;
 import com.android.tools.r8.KotlinTestParameters;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.utils.BooleanUtils;
@@ -20,26 +22,28 @@
 import org.junit.runners.Parameterized;
 
 @RunWith(Parameterized.class)
-public class KotlinIntrinsicsInlineTest extends AbstractR8KotlinTestBase {
+public class KotlinIntrinsicsInlineTest extends KotlinTestBase {
   private static final String FOLDER = "intrinsics";
   private static final String MAIN = FOLDER + ".InlineKt";
 
   @Parameterized.Parameters(name = "{0}, {1}, allowAccessModification: {2}")
   public static Collection<Object[]> data() {
     return buildParameters(
-        getTestParameters().withAllRuntimes().build(),
+        getTestParameters().withAllRuntimesAndApiLevels().build(),
         getKotlinTestParameters().withAllCompilersAndTargetVersions().build(),
         BooleanUtils.values());
   }
 
   private final TestParameters parameters;
+  private final boolean allowAccessModification;
 
   public KotlinIntrinsicsInlineTest(
       TestParameters parameters,
       KotlinTestParameters kotlinParameters,
       boolean allowAccessModification) {
-    super(kotlinParameters, allowAccessModification);
+    super(kotlinParameters);
     this.parameters = parameters;
+    this.allowAccessModification = allowAccessModification;
   }
 
   private static final KotlinCompileMemoizer compiledJars =
@@ -48,6 +52,8 @@
 
   @Test
   public void b139432507() throws Exception {
+    // TODO(b/179866251): Update tests.
+    assumeTrue(kotlinc.is(KOTLINC_1_3_72) || allowAccessModification);
     testForR8(parameters.getBackend())
         .addProgramFiles(compiledJars.getForConfiguration(kotlinc, targetVersion))
         .addKeepRules(
@@ -81,12 +87,16 @@
 
   @Test
   public void b139432507_isSupported() throws Exception {
+    // TODO(b/179866251): Update tests.
+    assumeTrue(kotlinc.is(KOTLINC_1_3_72) || allowAccessModification);
     assumeTrue("Different inlining behavior on CF backend", parameters.isDexRuntime());
     testSingle("isSupported");
   }
 
   @Test
   public void b139432507_containsArray() throws Exception {
+    // TODO(b/179866251): Update tests.
+    assumeTrue(kotlinc.is(KOTLINC_1_3_72) || allowAccessModification);
     assumeTrue("Different inlining behavior on CF backend", parameters.isDexRuntime());
     testSingle("containsArray");
   }
diff --git a/src/test/java/com/android/tools/r8/kotlin/KotlinUnusedArgumentsInLambdasTest.java b/src/test/java/com/android/tools/r8/kotlin/KotlinUnusedArgumentsInLambdasTest.java
index fa3b505..e928379 100644
--- a/src/test/java/com/android/tools/r8/kotlin/KotlinUnusedArgumentsInLambdasTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/KotlinUnusedArgumentsInLambdasTest.java
@@ -9,6 +9,7 @@
 import static org.junit.Assert.assertTrue;
 
 import com.android.tools.r8.KotlinTestParameters;
+import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestShrinkerBuilder;
 import com.android.tools.r8.utils.BooleanUtils;
 import com.android.tools.r8.utils.codeinspector.MethodSubject;
@@ -21,16 +22,19 @@
 @RunWith(Parameterized.class)
 public class KotlinUnusedArgumentsInLambdasTest extends AbstractR8KotlinTestBase {
 
-  @Parameterized.Parameters(name = "{0}, allowAccessModification: {1}")
+  @Parameterized.Parameters(name = "{0}, {1}, allowAccessModification: {2}")
   public static Collection<Object[]> data() {
     return buildParameters(
+        getTestParameters().withAllRuntimesAndApiLevels().build(),
         getKotlinTestParameters().withAllCompilersAndTargetVersions().build(),
         BooleanUtils.values());
   }
 
   public KotlinUnusedArgumentsInLambdasTest(
-      KotlinTestParameters kotlinParameters, boolean allowAccessModification) {
-    super(kotlinParameters, allowAccessModification);
+      TestParameters parameters,
+      KotlinTestParameters kotlinParameters,
+      boolean allowAccessModification) {
+    super(parameters, kotlinParameters, allowAccessModification);
   }
 
   @Test
diff --git a/src/test/java/com/android/tools/r8/kotlin/KotlinUnusedSingletonTest.java b/src/test/java/com/android/tools/r8/kotlin/KotlinUnusedSingletonTest.java
index 226fb8b..4da0c68 100644
--- a/src/test/java/com/android/tools/r8/kotlin/KotlinUnusedSingletonTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/KotlinUnusedSingletonTest.java
@@ -3,13 +3,16 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.kotlin;
 
+import static com.android.tools.r8.KotlinCompilerTool.KotlinCompilerVersion.KOTLINC_1_3_72;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
 import static org.hamcrest.CoreMatchers.not;
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeTrue;
 
 import com.android.tools.r8.KotlinTestParameters;
+import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestShrinkerBuilder;
 import com.android.tools.r8.utils.BooleanUtils;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
@@ -22,13 +25,15 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
 
 @RunWith(Parameterized.class)
 public class KotlinUnusedSingletonTest extends AbstractR8KotlinTestBase {
 
-  @Parameterized.Parameters(name = "{0}, allowAccessModification: {1}")
+  @Parameters(name = "{0}, {1}, allowAccessModification: {2}")
   public static Collection<Object[]> data() {
     return buildParameters(
+        getTestParameters().withAllRuntimesAndApiLevels().build(),
         getKotlinTestParameters().withAllCompilersAndTargetVersions().build(),
         BooleanUtils.values());
   }
@@ -37,12 +42,16 @@
       "void java.io.PrintStream.println(java.lang.Object)";
 
   public KotlinUnusedSingletonTest(
-      KotlinTestParameters kotlinParameters, boolean allowAccessModification) {
-    super(kotlinParameters, allowAccessModification);
+      TestParameters parameters,
+      KotlinTestParameters kotlinParameters,
+      boolean allowAccessModification) {
+    super(parameters, kotlinParameters, allowAccessModification);
   }
 
   @Test
   public void b110196118() throws Exception {
+    // TODO(b/179866251): Update tests.
+    assumeTrue(kotlinc.is(KOTLINC_1_3_72));
     final String mainClassName = "unused_singleton.MainKt";
     final String moduleName = "unused_singleton.TestModule";
     runTest(
@@ -66,6 +75,7 @@
               // The method provideGreeting() is no longer being invoked -- i.e., we have been able
               // to determine that the class initialization of the enclosing class is trivial.
               ClassSubject module = inspector.clazz(moduleName);
+              // TODO(b/179897889): Should probably check for module being present.
               assertThat(main, isPresent());
               assertEquals(
                   0,
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 f53dae8..b0dffa9 100644
--- a/src/test/java/com/android/tools/r8/kotlin/ProcessKotlinReflectionLibTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/ProcessKotlinReflectionLibTest.java
@@ -3,6 +3,7 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.kotlin;
 
+import static com.android.tools.r8.KotlinCompilerTool.KotlinCompilerVersion.KOTLINC_1_4_20;
 
 import com.android.tools.r8.KotlinTestBase;
 import com.android.tools.r8.KotlinTestParameters;
@@ -22,7 +23,7 @@
 
   private final TestParameters parameters;
 
-  @Parameterized.Parameters(name = "{0}, {1}")
+  @Parameterized.Parameters(name = "{0}, target: {1}")
   public static Collection<Object[]> data() {
     return buildParameters(
         getTestParameters().withAllRuntimes().build(),
@@ -45,8 +46,11 @@
         .addKeepAttributes(ProguardKeepAttributes.SIGNATURE)
         .addKeepAttributes(ProguardKeepAttributes.INNER_CLASSES)
         .addKeepAttributes(ProguardKeepAttributes.ENCLOSING_METHOD)
+        .allowUnusedDontWarnKotlinReflectJvmInternal(kotlinc.is(KOTLINC_1_4_20))
+        .allowUnusedProguardConfigurationRules(kotlinc.is(KOTLINC_1_4_20))
         .apply(consumer)
-        .compile();
+        .compile()
+        .apply(assertUnusedKeepRuleForKotlinMetadata(kotlinc.is(KOTLINC_1_4_20)));
   }
 
   @Test
diff --git a/src/test/java/com/android/tools/r8/kotlin/R8KotlinAccessorTest.java b/src/test/java/com/android/tools/r8/kotlin/R8KotlinAccessorTest.java
index a405400..8773698 100644
--- a/src/test/java/com/android/tools/r8/kotlin/R8KotlinAccessorTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/R8KotlinAccessorTest.java
@@ -9,7 +9,7 @@
 import static org.junit.Assert.assertTrue;
 
 import com.android.tools.r8.KotlinTestParameters;
-import com.android.tools.r8.R8TestBuilder;
+import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestShrinkerBuilder;
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.ToolHelper.ProcessResult;
@@ -66,16 +66,19 @@
           .addProperty("property", JAVA_LANG_STRING, Visibility.PRIVATE)
           .addProperty("indirectPropertyGetter", JAVA_LANG_STRING, Visibility.PRIVATE);
 
-  @Parameterized.Parameters(name = "{0}, allowAccessModification: {1}")
+  @Parameterized.Parameters(name = "{0}, {1}, allowAccessModification: {2}")
   public static Collection<Object[]> data() {
     return buildParameters(
+        getTestParameters().withAllRuntimesAndApiLevels().build(),
         getKotlinTestParameters().withAllCompilersAndTargetVersions().build(),
         BooleanUtils.values());
   }
 
   public R8KotlinAccessorTest(
-      KotlinTestParameters kotlinParameters, boolean allowAccessModification) {
-    super(kotlinParameters, allowAccessModification);
+      TestParameters parameters,
+      KotlinTestParameters kotlinParameters,
+      boolean allowAccessModification) {
+    super(parameters, kotlinParameters, allowAccessModification);
   }
 
   @Test
@@ -331,7 +334,13 @@
     TestKotlinCompanionClass testedClass = ACCESSOR_COMPANION_PROPERTY_CLASS;
     String mainClass =
         addMainToClasspath("accessors.AccessorKt", "accessor_accessPropertyFromCompanionClass");
-    runTest("accessors", mainClass, R8TestBuilder::noClassStaticizing)
+    runTest(
+            "accessors",
+            mainClass,
+            builder -> {
+              builder.addClasspathFiles(ToolHelper.getKotlinAnnotationJar(kotlinc));
+              builder.noClassStaticizing();
+            })
         .inspect(
             inspector -> {
               // The classes are removed entirely as a result of member value propagation, inlining,
diff --git a/src/test/java/com/android/tools/r8/kotlin/R8KotlinDataClassTest.java b/src/test/java/com/android/tools/r8/kotlin/R8KotlinDataClassTest.java
index b2f0927..b992613 100644
--- a/src/test/java/com/android/tools/r8/kotlin/R8KotlinDataClassTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/R8KotlinDataClassTest.java
@@ -4,7 +4,10 @@
 
 package com.android.tools.r8.kotlin;
 
+import static org.junit.Assume.assumeTrue;
+
 import com.android.tools.r8.KotlinTestParameters;
+import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.graph.DexCode;
 import com.android.tools.r8.kotlin.TestKotlinClass.Visibility;
 import com.android.tools.r8.naming.MemberNaming.MethodSignature;
@@ -42,20 +45,25 @@
 
   private Consumer<InternalOptions> disableClassInliner = o -> o.enableClassInlining = false;
 
-  @Parameterized.Parameters(name = "{0}, allowAccessModification: {1}")
+  @Parameterized.Parameters(name = "{0}, {1}, allowAccessModification: {2}")
   public static Collection<Object[]> data() {
     return buildParameters(
+        getTestParameters().withAllRuntimesAndApiLevels().build(),
         getKotlinTestParameters().withAllCompilersAndTargetVersions().build(),
         BooleanUtils.values());
   }
 
   public R8KotlinDataClassTest(
-      KotlinTestParameters kotlinParameters, boolean allowAccessModification) {
-    super(kotlinParameters, allowAccessModification);
+      TestParameters parameters,
+      KotlinTestParameters kotlinParameters,
+      boolean allowAccessModification) {
+    super(parameters, kotlinParameters, allowAccessModification);
   }
 
   @Test
   public void test_dataclass_gettersOnly() throws Exception {
+    // TODO(b/179866251): Allow for CF code.
+    assumeTrue(testParameters.isDexRuntime());
     final String mainClassName = "dataclass.MainGettersOnlyKt";
     final MethodSignature testMethodSignature =
         new MethodSignature("testDataClassGetters", "void", Collections.emptyList());
@@ -100,6 +108,8 @@
 
   @Test
   public void test_dataclass_componentOnly() throws Exception {
+    // TODO(b/179866251): Allow for CF code.
+    assumeTrue(testParameters.isDexRuntime());
     final String mainClassName = "dataclass.MainComponentOnlyKt";
     final MethodSignature testMethodSignature =
         new MethodSignature("testAllDataClassComponentFunctions", "void", Collections.emptyList());
@@ -143,6 +153,8 @@
 
   @Test
   public void test_dataclass_componentPartial() throws Exception {
+    // TODO(b/179866251): Allow for CF code.
+    assumeTrue(testParameters.isDexRuntime());
     final String mainClassName = "dataclass.MainComponentPartialKt";
     final MethodSignature testMethodSignature =
         new MethodSignature("testSomeDataClassComponentFunctions", "void", Collections.emptyList());
diff --git a/src/test/java/com/android/tools/r8/kotlin/R8KotlinIntrinsicsTest.java b/src/test/java/com/android/tools/r8/kotlin/R8KotlinIntrinsicsTest.java
index 11d1ea2..9d9fcee 100644
--- a/src/test/java/com/android/tools/r8/kotlin/R8KotlinIntrinsicsTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/R8KotlinIntrinsicsTest.java
@@ -4,7 +4,11 @@
 
 package com.android.tools.r8.kotlin;
 
+import static com.android.tools.r8.KotlinCompilerTool.KotlinCompilerVersion.KOTLINC_1_3_72;
+import static org.junit.Assume.assumeTrue;
+
 import com.android.tools.r8.KotlinTestParameters;
+import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.naming.MemberNaming.MethodSignature;
 import com.android.tools.r8.utils.BooleanUtils;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
@@ -23,20 +27,25 @@
   private static final TestKotlinDataClass KOTLIN_INTRINSICS_CLASS =
       new TestKotlinDataClass("kotlin.jvm.internal.Intrinsics");
 
-  @Parameterized.Parameters(name = "{0}, allowAccessModification: {1}")
+  @Parameterized.Parameters(name = "{0}, {1}, allowAccessModification: {2}")
   public static Collection<Object[]> data() {
     return buildParameters(
+        getTestParameters().withAllRuntimesAndApiLevels().build(),
         getKotlinTestParameters().withAllCompilersAndTargetVersions().build(),
         BooleanUtils.values());
   }
 
   public R8KotlinIntrinsicsTest(
-      KotlinTestParameters kotlinParameters, boolean allowAccessModification) {
-    super(kotlinParameters, allowAccessModification);
+      TestParameters parameters,
+      KotlinTestParameters kotlinParameters,
+      boolean allowAccessModification) {
+    super(parameters, kotlinParameters, allowAccessModification);
   }
 
   @Test
   public void testParameterNullCheckIsInlined() throws Exception {
+    // TODO(b/179866251): Update tests.
+    assumeTrue(kotlinc.is(KOTLINC_1_3_72));
     final String extraRules = keepClassMethod("intrinsics.IntrinsicsKt",
         new MethodSignature("expectsNonNullParameters",
             "java.lang.String", Lists.newArrayList("java.lang.String", "java.lang.String")));
@@ -67,7 +76,8 @@
                               "checkParameterIsNotNull",
                               "void",
                               Lists.newArrayList("java.lang.Object", "java.lang.String")),
-                          !allowAccessModification)
+                          // TODO(b/179866251): This is also different on CF
+                          !allowAccessModification || testParameters.isCfRuntime())
                       .build());
             });
   }
diff --git a/src/test/java/com/android/tools/r8/kotlin/R8KotlinPropertiesTest.java b/src/test/java/com/android/tools/r8/kotlin/R8KotlinPropertiesTest.java
index edd2c8f..c7325b0 100644
--- a/src/test/java/com/android/tools/r8/kotlin/R8KotlinPropertiesTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/R8KotlinPropertiesTest.java
@@ -7,6 +7,7 @@
 import static org.junit.Assert.assertTrue;
 
 import com.android.tools.r8.KotlinTestParameters;
+import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.kotlin.TestKotlinClass.Visibility;
 import com.android.tools.r8.naming.MemberNaming;
 import com.android.tools.r8.naming.MemberNaming.MethodSignature;
@@ -93,16 +94,19 @@
         o.enableClassStaticizer = false;
       };
 
-  @Parameterized.Parameters(name = "{0}, allowAccessModification: {1}")
+  @Parameterized.Parameters(name = "{0}, {1}, allowAccessModification: {2}")
   public static Collection<Object[]> data() {
     return buildParameters(
+        getTestParameters().withAllRuntimesAndApiLevels().build(),
         getKotlinTestParameters().withAllCompilersAndTargetVersions().build(),
         BooleanUtils.values());
   }
 
   public R8KotlinPropertiesTest(
-      KotlinTestParameters kotlinParameters, boolean allowAccessModification) {
-    super(kotlinParameters, allowAccessModification);
+      TestParameters parameters,
+      KotlinTestParameters kotlinParameters,
+      boolean allowAccessModification) {
+    super(parameters, kotlinParameters, allowAccessModification);
   }
 
   @Test
diff --git a/src/test/java/com/android/tools/r8/kotlin/SimplifyIfNotNullKotlinTest.java b/src/test/java/com/android/tools/r8/kotlin/SimplifyIfNotNullKotlinTest.java
index c9fd417..b3537a9 100644
--- a/src/test/java/com/android/tools/r8/kotlin/SimplifyIfNotNullKotlinTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/SimplifyIfNotNullKotlinTest.java
@@ -3,9 +3,12 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.kotlin;
 
+import static com.android.tools.r8.KotlinCompilerTool.KotlinCompilerVersion.KOTLINC_1_3_72;
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assume.assumeTrue;
 
 import com.android.tools.r8.KotlinTestParameters;
+import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.naming.MemberNaming.MethodSignature;
 import com.android.tools.r8.utils.BooleanUtils;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
@@ -21,16 +24,19 @@
   private static final String FOLDER = "non_null";
   private static final String STRING = "java.lang.String";
 
-  @Parameterized.Parameters(name = "{0}, allowAccessModification: {1}")
+  @Parameterized.Parameters(name = "{0}, {1}, allowAccessModification: {2}")
   public static Collection<Object[]> data() {
     return buildParameters(
+        getTestParameters().withAllRuntimesAndApiLevels().build(),
         getKotlinTestParameters().withAllCompilersAndTargetVersions().build(),
         BooleanUtils.values());
   }
 
   public SimplifyIfNotNullKotlinTest(
-      KotlinTestParameters kotlinParameters, boolean allowAccessModification) {
-    super(kotlinParameters, allowAccessModification);
+      TestParameters parameters,
+      KotlinTestParameters kotlinParameters,
+      boolean allowAccessModification) {
+    super(parameters, kotlinParameters, allowAccessModification);
   }
 
   @Test
@@ -56,13 +62,16 @@
               long paramNullCheckCount =
                   countCall(testMethod, "Intrinsics", "checkParameterIsNotNull");
               // One after Iterator#hasNext, and another in the filter predicate: sinceYear != null.
-              assertEquals(2, ifzCount);
+              // TODO(b/179951729): Not the same amount of ifz on CF and DEX.
+              assertEquals(testParameters.isCfRuntime() ? 1 : 2, ifzCount);
               assertEquals(0, paramNullCheckCount);
             });
   }
 
   @Test
   public void test_example2() throws Exception {
+    // TODO(b/179866251): Update tests.
+    assumeTrue(kotlinc.is(KOTLINC_1_3_72) || allowAccessModification);
     final TestKotlinClass ex2 = new TestKotlinClass("non_null.Example2Kt");
     final MethodSignature testMethodSignature =
         new MethodSignature("aOrDefault", STRING, ImmutableList.of(STRING, STRING));
@@ -83,7 +92,8 @@
               long paramNullCheckCount =
                   countCall(testMethod, "Intrinsics", "checkParameterIsNotNull");
               // ?: in aOrDefault
-              assertEquals(1, ifzCount);
+              // TODO(b/179951729): Not the same amount of ifz on CF and DEX.
+              assertEquals(testParameters.isCfRuntime() ? 0 : 1, ifzCount);
               assertEquals(allowAccessModification ? 0 : 1, paramNullCheckCount);
             });
   }
@@ -109,7 +119,8 @@
                   testMethod.streamInstructions().filter(i -> i.isIfEqz() || i.isIfNez()).count();
               // !! operator inside explicit null check should be gone.
               // One explicit null-check as well as 4 bar? accesses.
-              assertEquals(5, ifzCount);
+              // TODO(b/179951729): Not the same amount of ifz on CF and DEX.
+              assertEquals(testParameters.isCfRuntime() ? 0 : 5, ifzCount);
             });
   }
 }
diff --git a/src/test/java/com/android/tools/r8/kotlin/coroutines/KotlinxCoroutinesTestRunner.java b/src/test/java/com/android/tools/r8/kotlin/coroutines/KotlinxCoroutinesTestRunner.java
index 1241446..70ae682 100644
--- a/src/test/java/com/android/tools/r8/kotlin/coroutines/KotlinxCoroutinesTestRunner.java
+++ b/src/test/java/com/android/tools/r8/kotlin/coroutines/KotlinxCoroutinesTestRunner.java
@@ -4,7 +4,9 @@
 
 package com.android.tools.r8.kotlin.coroutines;
 
+import static com.android.tools.r8.KotlinCompilerTool.KotlinCompilerVersion.KOTLINC_1_3_72;
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assume.assumeTrue;
 
 import com.android.tools.r8.KotlinTestParameters;
 import com.android.tools.r8.TestParameters;
@@ -65,11 +67,15 @@
 
   @Test
   public void runKotlinxCoroutinesTests_smoke() throws Exception {
+    // TODO(b/179860018): Make run for 1.4.20
+    assumeTrue(kotlinc.is(KOTLINC_1_3_72));
     runTestsInJar(compileTestSources(BASE_LIBRARY), BASE_LIBRARY);
   }
 
   @Test
   public void runKotlinxCoroutinesTests_r8() throws Exception {
+    // TODO(b/179860018): Make run for 1.4.20
+    assumeTrue(kotlinc.is(KOTLINC_1_3_72));
     Path baseJar =
         testForR8(parameters.getBackend())
             .addProgramFiles(BASE_LIBRARY)
diff --git a/src/test/java/com/android/tools/r8/kotlin/lambda/KotlinLambdaMergerValidationTest.java b/src/test/java/com/android/tools/r8/kotlin/lambda/KotlinLambdaMergerValidationTest.java
index 655ddb5..9d9f42a 100644
--- a/src/test/java/com/android/tools/r8/kotlin/lambda/KotlinLambdaMergerValidationTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/lambda/KotlinLambdaMergerValidationTest.java
@@ -6,12 +6,12 @@
 import static org.hamcrest.CoreMatchers.equalTo;
 import static org.junit.Assume.assumeTrue;
 
+import com.android.tools.r8.KotlinTestBase;
 import com.android.tools.r8.KotlinTestParameters;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestRuntime;
 import com.android.tools.r8.TestRuntime.CfRuntime;
 import com.android.tools.r8.ToolHelper;
-import com.android.tools.r8.kotlin.AbstractR8KotlinTestBase;
 import com.android.tools.r8.utils.DescriptorUtils;
 import java.nio.file.Path;
 import java.util.Collection;
@@ -20,7 +20,7 @@
 import org.junit.runners.Parameterized;
 
 @RunWith(Parameterized.class)
-public class KotlinLambdaMergerValidationTest extends AbstractR8KotlinTestBase {
+public class KotlinLambdaMergerValidationTest extends KotlinTestBase {
 
   private final TestParameters parameters;
 
@@ -33,7 +33,7 @@
 
   public KotlinLambdaMergerValidationTest(
       TestParameters parameters, KotlinTestParameters kotlinParameters) {
-    super(kotlinParameters, false);
+    super(kotlinParameters);
     this.parameters = parameters;
   }
 
diff --git a/src/test/java/com/android/tools/r8/kotlin/lambda/KotlinLambdaMergingDebugTest.java b/src/test/java/com/android/tools/r8/kotlin/lambda/KotlinLambdaMergingDebugTest.java
index 61f417e..7aaeade 100644
--- a/src/test/java/com/android/tools/r8/kotlin/lambda/KotlinLambdaMergingDebugTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/lambda/KotlinLambdaMergingDebugTest.java
@@ -6,10 +6,10 @@
 import static org.hamcrest.CoreMatchers.equalTo;
 
 import com.android.tools.r8.CompilationMode;
+import com.android.tools.r8.KotlinTestBase;
 import com.android.tools.r8.KotlinTestParameters;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.ToolHelper.KotlinTargetVersion;
-import com.android.tools.r8.kotlin.AbstractR8KotlinTestBase;
 import java.util.List;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -17,7 +17,7 @@
 import org.junit.runners.Parameterized.Parameters;
 
 @RunWith(Parameterized.class)
-public class KotlinLambdaMergingDebugTest extends AbstractR8KotlinTestBase {
+public class KotlinLambdaMergingDebugTest extends KotlinTestBase {
 
   private final TestParameters parameters;
   private static final String FOLDER = "reprocess_merged_lambdas_kstyle";
diff --git a/src/test/java/com/android/tools/r8/kotlin/lambda/KotlinLambdaMergingWithReprocessingTest.java b/src/test/java/com/android/tools/r8/kotlin/lambda/KotlinLambdaMergingWithReprocessingTest.java
index 8258582..96135ae 100644
--- a/src/test/java/com/android/tools/r8/kotlin/lambda/KotlinLambdaMergingWithReprocessingTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/lambda/KotlinLambdaMergingWithReprocessingTest.java
@@ -4,6 +4,7 @@
 package com.android.tools.r8.kotlin.lambda;
 
 import com.android.tools.r8.KotlinTestParameters;
+import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestShrinkerBuilder;
 import com.android.tools.r8.kotlin.AbstractR8KotlinTestBase;
 import com.android.tools.r8.utils.BooleanUtils;
@@ -15,16 +16,19 @@
 @RunWith(Parameterized.class)
 public class KotlinLambdaMergingWithReprocessingTest extends AbstractR8KotlinTestBase {
 
-  @Parameterized.Parameters(name = "{0}, allowAccessModification: {1}")
+  @Parameterized.Parameters(name = "{0}, {1}, allowAccessModification: {2}")
   public static Collection<Object[]> data() {
     return buildParameters(
+        getTestParameters().withAllRuntimesAndApiLevels().build(),
         getKotlinTestParameters().withAllCompilersAndTargetVersions().build(),
         BooleanUtils.values());
   }
 
   public KotlinLambdaMergingWithReprocessingTest(
-      KotlinTestParameters kotlinParameters, boolean allowAccessModification) {
-    super(kotlinParameters, allowAccessModification);
+      TestParameters parameters,
+      KotlinTestParameters kotlinParameters,
+      boolean allowAccessModification) {
+    super(parameters, kotlinParameters, allowAccessModification);
   }
 
   @Test
diff --git a/src/test/java/com/android/tools/r8/kotlin/lambda/KotlinLambdaMergingWithSmallInliningBudgetTest.java b/src/test/java/com/android/tools/r8/kotlin/lambda/KotlinLambdaMergingWithSmallInliningBudgetTest.java
index 087a81b..2700f5e 100644
--- a/src/test/java/com/android/tools/r8/kotlin/lambda/KotlinLambdaMergingWithSmallInliningBudgetTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/lambda/KotlinLambdaMergingWithSmallInliningBudgetTest.java
@@ -4,6 +4,7 @@
 package com.android.tools.r8.kotlin.lambda;
 
 import com.android.tools.r8.KotlinTestParameters;
+import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.kotlin.AbstractR8KotlinTestBase;
 import com.android.tools.r8.utils.BooleanUtils;
 import java.util.Collection;
@@ -14,16 +15,19 @@
 @RunWith(Parameterized.class)
 public class KotlinLambdaMergingWithSmallInliningBudgetTest extends AbstractR8KotlinTestBase {
 
-  @Parameterized.Parameters(name = "{0}, allowAccessModification: {1}")
+  @Parameterized.Parameters(name = "{0}, {1}, allowAccessModification: {2}")
   public static Collection<Object[]> data() {
     return buildParameters(
+        getTestParameters().withAllRuntimesAndApiLevels().build(),
         getKotlinTestParameters().withAllCompilersAndTargetVersions().build(),
         BooleanUtils.values());
   }
 
   public KotlinLambdaMergingWithSmallInliningBudgetTest(
-      KotlinTestParameters kotlinParameters, boolean allowAccessModification) {
-    super(kotlinParameters, allowAccessModification);
+      TestParameters parameters,
+      KotlinTestParameters kotlinParameters,
+      boolean allowAccessModification) {
+    super(parameters, kotlinParameters, allowAccessModification);
   }
 
   @Test
diff --git a/src/test/java/com/android/tools/r8/kotlin/lambda/b159688129/LambdaSplitByCodeCorrectnessTest.java b/src/test/java/com/android/tools/r8/kotlin/lambda/b159688129/LambdaSplitByCodeCorrectnessTest.java
index 79cef64..67bb203 100644
--- a/src/test/java/com/android/tools/r8/kotlin/lambda/b159688129/LambdaSplitByCodeCorrectnessTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/lambda/b159688129/LambdaSplitByCodeCorrectnessTest.java
@@ -10,12 +10,12 @@
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.junit.Assert.assertEquals;
 
+import com.android.tools.r8.KotlinTestBase;
 import com.android.tools.r8.KotlinTestParameters;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestRuntime;
 import com.android.tools.r8.TestRuntime.CfRuntime;
 import com.android.tools.r8.ToolHelper;
-import com.android.tools.r8.kotlin.AbstractR8KotlinTestBase;
 import com.android.tools.r8.utils.BooleanUtils;
 import com.android.tools.r8.utils.DescriptorUtils;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
@@ -29,7 +29,7 @@
 import org.junit.runners.Parameterized.Parameters;
 
 @RunWith(Parameterized.class)
-public class LambdaSplitByCodeCorrectnessTest extends AbstractR8KotlinTestBase {
+public class LambdaSplitByCodeCorrectnessTest extends KotlinTestBase {
 
   private final TestParameters parameters;
   private final boolean splitGroup;
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/KotlinMetadataTestBase.java b/src/test/java/com/android/tools/r8/kotlin/metadata/KotlinMetadataTestBase.java
index 3765187..e9324e9 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/KotlinMetadataTestBase.java
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/KotlinMetadataTestBase.java
@@ -5,11 +5,14 @@
 
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
 import static junit.framework.TestCase.assertNotNull;
+import static org.hamcrest.CoreMatchers.anyOf;
+import static org.hamcrest.CoreMatchers.equalTo;
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.junit.Assert.assertNull;
 
+import com.android.tools.r8.KotlinTestBase;
 import com.android.tools.r8.KotlinTestParameters;
-import com.android.tools.r8.kotlin.AbstractR8KotlinTestBase;
+import com.android.tools.r8.TestCompileResult;
 import com.android.tools.r8.kotlin.KotlinMetadataWriter;
 import com.android.tools.r8.utils.DescriptorUtils;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
@@ -19,7 +22,7 @@
 import kotlinx.metadata.jvm.KotlinClassHeader;
 import kotlinx.metadata.jvm.KotlinClassMetadata;
 
-public abstract class KotlinMetadataTestBase extends AbstractR8KotlinTestBase {
+public abstract class KotlinMetadataTestBase extends KotlinTestBase {
 
   public KotlinMetadataTestBase(KotlinTestParameters kotlinParameters) {
     super(kotlinParameters);
@@ -64,4 +67,12 @@
       TestCase.assertEquals(expected, actual);
     }
   }
+
+  public static void verifyExpectedWarningsFromKotlinReflectAndStdLib(
+      TestCompileResult<?, ?> compileResult) {
+    compileResult.assertAllWarningMessagesMatch(
+        anyOf(
+            equalTo("Resource 'META-INF/MANIFEST.MF' already exists."),
+            equalTo("Resource 'META-INF/versions/9/module-info.class' already exists.")));
+  }
 }
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataStripTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataStripTest.java
index 8111567..7afbd44 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataStripTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataStripTest.java
@@ -3,10 +3,10 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.kotlin.metadata;
 
+import static com.android.tools.r8.KotlinCompilerTool.KotlinCompilerVersion.KOTLINC_1_4_20;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresentAndNotRenamed;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresentAndRenamed;
-import static org.hamcrest.CoreMatchers.equalTo;
 import static org.hamcrest.CoreMatchers.not;
 import static org.hamcrest.MatcherAssert.assertThat;
 
@@ -60,9 +60,9 @@
             .addDontWarnJetBrainsAnnotations()
             .allowDiagnosticWarningMessages()
             .setMinApi(parameters.getApiLevel())
+            .allowUnusedDontWarnKotlinReflectJvmInternal(kotlinc.is(KOTLINC_1_4_20))
             .compile()
-            .assertAllWarningMessagesMatch(
-                equalTo("Resource 'META-INF/MANIFEST.MF' already exists."))
+            .apply(KotlinMetadataTestBase::verifyExpectedWarningsFromKotlinReflectAndStdLib)
             .run(parameters.getRuntime(), mainClassName);
     CodeInspector inspector = result.inspector();
     ClassSubject clazz = inspector.clazz(mainClassName);
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 3732458..fcb64b6 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
@@ -4,7 +4,7 @@
 
 package com.android.tools.r8.kotlin.reflection;
 
-import static org.hamcrest.CoreMatchers.equalTo;
+import static com.android.tools.r8.KotlinCompilerTool.KotlinCompilerVersion.KOTLINC_1_4_20;
 import static org.junit.Assume.assumeTrue;
 
 import com.android.tools.r8.DexIndexedConsumer.ArchiveConsumer;
@@ -12,6 +12,7 @@
 import com.android.tools.r8.KotlinTestParameters;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.kotlin.metadata.KotlinMetadataTestBase;
 import com.android.tools.r8.shaking.ProguardKeepAttributes;
 import com.android.tools.r8.utils.DescriptorUtils;
 import com.android.tools.r8.utils.FileUtils;
@@ -91,9 +92,10 @@
         .addKeepAllClassesRule()
         .addKeepAttributes(ProguardKeepAttributes.RUNTIME_VISIBLE_ANNOTATIONS)
         .allowDiagnosticWarningMessages()
+        .allowUnusedDontWarnKotlinReflectJvmInternal(kotlinc.is(KOTLINC_1_4_20))
         .compile()
         .writeToZip(foo.toPath())
-        .assertAllWarningMessagesMatch(equalTo("Resource 'META-INF/MANIFEST.MF' already exists."))
+        .apply(KotlinMetadataTestBase::verifyExpectedWarningsFromKotlinReflectAndStdLib)
         .run(parameters.getRuntime(), PKG + ".SimpleReflectKt")
         .assertSuccessWithOutputLines(EXPECTED_OUTPUT);
   }
diff --git a/src/test/java/com/android/tools/r8/missingclasses/MissingClassReferencedFromClassAnnotationTest.java b/src/test/java/com/android/tools/r8/missingclasses/MissingClassReferencedFromClassAnnotationTest.java
new file mode 100644
index 0000000..0af31a3
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/missingclasses/MissingClassReferencedFromClassAnnotationTest.java
@@ -0,0 +1,62 @@
+// 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.ClassReference;
+import com.android.tools.r8.references.Reference;
+import org.junit.Test;
+
+// TODO(b/179456539): This test should fail without -keepattributes RuntimeVisibleAnnotations, but
+//  we retain missing annotations even if there is no -keepattributes *Annotations*.
+public class MissingClassReferencedFromClassAnnotationTest extends MissingClassesTestBase {
+
+  private static final ClassReference referencedFrom = Reference.classFromClass(Main.class);
+
+  public MissingClassReferencedFromClassAnnotationTest(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(MissingRuntimeAnnotation.class));
+  }
+
+  @Test
+  public void testIgnoreWarnings() throws Exception {
+    compileWithExpectedDiagnostics(
+        Main.class,
+        diagnostics -> inspectDiagnosticsWithIgnoreWarnings(diagnostics, referencedFrom),
+        addIgnoreWarnings());
+  }
+
+  @Override
+  ClassReference getMissingClassReference() {
+    return Reference.classFromClass(MissingRuntimeAnnotation.class);
+  }
+
+  @MissingRuntimeAnnotation
+  static class Main {
+
+    public static void main(String[] args) {}
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/missingclasses/MissingClassReferencedFromClassAnnotationWithDataTest.java b/src/test/java/com/android/tools/r8/missingclasses/MissingClassReferencedFromClassAnnotationWithDataTest.java
new file mode 100644
index 0000000..0918d76
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/missingclasses/MissingClassReferencedFromClassAnnotationWithDataTest.java
@@ -0,0 +1,76 @@
+// 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.ClassReference;
+import com.android.tools.r8.references.Reference;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import org.junit.Test;
+
+public class MissingClassReferencedFromClassAnnotationWithDataTest extends MissingClassesTestBase {
+
+  private static final ClassReference referencedFrom = Reference.classFromClass(Main.class);
+
+  public MissingClassReferencedFromClassAnnotationWithDataTest(TestParameters parameters) {
+    super(parameters);
+  }
+
+  @Test(expected = CompilationFailedException.class)
+  public void testNoRules() throws Exception {
+    compileWithExpectedDiagnostics(
+        Main.class,
+        diagnostics -> inspectDiagnosticsWithNoRules(diagnostics, referencedFrom),
+        addRuntimeAnnotation());
+  }
+
+  @Test
+  public void testDontWarnMainClass() throws Exception {
+    compileWithExpectedDiagnostics(
+        Main.class,
+        TestDiagnosticMessages::assertNoMessages,
+        addRuntimeAnnotation().andThen(addDontWarn(Main.class)));
+  }
+
+  @Test
+  public void testDontWarnMissingClass() throws Exception {
+    compileWithExpectedDiagnostics(
+        Main.class,
+        TestDiagnosticMessages::assertNoMessages,
+        addRuntimeAnnotation().andThen(addDontWarn(MissingClass.class)));
+  }
+
+  @Test
+  public void testIgnoreWarnings() throws Exception {
+    compileWithExpectedDiagnostics(
+        Main.class,
+        diagnostics -> inspectDiagnosticsWithIgnoreWarnings(diagnostics, referencedFrom),
+        addRuntimeAnnotation().andThen(addIgnoreWarnings()));
+  }
+
+  private ThrowableConsumer<R8FullTestBuilder> addRuntimeAnnotation() {
+    return builder ->
+        builder
+            .addProgramClasses(RuntimeAnnotation.class)
+            .addKeepClassRules(RuntimeAnnotation.class)
+            .addKeepRuntimeVisibleAnnotations();
+  }
+
+  @RuntimeAnnotation(data = MissingClass.class)
+  static class Main {
+
+    public static void main(String[] args) {}
+  }
+
+  @Retention(RetentionPolicy.RUNTIME)
+  @interface RuntimeAnnotation {
+    Class<?> data();
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/missingclasses/MissingClassReferencedFromFieldAnnotationTest.java b/src/test/java/com/android/tools/r8/missingclasses/MissingClassReferencedFromFieldAnnotationTest.java
new file mode 100644
index 0000000..7788831
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/missingclasses/MissingClassReferencedFromFieldAnnotationTest.java
@@ -0,0 +1,67 @@
+// 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.ClassReference;
+import com.android.tools.r8.references.FieldReference;
+import com.android.tools.r8.references.Reference;
+import org.junit.Test;
+
+// TODO(b/179456539): This test should fail without -keepattributes RuntimeVisibleAnnotations, but
+//  we retain missing annotations even if there is no -keepattributes *Annotations*.
+public class MissingClassReferencedFromFieldAnnotationTest extends MissingClassesTestBase {
+
+  private static final FieldReference referencedFrom =
+      Reference.field(Reference.classFromClass(Main.class), "FIELD", Reference.INT);
+
+  public MissingClassReferencedFromFieldAnnotationTest(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(MissingRuntimeAnnotation.class));
+  }
+
+  @Test
+  public void testIgnoreWarnings() throws Exception {
+    compileWithExpectedDiagnostics(
+        Main.class,
+        diagnostics -> inspectDiagnosticsWithIgnoreWarnings(diagnostics, referencedFrom),
+        addIgnoreWarnings());
+  }
+
+  @Override
+  ClassReference getMissingClassReference() {
+    return Reference.classFromClass(MissingRuntimeAnnotation.class);
+  }
+
+  static class Main {
+
+    @MissingRuntimeAnnotation static int FIELD;
+
+    public static void main(String[] args) {
+      int ignore = FIELD;
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/missingclasses/MissingClassReferencedFromMethodAnnotationTest.java b/src/test/java/com/android/tools/r8/missingclasses/MissingClassReferencedFromMethodAnnotationTest.java
new file mode 100644
index 0000000..189b49e
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/missingclasses/MissingClassReferencedFromMethodAnnotationTest.java
@@ -0,0 +1,65 @@
+// 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.ClassReference;
+import com.android.tools.r8.references.MethodReference;
+import com.android.tools.r8.references.Reference;
+import com.android.tools.r8.utils.MethodReferenceUtils;
+import org.junit.Test;
+
+// TODO(b/179456539): This test should fail without -keepattributes RuntimeVisibleAnnotations, but
+//  we retain missing annotations even if there is no -keepattributes *Annotations*.
+public class MissingClassReferencedFromMethodAnnotationTest extends MissingClassesTestBase {
+
+  private static final MethodReference referencedFrom =
+      MethodReferenceUtils.mainMethod(Reference.classFromClass(Main.class));
+
+  public MissingClassReferencedFromMethodAnnotationTest(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(MissingRuntimeAnnotation.class));
+  }
+
+  @Test
+  public void testIgnoreWarnings() throws Exception {
+    compileWithExpectedDiagnostics(
+        Main.class,
+        diagnostics -> inspectDiagnosticsWithIgnoreWarnings(diagnostics, referencedFrom),
+        addIgnoreWarnings());
+  }
+
+  @Override
+  ClassReference getMissingClassReference() {
+    return Reference.classFromClass(MissingRuntimeAnnotation.class);
+  }
+
+  static class Main {
+
+    @MissingRuntimeAnnotation
+    public static void main(String[] args) {}
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/missingclasses/MissingClassReferencedFromParameterAnnotationTest.java b/src/test/java/com/android/tools/r8/missingclasses/MissingClassReferencedFromParameterAnnotationTest.java
new file mode 100644
index 0000000..0017aa7
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/missingclasses/MissingClassReferencedFromParameterAnnotationTest.java
@@ -0,0 +1,64 @@
+// 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.ClassReference;
+import com.android.tools.r8.references.MethodReference;
+import com.android.tools.r8.references.Reference;
+import com.android.tools.r8.utils.MethodReferenceUtils;
+import org.junit.Test;
+
+// TODO(b/179456539): This test should fail without -keepattributes RuntimeVisibleAnnotations, but
+//  we retain missing annotations even if there is no -keepattributes *Annotations*.
+public class MissingClassReferencedFromParameterAnnotationTest extends MissingClassesTestBase {
+
+  private static final MethodReference referencedFrom =
+      MethodReferenceUtils.mainMethod(Reference.classFromClass(Main.class));
+
+  public MissingClassReferencedFromParameterAnnotationTest(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(MissingRuntimeAnnotation.class));
+  }
+
+  @Test
+  public void testIgnoreWarnings() throws Exception {
+    compileWithExpectedDiagnostics(
+        Main.class,
+        diagnostics -> inspectDiagnosticsWithIgnoreWarnings(diagnostics, referencedFrom),
+        addIgnoreWarnings());
+  }
+
+  @Override
+  ClassReference getMissingClassReference() {
+    return Reference.classFromClass(MissingRuntimeAnnotation.class);
+  }
+
+  static class Main {
+
+    public static void main(@MissingRuntimeAnnotation String[] args) {}
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/missingclasses/MissingClassesTestBase.java b/src/test/java/com/android/tools/r8/missingclasses/MissingClassesTestBase.java
index dafb7b4..3968b75 100644
--- a/src/test/java/com/android/tools/r8/missingclasses/MissingClassesTestBase.java
+++ b/src/test/java/com/android/tools/r8/missingclasses/MissingClassesTestBase.java
@@ -14,15 +14,17 @@
 import com.android.tools.r8.TestDiagnosticMessages;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.ThrowableConsumer;
+import com.android.tools.r8.diagnostic.internal.MissingDefinitionsDiagnosticImpl;
 import com.android.tools.r8.references.ClassReference;
 import com.android.tools.r8.references.FieldReference;
 import com.android.tools.r8.references.MethodReference;
 import com.android.tools.r8.references.Reference;
-import com.android.tools.r8.shaking.MissingClassesDiagnostic;
 import com.android.tools.r8.utils.FieldReferenceUtils;
 import com.android.tools.r8.utils.InternalOptions.TestingOptions;
 import com.android.tools.r8.utils.MethodReferenceUtils;
 import com.google.common.collect.ImmutableSet;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 import java.util.List;
 import java.util.function.Function;
 import org.junit.runner.RunWith;
@@ -39,6 +41,9 @@
     int field;
   }
 
+  @Retention(RetentionPolicy.RUNTIME)
+  @interface MissingRuntimeAnnotation {}
+
   interface MissingInterface {}
 
   private final TestParameters parameters;
@@ -129,11 +134,11 @@
 
   void inspectDiagnosticsWithIgnoreWarnings(
       TestDiagnosticMessages diagnostics, String expectedDiagnosticMessage) {
-    MissingClassesDiagnostic diagnostic =
+    MissingDefinitionsDiagnosticImpl diagnostic =
         diagnostics
             .assertOnlyWarnings()
             .assertWarningsCount(1)
-            .assertAllWarningsMatch(diagnosticType(MissingClassesDiagnostic.class))
+            .assertAllWarningsMatch(diagnosticType(MissingDefinitionsDiagnosticImpl.class))
             .getWarning(0);
     assertEquals(ImmutableSet.of(getMissingClassReference()), diagnostic.getMissingClasses());
     assertEquals(expectedDiagnosticMessage, diagnostic.getDiagnosticMessage());
@@ -173,11 +178,11 @@
 
   void inspectDiagnosticsWithNoRules(
       TestDiagnosticMessages diagnostics, String expectedDiagnosticMessage) {
-    MissingClassesDiagnostic diagnostic =
+    MissingDefinitionsDiagnosticImpl diagnostic =
         diagnostics
             .assertOnlyErrors()
             .assertErrorsCount(1)
-            .assertAllErrorsMatch(diagnosticType(MissingClassesDiagnostic.class))
+            .assertAllErrorsMatch(diagnosticType(MissingDefinitionsDiagnosticImpl.class))
             .getError(0);
     assertEquals(ImmutableSet.of(getMissingClassReference()), diagnostic.getMissingClasses());
     assertEquals(expectedDiagnosticMessage, diagnostic.getDiagnosticMessage());
diff --git a/src/test/java/com/android/tools/r8/naming/AbstractR8KotlinNamingTestBase.java b/src/test/java/com/android/tools/r8/naming/AbstractR8KotlinNamingTestBase.java
index 2c3f2bb..2a19802 100644
--- a/src/test/java/com/android/tools/r8/naming/AbstractR8KotlinNamingTestBase.java
+++ b/src/test/java/com/android/tools/r8/naming/AbstractR8KotlinNamingTestBase.java
@@ -8,6 +8,7 @@
 import static org.hamcrest.MatcherAssert.assertThat;
 
 import com.android.tools.r8.KotlinTestParameters;
+import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.kotlin.AbstractR8KotlinTestBase;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
@@ -19,10 +20,11 @@
   protected final boolean minification;
 
   AbstractR8KotlinNamingTestBase(
+      TestParameters parameters,
       KotlinTestParameters kotlinParameters,
       boolean allowAccessModification,
       boolean minification) {
-    super(kotlinParameters, allowAccessModification);
+    super(parameters, kotlinParameters, allowAccessModification);
     this.minification = minification;
   }
 
diff --git a/src/test/java/com/android/tools/r8/naming/KotlinIntrinsicsIdentifierTest.java b/src/test/java/com/android/tools/r8/naming/KotlinIntrinsicsIdentifierTest.java
index 5d9e78b..3aff7c9 100644
--- a/src/test/java/com/android/tools/r8/naming/KotlinIntrinsicsIdentifierTest.java
+++ b/src/test/java/com/android/tools/r8/naming/KotlinIntrinsicsIdentifierTest.java
@@ -3,15 +3,18 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.naming;
 
+import static com.android.tools.r8.KotlinCompilerTool.KotlinCompilerVersion.KOTLINC_1_3_72;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
 import static org.hamcrest.CoreMatchers.equalTo;
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeTrue;
 
 import com.android.tools.r8.KotlinTestParameters;
 import com.android.tools.r8.SingleTestRunResult;
 import com.android.tools.r8.TestCompileResult;
+import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.kotlin.TestKotlinClass;
@@ -37,19 +40,21 @@
 public class KotlinIntrinsicsIdentifierTest extends AbstractR8KotlinNamingTestBase {
   private static final String FOLDER = "intrinsics_identifiers";
 
-  @Parameters(name = "{0}, allowAccessModification: {1}, minification: {2}")
+  @Parameters(name = "{0}, {1}, allowAccessModification: {2}, minification: {3}")
   public static Collection<Object[]> data() {
     return buildParameters(
+        getTestParameters().withAllRuntimesAndApiLevels().build(),
         getKotlinTestParameters().withAllCompilersAndTargetVersions().build(),
         BooleanUtils.values(),
         BooleanUtils.values());
   }
 
   public KotlinIntrinsicsIdentifierTest(
+      TestParameters parameters,
       KotlinTestParameters kotlinParameters,
       boolean allowAccessModification,
       boolean minification) {
-    super(kotlinParameters, allowAccessModification, minification);
+    super(parameters, kotlinParameters, allowAccessModification, minification);
   }
 
   private static final KotlinCompileMemoizer compiledJars =
@@ -58,6 +63,8 @@
 
   @Test
   public void test_example1() throws Exception {
+    // TODO(b/179866251): Update tests.
+    assumeTrue(kotlinc.is(KOTLINC_1_3_72));
     TestKotlinClass ex1 = new TestKotlinClass("intrinsics_identifiers.Example1Kt");
     String targetClassName = "ToBeRenamedClass";
     String targetFieldName = "toBeRenamedField";
@@ -67,6 +74,8 @@
 
   @Test
   public void test_example2() throws Exception {
+    // TODO(b/179866251): Update tests.
+    assumeTrue(kotlinc.is(KOTLINC_1_3_72));
     TestKotlinClass ex2 = new TestKotlinClass("intrinsics_identifiers.Example2Kt");
     String targetClassName = "AnotherClass";
     String targetFieldName = "anotherField";
@@ -76,6 +85,8 @@
 
   @Test
   public void test_example3() throws Exception {
+    // TODO(b/179866251): Update tests.
+    assumeTrue(kotlinc.is(KOTLINC_1_3_72));
     TestKotlinClass ex3 = new TestKotlinClass("intrinsics_identifiers.Example3Kt");
     String mainClassName = ex3.getClassName();
     TestCompileResult<?, ?> result =
diff --git a/src/test/java/com/android/tools/r8/naming/retrace/DesugarLambdaRetraceTest.java b/src/test/java/com/android/tools/r8/naming/retrace/DesugarLambdaRetraceTest.java
index 2836f61..f59b75d 100644
--- a/src/test/java/com/android/tools/r8/naming/retrace/DesugarLambdaRetraceTest.java
+++ b/src/test/java/com/android/tools/r8/naming/retrace/DesugarLambdaRetraceTest.java
@@ -4,6 +4,7 @@
 
 package com.android.tools.r8.naming.retrace;
 
+import static com.android.tools.r8.ir.desugar.LambdaClass.R8_LAMBDA_ACCESSOR_METHOD_PREFIX;
 import static com.android.tools.r8.naming.retrace.StackTrace.isSame;
 import static com.android.tools.r8.naming.retrace.StackTrace.isSameExceptForFileName;
 import static com.android.tools.r8.naming.retrace.StackTrace.isSameExceptForFileNameAndLineNumber;
@@ -52,7 +53,7 @@
   private int expectedActualStackTraceHeight() {
     // In DEX release the entire lambda is inlined.
     if (parameters.isDexRuntime()) {
-      return mode == CompilationMode.RELEASE ? 1 : 5;
+      return mode == CompilationMode.RELEASE ? 1 : 6;
     }
     // In CF release it is not and in debug there is no lambda desugaring thus the shorter stack.
     return mode == CompilationMode.RELEASE ? 2 : 4;
@@ -60,19 +61,22 @@
 
   private boolean isSynthesizedLambdaFrame(StackTraceLine line) {
     // TODO(141287349): The mapping should not map the external name to the internal name!
-    return SyntheticItemsTestUtils.isInternalLambda(Reference.classFromTypeName(line.className));
+    return SyntheticItemsTestUtils.isInternalLambda(Reference.classFromTypeName(line.className))
+        || line.methodName.startsWith(R8_LAMBDA_ACCESSOR_METHOD_PREFIX);
   }
 
-  private void checkLambdaFrame(StackTrace retracedStackTrace) {
+  private void checkLambdaFrames(StackTrace retracedStackTrace) {
     StackTrace lambdaFrames = retracedStackTrace.filter(this::isSynthesizedLambdaFrame);
-    assertEquals(1, lambdaFrames.size());
-    if (lambdaFrames.get(0).hasLineNumber()) {
-      assertEquals(mode == CompilationMode.RELEASE ? 0 : 2, lambdaFrames.get(0).lineNumber);
+    assertEquals(2, lambdaFrames.size());
+
+    StackTraceLine syntheticLambdaClassFrame = lambdaFrames.get(1);
+    if (syntheticLambdaClassFrame.hasLineNumber()) {
+      assertEquals(mode == CompilationMode.RELEASE ? 0 : 2, syntheticLambdaClassFrame.lineNumber);
     }
     // Proguard retrace will take the class name until the first $ to construct the file
     // name, so for "-$$Lambda$...", the file name becomes "-.java".
-    // TODO(b/141287349): Format the class name of desugard lambda classes.
-    // assertEquals("-.java", lambdaFrames.get(0).fileName);
+    // TODO(b/141287349): Format the class name of desugared lambda classes.
+    // assertEquals("-.java", syntheticLambdaClassFrame.fileName);
   }
 
   private void checkIsSame(StackTrace actualStackTrace, StackTrace retracedStackTrace) {
@@ -85,7 +89,7 @@
           retracedStackTrace.filter(line -> !isSynthesizedLambdaFrame(line)),
           isSame(expectedStackTrace));
       // Check the frame from the lambda class.
-      checkLambdaFrame(retracedStackTrace);
+      checkLambdaFrames(retracedStackTrace);
     }
     assertEquals(expectedActualStackTraceHeight(), actualStackTrace.size());
   }
@@ -96,12 +100,12 @@
     if (parameters.isCfRuntime()) {
       assertThat(retracedStackTrace, isSameExceptForFileName(expectedStackTrace));
     } else {
-      // With the frame from the lambda class filtered out the stack trace is the same.
+      // With the frames from the lambda class filtered out the stack trace is the same.
       assertThat(
           retracedStackTrace.filter(line -> !isSynthesizedLambdaFrame(line)),
           isSameExceptForFileName(expectedStackTrace));
       // Check the frame from the lambda class.
-      checkLambdaFrame(retracedStackTrace);
+      checkLambdaFrames(retracedStackTrace);
     }
     assertEquals(expectedActualStackTraceHeight(), actualStackTrace.size());
   }
@@ -117,7 +121,7 @@
           retracedStackTrace.filter(line -> !isSynthesizedLambdaFrame(line)),
           isSameExceptForFileNameAndLineNumber(expectedStackTrace));
       // Check the frame from the lambda class.
-      checkLambdaFrame(retracedStackTrace);
+      checkLambdaFrames(retracedStackTrace);
     }
     assertEquals(expectedActualStackTraceHeight(), actualStackTrace.size());
   }
diff --git a/src/test/java/com/android/tools/r8/regress/b163264839/Regress163264839Test.java b/src/test/java/com/android/tools/r8/regress/b163264839/Regress163264839Test.java
index 541ca47..1527d17 100644
--- a/src/test/java/com/android/tools/r8/regress/b163264839/Regress163264839Test.java
+++ b/src/test/java/com/android/tools/r8/regress/b163264839/Regress163264839Test.java
@@ -49,8 +49,7 @@
             .addProgramClasses(TestClass.class)
             .run(parameters.getRuntime(), TestClass.class);
 
-    if (isInterface
-        || (parameters.isCfRuntime() && parameters.getRuntime().asCf().getVm().equals(CfVm.JDK8))) {
+    if (isInterface || parameters.isCfRuntime(CfVm.JDK8)) {
       // JDK 8 allows mismatched method references in this case.
       result.assertSuccessWithOutput(EXPECTED);
     } else {
diff --git a/src/test/java/com/android/tools/r8/repackage/RepackageLambdaMissingInterfaceTest.java b/src/test/java/com/android/tools/r8/repackage/RepackageLambdaMissingInterfaceTest.java
new file mode 100644
index 0000000..622b8fe
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/repackage/RepackageLambdaMissingInterfaceTest.java
@@ -0,0 +1,89 @@
+// 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.repackage;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.R8TestRunResult;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.ToolHelper.DexVm.Version;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class RepackageLambdaMissingInterfaceTest extends RepackageTestBase {
+
+  public RepackageLambdaMissingInterfaceTest(
+      String flattenPackageHierarchyOrRepackageClasses, TestParameters parameters) {
+    super(flattenPackageHierarchyOrRepackageClasses, parameters);
+  }
+
+  @Test
+  public void testR8WithoutRepackaging() throws Exception {
+    runTest(false)
+        .assertFailureWithErrorThatThrowsIf(parameters.isDexRuntime(), AbstractMethodError.class)
+        .assertSuccessWithOutputLinesIf(parameters.isCfRuntime(), "0");
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    R8TestRunResult r8TestRunResult = runTest(true);
+    if (parameters.isDexRuntime()
+        && parameters.getDexRuntimeVersion().isOlderThanOrEqual(Version.V4_4_4)) {
+      r8TestRunResult.assertFailureWithErrorThatThrows(NoClassDefFoundError.class);
+    } else {
+      r8TestRunResult.assertFailureWithErrorThatThrows(IllegalAccessError.class);
+    }
+  }
+
+  private R8TestRunResult runTest(boolean repackage) throws Exception {
+    return testForR8(parameters.getBackend())
+        .addProgramClasses(ClassWithLambda.class, Main.class)
+        .addKeepMainRule(Main.class)
+        .applyIf(repackage, this::configureRepackaging)
+        .setMinApi(parameters.getApiLevel())
+        .addDontWarn(MissingInterface.class)
+        .noClassInlining()
+        .enableInliningAnnotations()
+        .compile()
+        .inspect(
+            inspector -> {
+              // TODO(b/179889105): This should probably not be repackaged.
+              assertThat(ClassWithLambda.class, isRepackagedIf(inspector, repackage));
+            })
+        .addRunClasspathClasses(MissingInterface.class)
+        .run(parameters.getRuntime(), Main.class);
+  }
+
+  interface MissingInterface {
+
+    void bar(int x);
+  }
+
+  public static class ClassWithLambda {
+
+    @NeverInline
+    public static void callWithLambda() {
+      Main.foo(System.out::println);
+    }
+  }
+
+  public static class Main {
+
+    private static int argCount;
+
+    @NeverInline
+    public static void foo(MissingInterface i) {
+      i.bar(argCount);
+    }
+
+    public static void main(String[] args) {
+      argCount = args.length;
+      ClassWithLambda.callWithLambda();
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/repackage/RepackageMissingMemberReferenceTest.java b/src/test/java/com/android/tools/r8/repackage/RepackageMissingMemberReferenceTest.java
new file mode 100644
index 0000000..e6f8e93
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/repackage/RepackageMissingMemberReferenceTest.java
@@ -0,0 +1,80 @@
+// 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.repackage;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.R8TestRunResult;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.ToolHelper.DexVm.Version;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class RepackageMissingMemberReferenceTest extends RepackageTestBase {
+
+  private final String EXPECTED = "MissingReference::doSomething";
+
+  public RepackageMissingMemberReferenceTest(
+      String flattenPackageHierarchyOrRepackageClasses, TestParameters parameters) {
+    super(flattenPackageHierarchyOrRepackageClasses, parameters);
+  }
+
+  @Test
+  public void testR8WithoutRepackaging() throws Exception {
+    runTest(false).assertSuccessWithOutputLines(EXPECTED);
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    R8TestRunResult r8TestRunResult = runTest(true);
+    if (parameters.isDexRuntime() && parameters.getDexRuntimeVersion() == Version.V8_1_0) {
+      r8TestRunResult.assertSuccessWithOutputLines(EXPECTED);
+    } else {
+      r8TestRunResult.assertFailureWithErrorThatThrows(IllegalAccessError.class);
+    }
+  }
+
+  private R8TestRunResult runTest(boolean repackage) throws Exception {
+    return testForR8(parameters.getBackend())
+        .addProgramClasses(ClassWithMissingReferenceInCode.class, Main.class)
+        .addKeepMainRule(Main.class)
+        .applyIf(repackage, this::configureRepackaging)
+        .setMinApi(parameters.getApiLevel())
+        .addDontWarn(MissingReference.class)
+        .enableInliningAnnotations()
+        .compile()
+        .inspect(
+            inspector -> {
+              // TODO(b/179889105): This should probably not be repackaged.
+              assertThat(
+                  ClassWithMissingReferenceInCode.class, isRepackagedIf(inspector, repackage));
+            })
+        .addRunClasspathClasses(MissingReference.class)
+        .run(parameters.getRuntime(), Main.class);
+  }
+
+  static class MissingReference {
+    public static void doSomething() {
+      System.out.println("MissingReference::doSomething");
+    }
+  }
+
+  public static class ClassWithMissingReferenceInCode {
+
+    @NeverInline
+    public static void test() {
+      MissingReference.doSomething();
+    }
+  }
+
+  public static class Main {
+    public static void main(String[] args) {
+      ClassWithMissingReferenceInCode.test();
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/repackage/RepackageMissingSuperInterfaceTestTest.java b/src/test/java/com/android/tools/r8/repackage/RepackageMissingSuperInterfaceTestTest.java
new file mode 100644
index 0000000..8f08c58
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/repackage/RepackageMissingSuperInterfaceTestTest.java
@@ -0,0 +1,83 @@
+// 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.repackage;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.R8TestRunResult;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.ToolHelper.DexVm.Version;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class RepackageMissingSuperInterfaceTestTest extends RepackageTestBase {
+
+  public RepackageMissingSuperInterfaceTestTest(
+      String flattenPackageHierarchyOrRepackageClasses, TestParameters parameters) {
+    super(flattenPackageHierarchyOrRepackageClasses, parameters);
+  }
+
+  @Test
+  public void testR8WithoutRepackaging() throws Exception {
+    runTest(false).assertSuccessWithOutputLines("ClassImplementingMissingInterface::bar");
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    R8TestRunResult r8TestRunResult = runTest(true);
+    if (parameters.isDexRuntime()
+        && parameters.getDexRuntimeVersion().isOlderThanOrEqual(Version.V4_4_4)) {
+      r8TestRunResult.assertFailureWithErrorThatThrows(NoClassDefFoundError.class);
+    } else {
+      r8TestRunResult.assertFailureWithErrorThatThrows(IllegalAccessError.class);
+    }
+  }
+
+  private R8TestRunResult runTest(boolean repackage) throws Exception {
+    return testForR8(parameters.getBackend())
+        .addProgramClasses(ClassImplementingMissingInterface.class, Main.class)
+        .addKeepMainRule(Main.class)
+        .applyIf(repackage, this::configureRepackaging)
+        .setMinApi(parameters.getApiLevel())
+        .addDontWarn(MissingInterface.class)
+        .enableInliningAnnotations()
+        .enableNeverClassInliningAnnotations()
+        .compile()
+        .inspect(
+            inspector -> {
+              // TODO(b/179889105): This should probably not be repackaged.
+              assertThat(
+                  ClassImplementingMissingInterface.class, isRepackagedIf(inspector, repackage));
+            })
+        .addRunClasspathClasses(MissingInterface.class)
+        .run(parameters.getRuntime(), Main.class);
+  }
+
+  private interface MissingInterface {
+
+    void bar();
+  }
+
+  @NeverClassInline
+  public static class ClassImplementingMissingInterface implements MissingInterface {
+
+    @Override
+    @NeverInline
+    public void bar() {
+      System.out.println("ClassImplementingMissingInterface::bar");
+    }
+  }
+
+  public static class Main {
+
+    public static void main(String[] args) {
+      new ClassImplementingMissingInterface().bar();
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/repackage/RepackageMissingSuperTypeTest.java b/src/test/java/com/android/tools/r8/repackage/RepackageMissingSuperTypeTest.java
new file mode 100644
index 0000000..cb2ce41
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/repackage/RepackageMissingSuperTypeTest.java
@@ -0,0 +1,110 @@
+// 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.repackage;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.R8TestRunResult;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.ToolHelper.DexVm.Version;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class RepackageMissingSuperTypeTest extends RepackageTestBase {
+
+  public RepackageMissingSuperTypeTest(
+      String flattenPackageHierarchyOrRepackageClasses, TestParameters parameters) {
+    super(flattenPackageHierarchyOrRepackageClasses, parameters);
+  }
+
+  @Test
+  public void testR8WithoutRepackaging() throws Exception {
+    runTest(false)
+        .assertSuccessWithOutputLines(
+            "ClassWithSuperCall::foo",
+            "MissingSuperType::foo",
+            "ClassWithoutSuperCall::foo",
+            "MissingSuperType::foo");
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    R8TestRunResult r8TestRunResult = runTest(true);
+    if (parameters.isDexRuntime()
+        && parameters.getDexRuntimeVersion().isOlderThanOrEqual(Version.V4_4_4)) {
+      r8TestRunResult.assertFailureWithErrorThatThrows(NoClassDefFoundError.class);
+    } else {
+      r8TestRunResult.assertFailureWithErrorThatThrows(IllegalAccessError.class);
+    }
+  }
+
+  private R8TestRunResult runTest(boolean repackage) throws Exception {
+    return testForR8(parameters.getBackend())
+        .addProgramClasses(
+            ClassWithSuperCall.class,
+            ClassWithoutSuperCall.class,
+            ClassWithoutDefinition.class,
+            Main.class)
+        .addKeepMainRule(Main.class)
+        .applyIf(repackage, this::configureRepackaging)
+        .setMinApi(parameters.getApiLevel())
+        .addDontWarn(MissingSuperType.class)
+        .enableInliningAnnotations()
+        .enableNeverClassInliningAnnotations()
+        .compile()
+        .inspect(
+            inspector -> {
+              // TODO(b/179889105): These should probably not be repackaged.
+              assertThat(ClassWithSuperCall.class, isRepackagedIf(inspector, repackage));
+              assertThat(ClassWithoutSuperCall.class, isRepackagedIf(inspector, repackage));
+            })
+        .addRunClasspathClasses(MissingSuperType.class)
+        .run(parameters.getRuntime(), Main.class);
+  }
+
+  static class MissingSuperType {
+
+    public void foo() {
+      System.out.println("MissingSuperType::foo");
+    }
+  }
+
+  @NeverClassInline
+  public static class ClassWithSuperCall extends MissingSuperType {
+
+    @Override
+    @NeverInline
+    public void foo() {
+      System.out.println("ClassWithSuperCall::foo");
+      super.foo();
+    }
+  }
+
+  @NeverClassInline
+  public static class ClassWithoutSuperCall extends MissingSuperType {
+
+    @Override
+    @NeverInline
+    public void foo() {
+      System.out.println("ClassWithoutSuperCall::foo");
+    }
+  }
+
+  @NeverClassInline
+  public static class ClassWithoutDefinition extends MissingSuperType {}
+
+  public static class Main {
+
+    public static void main(String[] args) {
+      new ClassWithSuperCall().foo();
+      new ClassWithoutSuperCall().foo();
+      new ClassWithoutDefinition().foo();
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/repackage/RepackageParameterSyntheticOutlineTest.java b/src/test/java/com/android/tools/r8/repackage/RepackageParameterSyntheticOutlineTest.java
new file mode 100644
index 0000000..0636a50
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/repackage/RepackageParameterSyntheticOutlineTest.java
@@ -0,0 +1,139 @@
+// 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.repackage;
+
+import static com.android.tools.r8.DiagnosticsMatcher.diagnosticMessage;
+import static com.android.tools.r8.shaking.ProguardConfigurationParser.REPACKAGE_CLASSES;
+import static org.hamcrest.CoreMatchers.containsString;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.utils.DescriptorUtils;
+import com.google.common.collect.ImmutableList;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class RepackageParameterSyntheticOutlineTest extends RepackageTestBase {
+
+  private final String NEW_DESCRIPTOR = "Lfoo/ClassWithCodeToBeOutlined;";
+  private final String[] EXPECTED =
+      new String[] {"Param::testParam", "Return::print", "Param::testParam", "Return::print"};
+
+  @Parameters(name = "{1}, kind: {0}")
+  public static List<Object[]> data() {
+    return buildParameters(
+        ImmutableList.of(REPACKAGE_CLASSES), // Repackage will use foo as the package name.
+        getTestParameters().withDexRuntimes().withAllApiLevels().build());
+  }
+
+  public RepackageParameterSyntheticOutlineTest(
+      String flattenPackageHierarchyOrRepackageClasses, TestParameters parameters) {
+    super(flattenPackageHierarchyOrRepackageClasses, parameters);
+  }
+
+  @Test
+  public void testRuntime() throws Exception {
+    testForRuntime(parameters)
+        .addProgramClasses(Param.class, Return.class)
+        .addProgramClassFileData(
+            rewrittenPackageForClassWithCodeToBeOutlined(),
+            rewrittenMainWithMethodReferencesToCodeToBeOutlined())
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines(EXPECTED);
+  }
+
+  @Test(expected = CompilationFailedException.class)
+  public void testR8() throws Exception {
+    testForR8(parameters.getBackend())
+        .addProgramClasses(Param.class, Return.class)
+        .addProgramClassFileData(
+            rewrittenPackageForClassWithCodeToBeOutlined(),
+            rewrittenMainWithMethodReferencesToCodeToBeOutlined())
+        .addKeepMainRule(Main.class)
+        .addKeepClassRulesWithAllowObfuscation(Param.class, Return.class)
+        .setMinApi(parameters.getApiLevel())
+        .enableInliningAnnotations()
+        .addOptionsModification(
+            options -> {
+              options.outline.minSize = 2;
+              options.outline.threshold = 2;
+            })
+        .apply(this::configureRepackaging)
+        .addKeepPackageNamesRule("bar**")
+        .compileWithExpectedDiagnostics(
+            diagnostics -> {
+              // TODO(b/180092122): This should not fail.
+              diagnostics.assertErrorsMatch(
+                  diagnosticMessage(
+                      containsString("The synthetic method reference should have moved")));
+            });
+  }
+
+  private byte[] rewrittenPackageForClassWithCodeToBeOutlined() throws Exception {
+    return transformer(ClassWithCodeToBeOutlined.class)
+        .setClassDescriptor(NEW_DESCRIPTOR)
+        .transform();
+  }
+
+  private byte[] rewrittenMainWithMethodReferencesToCodeToBeOutlined() throws Exception {
+    return transformer(Main.class)
+        .replaceClassDescriptorInMethodInstructions(
+            DescriptorUtils.javaTypeToDescriptor(ClassWithCodeToBeOutlined.class.getTypeName()),
+            NEW_DESCRIPTOR)
+        .transform();
+  }
+
+  // Will be renamed by repackaging
+  public static class Param {
+
+    @NeverInline
+    public void testParam() {
+      System.out.println("Param::testParam");
+    }
+
+    @NeverInline
+    public Return getReturn() {
+      return new Return();
+    }
+  }
+
+  // Will be renamed by repackaging
+  public static class Return {
+
+    public void print() {
+      System.out.println("Return::print");
+    }
+  }
+
+  // Renamed to baz.ClassWithCodeToBeOutlined such that we can keep this package name.
+  public static class ClassWithCodeToBeOutlined {
+
+    @NeverInline
+    public static Return foo(Param param) {
+      param.testParam();
+      return param.getReturn();
+    }
+
+    @NeverInline
+    public static Return bar(Param param) {
+      param.testParam();
+      return param.getReturn();
+    }
+  }
+
+  public static class Main {
+
+    public static void main(String[] args) {
+      Param param = new Param();
+      ClassWithCodeToBeOutlined.foo(param).print();
+      ClassWithCodeToBeOutlined.bar(param).print();
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/repackage/RepackageWithSyntheticItemTest.java b/src/test/java/com/android/tools/r8/repackage/RepackageWithSyntheticItemTest.java
new file mode 100644
index 0000000..4df6622
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/repackage/RepackageWithSyntheticItemTest.java
@@ -0,0 +1,104 @@
+// 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.repackage;
+
+import static com.android.tools.r8.shaking.ProguardConfigurationParser.FLATTEN_PACKAGE_HIERARCHY;
+import static com.android.tools.r8.shaking.ProguardConfigurationParser.REPACKAGE_CLASSES;
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.utils.codeinspector.FoundClassSubject;
+import com.google.common.collect.ImmutableList;
+import java.util.List;
+import java.util.stream.Collectors;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class RepackageWithSyntheticItemTest extends RepackageTestBase {
+
+  @Parameters(name = "{1}, kind: {0}")
+  public static List<Object[]> data() {
+    return buildParameters(
+        ImmutableList.of(FLATTEN_PACKAGE_HIERARCHY, REPACKAGE_CLASSES),
+        getTestParameters().withDexRuntimes().withAllApiLevels().build());
+  }
+
+  public RepackageWithSyntheticItemTest(
+      String flattenPackageHierarchyOrRepackageClasses, TestParameters parameters) {
+    super(flattenPackageHierarchyOrRepackageClasses, parameters);
+  }
+
+  @Test
+  public void testRuntime() throws Exception {
+    testForRuntime(parameters)
+        .addInnerClasses(RepackageWithSyntheticItemTest.class)
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines("0");
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(RepackageWithSyntheticItemTest.class)
+        .addKeepMainRule(Main.class)
+        .addKeepClassRules(I.class)
+        .setMinApi(parameters.getApiLevel())
+        .apply(this::configureRepackaging)
+        .noClassInlining()
+        .addInliningAnnotations()
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines("0")
+        .inspect(
+            inspector -> {
+              // Find the lambda class that starts with foo.
+              List<FoundClassSubject> classesStartingWithfoo =
+                  inspector.allClasses().stream()
+                      .filter(item -> item.getFinalName().startsWith("foo"))
+                      .collect(Collectors.toList());
+              assertEquals(1, classesStartingWithfoo.size());
+              // TODO(b/172014416): We should not be able to look this up through the repackage name
+              String expectedOriginalNamePrefix =
+                  isFlattenPackageHierarchy()
+                      ? "foo.repackage.RepackageWithSyntheticItemTest$A"
+                      : "foo.RepackageWithSyntheticItemTest$A";
+              assertThat(
+                  classesStartingWithfoo.get(0).getOriginalName(),
+                  containsString(expectedOriginalNamePrefix));
+            });
+  }
+
+  public static class A {
+
+    @NeverInline
+    public static void testLambda() {
+      Main.test(System.out::println);
+    }
+  }
+
+  public interface I {
+    void foo(int x);
+  }
+
+  public static class Main {
+
+    private static int argCount = 0;
+
+    @NeverInline
+    public static void test(I i) {
+      i.foo(argCount);
+    }
+
+    public static void main(String[] args) {
+      argCount = args.length;
+      A.testLambda();
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/resolution/access/NestInvokeSpecialInterfaceMethodAccessWithIntermediateTest.java b/src/test/java/com/android/tools/r8/resolution/access/NestInvokeSpecialInterfaceMethodAccessWithIntermediateTest.java
index 07487ec..af6e166 100644
--- a/src/test/java/com/android/tools/r8/resolution/access/NestInvokeSpecialInterfaceMethodAccessWithIntermediateTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/access/NestInvokeSpecialInterfaceMethodAccessWithIntermediateTest.java
@@ -171,10 +171,6 @@
         .addProgramClassFileData(getTransformedClasses())
         .setMinApi(parameters.getApiLevel())
         .addKeepMainRule(Main.class)
-        .applyIf(
-            !parameters.canUseDefaultAndStaticInterfaceMethods()
-                && !symbolicReferenceIsDefiningType,
-            builder -> builder.addDontWarnCompanionClass(J.class))
         .run(parameters.getRuntime(), Main.class)
         .apply(result -> checkExpectedResult(result, true));
   }
@@ -185,19 +181,11 @@
           "TODO(b/144410139): Input does not verify. Should compilation throw an error?",
           parameters.isCfRuntime() && !isR8);
       result.assertFailureWithErrorThatMatches(containsString(VerifyError.class.getName()));
-    } else if (isDesugaring()) {
-      // TODO(b/145775365): Desugaring results in a reference to a non-existent companion class.
-      result.assertFailureWithErrorThatMatches(
-          containsString(NoClassDefFoundError.class.getName()));
     } else {
       result.assertFailureWithErrorThatMatches(containsString(NoSuchMethodError.class.getName()));
     }
   }
 
-  private boolean isDesugaring() {
-    return parameters.isDexRuntime() && parameters.getApiLevel().isLessThan(AndroidApiLevel.N);
-  }
-
   interface I {
     /* will be private */ default void bar() {
       System.out.println("I::bar");
diff --git a/src/test/java/com/android/tools/r8/retrace/KotlinInlineFunctionInSameFileRetraceTests.java b/src/test/java/com/android/tools/r8/retrace/KotlinInlineFunctionInSameFileRetraceTests.java
index 2479dce..b16dc35 100644
--- a/src/test/java/com/android/tools/r8/retrace/KotlinInlineFunctionInSameFileRetraceTests.java
+++ b/src/test/java/com/android/tools/r8/retrace/KotlinInlineFunctionInSameFileRetraceTests.java
@@ -12,9 +12,10 @@
 import static org.hamcrest.CoreMatchers.equalTo;
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.hamcrest.core.StringContains.containsString;
+import static org.junit.Assume.assumeTrue;
 
-import com.android.tools.r8.CompilationFailedException;
 import com.android.tools.r8.CompilationMode;
+import com.android.tools.r8.KotlinCompilerTool.KotlinCompilerVersion;
 import com.android.tools.r8.KotlinTestBase;
 import com.android.tools.r8.KotlinTestParameters;
 import com.android.tools.r8.TestParameters;
@@ -28,7 +29,6 @@
 import java.nio.file.Path;
 import java.util.Collection;
 import java.util.List;
-import java.util.concurrent.ExecutionException;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
@@ -42,7 +42,7 @@
 
   private final TestParameters parameters;
 
-  @Parameters(name = "{0}, {1}")
+  @Parameters(name = "{0}, target: {1}")
   public static List<Object[]> data() {
     // TODO(b/141817471): Extend with compilation modes.
     return buildParameters(
@@ -69,7 +69,9 @@
   }
 
   @Test
-  public void testRuntime() throws ExecutionException, CompilationFailedException, IOException {
+  public void testRuntime() throws Exception {
+    // TODO(b/179666509): SMAP has changed.
+    assumeTrue(kotlinc.is(KotlinCompilerVersion.KOTLINC_1_3_72));
     testForRuntime(parameters)
         .addProgramFiles(compilationResults.getForConfiguration(kotlinc, targetVersion))
         .addRunClasspathFiles(buildOnDexRuntime(parameters, ToolHelper.getKotlinStdlibJar(kotlinc)))
@@ -81,8 +83,9 @@
   }
 
   @Test
-  public void testRetraceKotlinInlineStaticFunction()
-      throws ExecutionException, CompilationFailedException, IOException {
+  public void testRetraceKotlinInlineStaticFunction() throws Exception {
+    // TODO(b/179666509): SMAP has changed.
+    assumeTrue(kotlinc.is(KotlinCompilerVersion.KOTLINC_1_3_72));
     Path kotlinSources = compilationResults.getForConfiguration(kotlinc, targetVersion);
     CodeInspector kotlinInspector = new CodeInspector(kotlinSources);
     testForR8(parameters.getBackend())
diff --git a/src/test/java/com/android/tools/r8/retrace/KotlinInlineFunctionRetraceTest.java b/src/test/java/com/android/tools/r8/retrace/KotlinInlineFunctionRetraceTest.java
index 945b732..ffc5515 100644
--- a/src/test/java/com/android/tools/r8/retrace/KotlinInlineFunctionRetraceTest.java
+++ b/src/test/java/com/android/tools/r8/retrace/KotlinInlineFunctionRetraceTest.java
@@ -13,9 +13,10 @@
 import static org.hamcrest.CoreMatchers.equalTo;
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.hamcrest.core.StringContains.containsString;
+import static org.junit.Assume.assumeTrue;
 
-import com.android.tools.r8.CompilationFailedException;
 import com.android.tools.r8.CompilationMode;
+import com.android.tools.r8.KotlinCompilerTool.KotlinCompilerVersion;
 import com.android.tools.r8.KotlinTestBase;
 import com.android.tools.r8.KotlinTestParameters;
 import com.android.tools.r8.TestParameters;
@@ -30,7 +31,6 @@
 import java.nio.file.Path;
 import java.util.Collection;
 import java.util.List;
-import java.util.concurrent.ExecutionException;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
@@ -84,7 +84,7 @@
   }
 
   @Test
-  public void testRuntime() throws ExecutionException, CompilationFailedException, IOException {
+  public void testRuntime() throws Exception {
     testForRuntime(parameters)
         .addProgramFiles(compilationResults.getForConfiguration(kotlinc, targetVersion))
         .addRunClasspathFiles(buildOnDexRuntime(parameters, ToolHelper.getKotlinStdlibJar(kotlinc)))
@@ -94,8 +94,9 @@
   }
 
   @Test
-  public void testRetraceKotlinInlineStaticFunction()
-      throws ExecutionException, CompilationFailedException, IOException {
+  public void testRetraceKotlinInlineStaticFunction() throws Exception {
+    // TODO(b/179666509): SMAP has changed.
+    assumeTrue(kotlinc.is(KotlinCompilerVersion.KOTLINC_1_3_72));
     String main = "retrace.MainKt";
     String mainFileName = "Main.kt";
     Path kotlinSources = compilationResults.getForConfiguration(kotlinc, targetVersion);
@@ -125,8 +126,9 @@
   }
 
   @Test
-  public void testRetraceKotlinInlineInstanceFunction()
-      throws ExecutionException, CompilationFailedException, IOException {
+  public void testRetraceKotlinInlineInstanceFunction() throws Exception {
+    // TODO(b/179666509): SMAP has changed.
+    assumeTrue(kotlinc.is(KotlinCompilerVersion.KOTLINC_1_3_72));
     String main = "retrace.MainInstanceKt";
     String mainFileName = "MainInstance.kt";
     Path kotlinSources = compilationResults.getForConfiguration(kotlinc, targetVersion);
@@ -159,8 +161,9 @@
   }
 
   @Test
-  public void testRetraceKotlinNestedInlineFunction()
-      throws ExecutionException, CompilationFailedException, IOException {
+  public void testRetraceKotlinNestedInlineFunction() throws Exception {
+    // TODO(b/179666509): SMAP has changed.
+    assumeTrue(kotlinc.is(KotlinCompilerVersion.KOTLINC_1_3_72));
     String main = "retrace.MainNestedKt";
     String mainFileName = "MainNested.kt";
     Path kotlinSources = compilationResults.getForConfiguration(kotlinc, targetVersion);
@@ -192,8 +195,9 @@
   }
 
   @Test
-  public void testRetraceKotlinNestedInlineFunctionOnFirstLine()
-      throws ExecutionException, CompilationFailedException, IOException {
+  public void testRetraceKotlinNestedInlineFunctionOnFirstLine() throws Exception {
+    // TODO(b/179666509): SMAP has changed.
+    assumeTrue(kotlinc.is(KotlinCompilerVersion.KOTLINC_1_3_72));
     String main = "retrace.MainNestedFirstLineKt";
     String mainFileName = "MainNestedFirstLine.kt";
     Path kotlinSources = compilationResults.getForConfiguration(kotlinc, targetVersion);
diff --git a/src/test/java/com/android/tools/r8/rewrite/assertions/AssertionConfigurationKotlinTest.java b/src/test/java/com/android/tools/r8/rewrite/assertions/AssertionConfigurationKotlinTest.java
index 2cc7c96..0298ecd 100644
--- a/src/test/java/com/android/tools/r8/rewrite/assertions/AssertionConfigurationKotlinTest.java
+++ b/src/test/java/com/android/tools/r8/rewrite/assertions/AssertionConfigurationKotlinTest.java
@@ -161,6 +161,7 @@
               b.addRunClasspathFiles(kotlinStdlibLibraryForRuntime());
             },
             b -> b.addProgramFiles(ToolHelper.getKotlinStdlibJar(kotlinc)))
+        .addClasspathFiles(ToolHelper.getKotlinAnnotationJar(kotlinc))
         .addProgramFiles(compiledForAssertions.getForConfiguration(kotlinc, targetVersion))
         .addKeepMainRule(testClassKt)
         .addKeepClassAndMembersRules(class1, class2)
diff --git a/src/test/java/com/android/tools/r8/shaking/TreeShakingSpecificTest.java b/src/test/java/com/android/tools/r8/shaking/TreeShakingSpecificTest.java
index 7ba46d2..63c9f40 100644
--- a/src/test/java/com/android/tools/r8/shaking/TreeShakingSpecificTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/TreeShakingSpecificTest.java
@@ -13,7 +13,9 @@
 import com.android.tools.r8.OutputMode;
 import com.android.tools.r8.R8;
 import com.android.tools.r8.R8Command;
-import com.android.tools.r8.TestBase.Backend;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersBuilder;
 import com.android.tools.r8.ToolHelper;
 import java.io.BufferedReader;
 import java.io.IOException;
@@ -23,8 +25,7 @@
 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.List;
 import java.util.stream.Collectors;
 import java.util.stream.Stream;
 import org.junit.Rule;
@@ -36,17 +37,19 @@
 import org.junit.runners.Parameterized.Parameters;
 
 @RunWith(Parameterized.class)
-public class TreeShakingSpecificTest {
+public class TreeShakingSpecificTest extends TestBase {
 
   private Backend backend;
 
-  @Parameters(name = "Backend: {0}")
-  public static Collection<Backend> data() {
-    return Arrays.asList(Backend.values());
+  @Parameters(name = "Backend: {1}")
+  public static List<Object[]> data() {
+    return buildParameters(
+        TestParametersBuilder.builder().withNoneRuntime().build(), Backend.values());
   }
 
-  public TreeShakingSpecificTest(Backend backend) {
+  public TreeShakingSpecificTest(TestParameters parameters, Backend backend) {
     this.backend = backend;
+    parameters.assertNoneRuntime();
   }
 
   private static final String VALID_PROGUARD_DIR = "src/test/proguard/valid/";
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 2f098bf..f828cfc 100644
--- a/src/test/java/com/android/tools/r8/transformers/ClassFileTransformer.java
+++ b/src/test/java/com/android/tools/r8/transformers/ClassFileTransformer.java
@@ -31,12 +31,14 @@
 import java.util.Arrays;
 import java.util.List;
 import java.util.function.Consumer;
+import java.util.function.Function;
 import java.util.stream.Collectors;
 import org.objectweb.asm.AnnotationVisitor;
 import org.objectweb.asm.ClassReader;
 import org.objectweb.asm.ClassVisitor;
 import org.objectweb.asm.ClassWriter;
 import org.objectweb.asm.FieldVisitor;
+import org.objectweb.asm.Handle;
 import org.objectweb.asm.Label;
 import org.objectweb.asm.MethodVisitor;
 import org.objectweb.asm.Type;
@@ -330,6 +332,10 @@
   }
 
   public ClassFileTransformer setGenericSignature(String newGenericSignature) {
+    return setGenericSignature(signature -> newGenericSignature);
+  }
+
+  public ClassFileTransformer setGenericSignature(Function<String, String> newGenericSignature) {
     return addClassTransformer(
         new ClassTransformer() {
           @Override
@@ -340,7 +346,8 @@
               String signature,
               String superName,
               String[] interfaces) {
-            super.visit(version, access, name, newGenericSignature, superName, interfaces);
+            super.visit(
+                version, access, name, newGenericSignature.apply(signature), superName, interfaces);
           }
         });
   }
@@ -586,6 +593,21 @@
         });
   }
 
+  public ClassFileTransformer setGenericSignature(
+      MethodPredicate predicate, Function<String, String> newSignature) {
+    return addClassTransformer(
+        new ClassTransformer() {
+          @Override
+          public MethodVisitor visitMethod(
+              int access, String name, String descriptor, String signature, String[] exceptions) {
+            return predicate.test(access, name, descriptor, signature, exceptions)
+                ? super.visitMethod(
+                    access, name, descriptor, newSignature.apply(signature), exceptions)
+                : super.visitMethod(access, name, descriptor, signature, exceptions);
+          }
+        });
+  }
+
   public ClassFileTransformer removeFields(FieldPredicate predicate) {
     return addClassTransformer(
         new ClassTransformer() {
@@ -649,6 +671,17 @@
         });
   }
 
+  /** Abstraction of the MethodVisitor.visitInvokeDynamicInsn method with its sub visitor. */
+  @FunctionalInterface
+  public interface InvokeDynamicInsnTransform {
+    void visitInvokeDynamicInsn(
+        String name,
+        String descriptor,
+        Handle bootstrapMethodHandle,
+        List<Object> bootstrapMethodArguments,
+        MethodVisitor visitor);
+  }
+
   /** Abstraction of the MethodVisitor.visitMethodInsn method with its sub visitor. */
   @FunctionalInterface
   public interface MethodInsnTransform {
@@ -742,6 +775,55 @@
   }
 
   @FunctionalInterface
+  private interface VisitInvokeDynamicInsnCallback {
+    void visitInvokeDynamicInsn(
+        String name,
+        String descriptor,
+        Handle bootstrapMethodHandle,
+        Object... bootstrapMethodArguments);
+  }
+
+  private MethodVisitor redirectVisitInvokeDynamicInsn(
+      MethodVisitor visitor, VisitInvokeDynamicInsnCallback callback) {
+    return new MethodVisitor(ASM7, visitor) {
+      @Override
+      public void visitInvokeDynamicInsn(
+          String name,
+          String descriptor,
+          Handle bootstrapMethodHandle,
+          Object... bootstrapMethodArguments) {
+        callback.visitInvokeDynamicInsn(
+            name, descriptor, bootstrapMethodHandle, bootstrapMethodArguments);
+      }
+    };
+  }
+
+  public ClassFileTransformer transformInvokeDynamicInsnInMethod(
+      String methodName, InvokeDynamicInsnTransform transform) {
+    return addMethodTransformer(
+        new MethodTransformer() {
+          @Override
+          public void visitInvokeDynamicInsn(
+              String name,
+              String descriptor,
+              Handle bootstrapMethodHandle,
+              Object... bootstrapMethodArguments) {
+            if (getContext().method.getMethodName().equals(methodName)) {
+              transform.visitInvokeDynamicInsn(
+                  name,
+                  descriptor,
+                  bootstrapMethodHandle,
+                  Arrays.asList(bootstrapMethodArguments),
+                  redirectVisitInvokeDynamicInsn(this, super::visitInvokeDynamicInsn));
+            } else {
+              super.visitInvokeDynamicInsn(
+                  name, descriptor, bootstrapMethodHandle, bootstrapMethodArguments);
+            }
+          }
+        });
+  }
+
+  @FunctionalInterface
   private interface VisitMethodInsnCallback {
     void visitMethodInsn(
         int opcode, String owner, String name, String descriptor, boolean isInterface);
diff --git a/tools/archive_desugar_jdk_libs.py b/tools/archive_desugar_jdk_libs.py
index 343a7f2..403212f 100755
--- a/tools/archive_desugar_jdk_libs.py
+++ b/tools/archive_desugar_jdk_libs.py
@@ -44,6 +44,9 @@
       help='GitHub account to clone from.',
       default="google",
       type="string", action="store")
+  result.add_option('--build_only', '--build-only',
+      help='Build desugared library without archiving.',
+      type="string", action="store")
   (options, args) = result.parse_args(argv)
   return (options, args)
 
@@ -77,20 +80,44 @@
     print('File available at: %s' %
         destination.replace('gs://', 'https://storage.googleapis.com/', 1))
 
+def CloneDesugaredLibrary(github_account, checkout_dir):
+  git_utils.GitClone(
+    'https://github.com/'
+        + github_account + '/' + LIBRARY_NAME, checkout_dir)
+
+def BuildDesugaredLibrary(checkout_dir):
+  with utils.ChangedWorkingDirectory(checkout_dir):
+    bazel = os.path.join(utils.BAZEL_TOOL, 'lib', 'bazel', 'bin', 'bazel')
+    cmd = [bazel, 'build', 'maven_release']
+    utils.PrintCmd(cmd)
+    subprocess.check_call(cmd)
+    cmd = [bazel, 'shutdown']
+    utils.PrintCmd(cmd)
+    subprocess.check_call(cmd)
+
+    # Locate the library jar and the maven zip with the jar from the
+    # bazel build.
+    library_jar = os.path.join(
+        checkout_dir, 'bazel-bin', 'src', 'share', 'classes', 'java', 'libjava.jar')
+    maven_zip = os.path.join(checkout_dir, 'bazel-bin', LIBRARY_NAME +'.zip')
+    return (library_jar, maven_zip)
+
+
+def MustBeExistingDirectory(path):
+  if (not os.path.exists(path) or not os.path.isdir(path)):
+    raise Exception(path + ' does not exist or is not a directory')
 
 def Main(argv):
   (options, args) = ParseOptions(argv)
   if (len(args) > 0):
     raise Exception('Unsupported arguments')
-  if not utils.is_bot() and not options.dry_run:
+  if not utils.is_bot() and not (options.dry_run or options.build_only):
     raise Exception('You are not a bot, don\'t archive builds. '
-        + 'Use --dry-run to test locally')
-  if (options.dry_run_output and
-      (not os.path.exists(options.dry_run_output) or
-       not os.path.isdir(options.dry_run_output))):
-    raise Exception(options.dry_run_output
-        + ' does not exist or is not a directory')
-
+        + 'Use --dry-run or --build-only to test locally')
+  if options.dry_run_output:
+     MustBeExistingDirectory(options.dry_run_output)
+  if options.build_only:
+     MustBeExistingDirectory(options.build_only)
   if utils.is_bot():
     archive.SetRLimitToMax()
 
@@ -98,57 +125,54 @@
   utils.DownloadFromGoogleCloudStorage(utils.BAZEL_SHA_FILE)
   utils.DownloadFromGoogleCloudStorage(utils.JAVA8_SHA_FILE)
 
+  if options.build_only:
+    with utils.TempDir() as checkout_dir:
+      CloneDesugaredLibrary(options.github_account, checkout_dir)
+      (library_jar, maven_zip) = BuildDesugaredLibrary(checkout_dir)
+      shutil.copyfile(
+        library_jar,
+        os.path.join(options.build_only, os.path.basename(library_jar)))
+      shutil.copyfile(
+        maven_zip,
+        os.path.join(options.build_only, os.path.basename(maven_zip)))
+      return
+
   # Only handling versioned desugar_jdk_libs.
   is_master = False
 
   with utils.TempDir() as checkout_dir:
-    git_utils.GitClone(
-      'https://github.com/'
-          + options.github_account + '/' + LIBRARY_NAME, checkout_dir)
-    with utils.ChangedWorkingDirectory(checkout_dir):
-      version = GetVersion(VERSION_FILE)
+    CloneDesugaredLibrary(options.github_account, checkout_dir)
+    version = GetVersion(os.path.join(checkout_dir, VERSION_FILE))
 
-      destination = archive.GetVersionDestination(
-          'gs://', LIBRARY_NAME + '/' + version, is_master)
-      if utils.cloud_storage_exists(destination) and not options.dry_run:
-        raise Exception(
-            'Target archive directory %s already exists' % destination)
+    destination = archive.GetVersionDestination(
+        'gs://', LIBRARY_NAME + '/' + version, is_master)
+    if utils.cloud_storage_exists(destination) and not options.dry_run:
+      raise Exception(
+          'Target archive directory %s already exists' % destination)
 
-      bazel = os.path.join(utils.BAZEL_TOOL, 'lib', 'bazel', 'bin', 'bazel')
-      cmd = [bazel, 'build', 'maven_release']
-      utils.PrintCmd(cmd)
-      subprocess.check_call(cmd)
-      cmd = [bazel, 'shutdown']
-      utils.PrintCmd(cmd)
-      subprocess.check_call(cmd)
+    (library_jar, maven_zip) = BuildDesugaredLibrary(checkout_dir)
 
-      # Locate the library jar and the maven zip with the jar from the
-      # bazel build.
-      library_jar = os.path.join(
-          'bazel-bin', 'src', 'share', 'classes', 'java', 'libjava.jar')
-      maven_zip = os.path.join('bazel-bin', LIBRARY_NAME +'.zip')
+    storage_path = LIBRARY_NAME + '/' + version
+    # Upload the jar file with the library.
+    destination = archive.GetUploadDestination(
+        storage_path, LIBRARY_NAME + '.jar', is_master)
+    Upload(options, library_jar, storage_path, destination, is_master)
 
-      storage_path = LIBRARY_NAME + '/' + version
-      # Upload the jar file with the library.
-      destination = archive.GetUploadDestination(
-          storage_path, LIBRARY_NAME + '.jar', is_master)
-      Upload(options, library_jar, storage_path, destination, is_master)
+    # Upload the maven zip file with the library.
+    destination = archive.GetUploadDestination(
+        storage_path, LIBRARY_NAME + '.zip', is_master)
+    Upload(options, maven_zip, storage_path, destination, is_master)
 
-      # Upload the maven zip file with the library.
-      destination = archive.GetUploadDestination(
-          storage_path, LIBRARY_NAME + '.zip', is_master)
-      Upload(options, maven_zip, storage_path, destination, is_master)
-
-      # Upload the jar file for accessing GCS as a maven repro.
-      maven_destination = archive.GetUploadDestination(
-          utils.get_maven_path('desugar_jdk_libs', version),
-          'desugar_jdk_libs-%s.jar' % version,
-          is_master)
-      if options.dry_run:
-        print('Dry run, not actually creating maven repo')
-      else:
-        utils.upload_file_to_cloud_storage(library_jar, maven_destination)
-        print('Maven repo root available at: %s' % archive.GetMavenUrl(is_master))
+    # Upload the jar file for accessing GCS as a maven repro.
+    maven_destination = archive.GetUploadDestination(
+        utils.get_maven_path('desugar_jdk_libs', version),
+        'desugar_jdk_libs-%s.jar' % version,
+        is_master)
+    if options.dry_run:
+      print('Dry run, not actually creating maven repo')
+    else:
+      utils.upload_file_to_cloud_storage(library_jar, maven_destination)
+      print('Maven repo root available at: %s' % archive.GetMavenUrl(is_master))
 
 
 if __name__ == '__main__':
diff --git a/tools/test.py b/tools/test.py
index dffc4565..546a25f 100755
--- a/tools/test.py
+++ b/tools/test.py
@@ -16,6 +16,7 @@
 import time
 import uuid
 
+import archive_desugar_jdk_libs
 import gradle
 import notify
 import utils
@@ -161,6 +162,8 @@
       help='Enable Java debug agent and suspend compilation (default disabled)',
       default=False,
       action='store_true')
+  result.add_option('--desugared-library', '--desugared_library',
+      help='Build and use desugared library from GitHub.')
   return result.parse_args()
 
 def archive_failures():
@@ -178,6 +181,23 @@
   if utils.is_bot():
     gradle.RunGradle(['--no-daemon', 'clean'])
 
+  desugar_jdk_libs = None
+  if options.desugared_library:
+    if options.desugared_library != 'HEAD':
+      print("Only value supported for --desugared-library is 'HEAD'")
+      exit(1)
+    desugar_jdk_libs_dir = 'build/desugar_jdk_libs'
+    shutil.rmtree(desugar_jdk_libs_dir, ignore_errors=True)
+    os.makedirs(desugar_jdk_libs_dir)
+    print('Building desugared library.')
+    with utils.TempDir() as checkout_dir:
+      archive_desugar_jdk_libs.CloneDesugaredLibrary('google', checkout_dir)
+      (library_jar, maven_zip) = archive_desugar_jdk_libs.BuildDesugaredLibrary(checkout_dir)
+      desugar_jdk_libs = os.path.join(desugar_jdk_libs_dir, os.path.basename(library_jar))
+      shutil.copyfile(library_jar, desugar_jdk_libs)
+      print('Desugared library for test in ' + desugar_jdk_libs)
+
+
   gradle_args = ['--stacktrace']
   if utils.is_bot():
     # Bots don't like dangling processes.
@@ -261,6 +281,9 @@
     gradle_args.append('--no-daemon')
   if options.debug_agent:
     gradle_args.append('--no-daemon')
+  if desugar_jdk_libs:
+    gradle_args.append('-Pdesugar_jdk_libs=' + desugar_jdk_libs)
+
 
   # Build an R8 with dependencies for bootstrapping tests before adding test sources.
   gradle_args.append('r8WithDeps')