Merge commit '62b3a4af8a0017ca8ebd236845894af2b462bdf0' into dev-release
diff --git a/.gitignore b/.gitignore
index a5b9426..147cf9a 100644
--- a/.gitignore
+++ b/.gitignore
@@ -217,6 +217,7 @@
 third_party/youtube-developer/20200415.tar.gz
 tmp/
 tools/*.pyc
+tools/__pycache__
 tools/*/art
 tools/*/art-5.1.1
 tools/*/art-5.1.1.tar.gz
diff --git a/src/main/java/com/android/tools/r8/D8.java b/src/main/java/com/android/tools/r8/D8.java
index 4def5d0..b762557 100644
--- a/src/main/java/com/android/tools/r8/D8.java
+++ b/src/main/java/com/android/tools/r8/D8.java
@@ -271,6 +271,9 @@
         if (result != null) {
           appView.setAppInfo(new AppInfo(result.commit, appView.appInfo().getMainDexClasses()));
           appView.pruneItems(result.prunedItems);
+          if (result.lens != null) {
+            appView.setGraphLens(result.lens);
+          }
         }
         new CfApplicationWriter(
                 appView,
@@ -315,6 +318,9 @@
         if (result != null) {
           appView.setAppInfo(new AppInfo(result.commit, appView.appInfo().getMainDexClasses()));
           appView.pruneItems(result.prunedItems);
+          if (result.lens != null) {
+            appView.setGraphLens(result.lens);
+          }
         }
 
         new ApplicationWriter(
diff --git a/src/main/java/com/android/tools/r8/D8Command.java b/src/main/java/com/android/tools/r8/D8Command.java
index 047df78..b12a940 100644
--- a/src/main/java/com/android/tools/r8/D8Command.java
+++ b/src/main/java/com/android/tools/r8/D8Command.java
@@ -445,6 +445,11 @@
     internal.programConsumer = getProgramConsumer();
     if (internal.isGeneratingClassFiles()) {
       internal.cfToCfDesugar = true;
+      // Turn off switch optimizations when desugaring to class file format.
+      assert internal.enableSwitchRewriting;
+      internal.enableSwitchRewriting = false;
+      assert internal.enableStringSwitchConversion;
+      internal.enableStringSwitchConversion = false;
     }
     internal.mainDexListConsumer = getMainDexListConsumer();
     internal.minimalMainDex = internal.debug || minimalMainDex;
diff --git a/src/main/java/com/android/tools/r8/GenerateMainDexList.java b/src/main/java/com/android/tools/r8/GenerateMainDexList.java
index d260bcb..bf85148 100644
--- a/src/main/java/com/android/tools/r8/GenerateMainDexList.java
+++ b/src/main/java/com/android/tools/r8/GenerateMainDexList.java
@@ -96,7 +96,8 @@
     }
 
     Enqueuer enqueuer =
-        EnqueuerFactory.createForMainDexTracing(appView, subtypingInfo, graphConsumer);
+        EnqueuerFactory.createForFinalMainDexTracing(
+            appView, executor, subtypingInfo, graphConsumer, MainDexTracingResult.NONE);
     Set<DexProgramClass> liveTypes = enqueuer.traceMainDex(mainDexRootSet, executor, timing);
     // LiveTypes is the result.
     MainDexTracingResult mainDexTracingResult = new MainDexListBuilder(liveTypes, appView).run();
diff --git a/src/main/java/com/android/tools/r8/L8.java b/src/main/java/com/android/tools/r8/L8.java
index f610d83..64fc53e 100644
--- a/src/main/java/com/android/tools/r8/L8.java
+++ b/src/main/java/com/android/tools/r8/L8.java
@@ -94,9 +94,19 @@
       ExceptionUtils.withD8CompilationHandler(
           options.reporter,
           () -> {
+            // Desugar to class file format and turn off switch optimizations, as the final
+            // compilation with D8 or R8 will do that.
             options.cfToCfDesugar = true;
+            assert options.enableSwitchRewriting;
+            options.enableSwitchRewriting = false;
+            assert options.enableStringSwitchConversion;
+            options.enableStringSwitchConversion = false;
+
             desugar(app, options, executorService);
+
             options.cfToCfDesugar = false;
+            options.enableSwitchRewriting = true;
+            options.enableStringSwitchConversion = true;
           });
       assert !options.cfToCfDesugar;
       if (shrink) {
@@ -133,6 +143,9 @@
       if (result != null) {
         appView.setAppInfo(new AppInfo(result.commit, appView.appInfo().getMainDexClasses()));
         appView.pruneItems(result.prunedItems);
+        if (result.lens != null) {
+          appView.setGraphLens(result.lens);
+        }
       }
 
       NamingLens namingLens = PrefixRewritingNamingLens.createPrefixRewritingNamingLens(appView);
diff --git a/src/main/java/com/android/tools/r8/PrintSeeds.java b/src/main/java/com/android/tools/r8/PrintSeeds.java
index 212eb32..4be995a 100644
--- a/src/main/java/com/android/tools/r8/PrintSeeds.java
+++ b/src/main/java/com/android/tools/r8/PrintSeeds.java
@@ -92,10 +92,9 @@
       RootSet rootSet =
           new RootSetBuilder(appView, subtypingInfo, options.getProguardConfiguration().getRules())
               .run(executor);
-      Enqueuer enqueuer = EnqueuerFactory.createForInitialTreeShaking(appView, subtypingInfo);
-      AppInfoWithLiveness appInfo =
-          enqueuer.traceApplication(
-              rootSet, options.getProguardConfiguration().getDontWarnPatterns(), executor, timing);
+      Enqueuer enqueuer =
+          EnqueuerFactory.createForInitialTreeShaking(appView, executor, subtypingInfo);
+      AppInfoWithLiveness appInfo = enqueuer.traceApplication(rootSet, executor, timing);
       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 9e7a967..5e31835 100644
--- a/src/main/java/com/android/tools/r8/R8.java
+++ b/src/main/java/com/android/tools/r8/R8.java
@@ -31,7 +31,6 @@
 import com.android.tools.r8.graph.DexReference;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.DirectMappedDexApplication;
-import com.android.tools.r8.graph.DirectMappedDexApplication.Builder;
 import com.android.tools.r8.graph.GraphLens;
 import com.android.tools.r8.graph.GraphLens.NestedGraphLens;
 import com.android.tools.r8.graph.InitClassLens;
@@ -49,8 +48,6 @@
 import com.android.tools.r8.ir.desugar.BackportedMethodRewriter;
 import com.android.tools.r8.ir.desugar.DesugaredLibraryRetargeter;
 import com.android.tools.r8.ir.desugar.InterfaceMethodRewriter;
-import com.android.tools.r8.ir.desugar.NestedPrivateMethodLens;
-import com.android.tools.r8.ir.desugar.R8NestBasedAccessDesugaring;
 import com.android.tools.r8.ir.optimize.AssertionsRewriter;
 import com.android.tools.r8.ir.optimize.MethodPoolCollection;
 import com.android.tools.r8.ir.optimize.NestReducer;
@@ -332,7 +329,7 @@
                   .rebuildWithClassHierarchy(
                       MissingClasses.builderForInitialMissingClasses()
                           .addNewMissingClasses(new SubtypingInfo(appView).getMissingClasses())
-                          .reportMissingClasses(options)));
+                          .reportMissingClasses(appView)));
         }
 
         // Add synthesized -assumenosideeffects from min api if relevant.
@@ -420,6 +417,8 @@
       }
 
       assert appView.appInfo().hasLiveness();
+      AppView<AppInfoWithLiveness> appViewWithLiveness = appView.withLiveness();
+
       assert verifyNoJarApplicationReaders(appView.appInfo().classes());
       // Build conservative main dex content after first round of tree shaking. This is used
       // by certain optimizations to avoid introducing additional class references into main dex
@@ -435,7 +434,7 @@
                 .run(executorService);
         // Live types is the tracing result.
         Set<DexProgramClass> mainDexBaseClasses =
-            EnqueuerFactory.createForMainDexTracing(appView, subtypingInfo)
+            EnqueuerFactory.createForInitialMainDexTracing(appView, executorService, subtypingInfo)
                 .traceMainDex(mainDexRootSet, executorService, timing);
         // Calculate the automatic main dex list according to legacy multidex constraints.
         mainDexTracingResult = new MainDexListBuilder(mainDexBaseClasses, appView).run();
@@ -448,7 +447,6 @@
       appView.dexItemFactory().clearTypeElementsCache();
 
       if (options.getProguardConfiguration().isAccessModificationAllowed()) {
-        AppView<AppInfoWithLiveness> appViewWithLiveness = appView.withLiveness();
         SubtypingInfo subtypingInfo = appViewWithLiveness.appInfo().computeSubtypingInfo();
         GraphLens publicizedLens =
             ClassAndMemberPublicizer.run(
@@ -466,29 +464,13 @@
         }
       }
 
-      AppView<AppInfoWithLiveness> appViewWithLiveness = appView.withLiveness();
+      // This pass attempts to reduce the number of nests and nest size to allow further passes, and
+      // should therefore be run after the publicizer.
+      new NestReducer(appViewWithLiveness).run(executorService, timing);
+
       appView.setGraphLens(new MemberRebindingAnalysis(appViewWithLiveness).run(executorService));
       appView.appInfo().withLiveness().getFieldAccessInfoCollection().restrictToProgram(appView);
 
-      if (options.shouldDesugarNests()) {
-        timing.begin("NestBasedAccessDesugaring");
-        R8NestBasedAccessDesugaring analyzer = new R8NestBasedAccessDesugaring(appViewWithLiveness);
-        Builder appBuilder = getDirectApp(appView).builder();
-        NestedPrivateMethodLens lens = analyzer.run(executorService, appBuilder);
-        if (lens != null) {
-          appView.rewriteWithLensAndApplication(lens, appBuilder.build());
-        }
-        timing.end();
-      } else {
-        timing.begin("NestReduction");
-        // This pass attempts to reduce the number of nests and nest size
-        // to allow further passes, specifically the class mergers, to do
-        // a better job. This pass is better run before the class merger
-        // but after the publicizer (cannot be run as part of the Enqueuer).
-        new NestReducer(appViewWithLiveness).run(executorService);
-        timing.end();
-      }
-
       boolean isKotlinLibraryCompilationWithInlinePassThrough =
           options.enableCfByteCodePassThrough && appView.hasCfByteCodePassThroughMethods();
 
@@ -644,8 +626,12 @@
         }
 
         Enqueuer enqueuer =
-            EnqueuerFactory.createForMainDexTracing(
-                appView, new SubtypingInfo(appView), mainDexKeptGraphConsumer);
+            EnqueuerFactory.createForFinalMainDexTracing(
+                appView,
+                executorService,
+                new SubtypingInfo(appView),
+                mainDexKeptGraphConsumer,
+                mainDexTracingResult);
         // Find classes which may have code executed before secondary dex files installation.
         // Live types is the tracing result.
         Set<DexProgramClass> mainDexBaseClasses =
@@ -694,13 +680,13 @@
           Enqueuer enqueuer =
               EnqueuerFactory.createForFinalTreeShaking(
                   appView,
+                  executorService,
                   new SubtypingInfo(appView),
                   keptGraphConsumer,
                   prunedTypes);
           appView.setAppInfo(
               enqueuer.traceApplication(
                   appView.rootSet(),
-                  options.getProguardConfiguration().getDontWarnPatterns(),
                   executorService,
                   timing));
           // Rerunning the enqueuer should not give rise to any method rewritings.
@@ -827,7 +813,9 @@
               lens, appBuilder.build(), memberRebindingLens.getPrevious());
         }
       }
-      assert Repackaging.verifyIdentityRepackaging(appView);
+      if (appView.appInfo().hasLiveness()) {
+        assert Repackaging.verifyIdentityRepackaging(appView.withLiveness());
+      }
 
       // Add automatic main dex classes to an eventual manual list of classes.
       if (!options.mainDexKeepRules.isEmpty()) {
@@ -838,12 +826,21 @@
           appView.getSyntheticItems().computeFinalSynthetics(appView);
       if (result != null) {
         if (appView.appInfo().hasLiveness()) {
-          appViewWithLiveness.setAppInfo(
-              appViewWithLiveness.appInfo().rebuildWithLiveness(result.commit));
+          if (result.lens == null) {
+            appViewWithLiveness.setAppInfo(
+                appViewWithLiveness.appInfo().rebuildWithLiveness(result.commit));
+          } else {
+            appViewWithLiveness.rewriteWithLensAndApplication(
+                result.lens, result.commit.getApplication().asDirect());
+          }
+          appViewWithLiveness.pruneItems(result.prunedItems);
         } else {
           appView.setAppInfo(appView.appInfo().rebuildWithClassHierarchy(result.commit));
+          appView.pruneItems(result.prunedItems);
+          if (result.lens != null) {
+            appView.setGraphLens(result.lens);
+          }
         }
-        appViewWithLiveness.pruneItems(result.prunedItems);
       }
 
       // Perform minification.
@@ -931,6 +928,8 @@
           options,
           ProguardMapSupplier.create(classNameMapper, options));
 
+      assert appView.getDontWarnConfiguration().validate(options);
+
       options.printWarnings();
     } catch (ExecutionException e) {
       throw unwrapExecutionException(e);
@@ -1002,7 +1001,8 @@
       SubtypingInfo subtypingInfo,
       RuntimeTypeCheckInfo.Builder classMergingEnqueuerExtensionBuilder)
       throws ExecutionException {
-    Enqueuer enqueuer = EnqueuerFactory.createForInitialTreeShaking(appView, subtypingInfo);
+    Enqueuer enqueuer =
+        EnqueuerFactory.createForInitialTreeShaking(appView, executorService, subtypingInfo);
     enqueuer.setAnnotationRemoverBuilder(annotationRemoverBuilder);
     if (appView.options().enableInitializedClassesInInstanceMethodsAnalysis) {
       enqueuer.registerAnalysis(new InitializedClassesInInstanceMethodsAnalysis(appView));
@@ -1021,7 +1021,6 @@
         appView.setAppInfo(
             enqueuer.traceApplication(
                 appView.rootSet(),
-                options.getProguardConfiguration().getDontWarnPatterns(),
                 executorService,
                 timing));
     NestedGraphLens lens = enqueuer.buildGraphLens();
@@ -1071,16 +1070,19 @@
       SubtypingInfo subtypingInfo = new SubtypingInfo(appView);
       if (forMainDex) {
         enqueuer =
-            EnqueuerFactory.createForMainDexTracing(
-                appView, subtypingInfo, whyAreYouKeepingConsumer);
+            EnqueuerFactory.createForFinalMainDexTracing(
+                appView,
+                executorService,
+                subtypingInfo,
+                whyAreYouKeepingConsumer,
+                MainDexTracingResult.NONE);
         enqueuer.traceMainDex(rootSet, executorService, timing);
       } else {
         enqueuer =
             EnqueuerFactory.createForWhyAreYouKeeping(
-                appView, subtypingInfo, whyAreYouKeepingConsumer);
+                appView, executorService, subtypingInfo, whyAreYouKeepingConsumer);
         enqueuer.traceApplication(
             rootSet,
-            options.getProguardConfiguration().getDontWarnPatterns(),
             executorService,
             timing);
       }
diff --git a/src/main/java/com/android/tools/r8/ReadProguardMap.java b/src/main/java/com/android/tools/r8/ReadProguardMap.java
index 31bc2d8..0e02ce0 100644
--- a/src/main/java/com/android/tools/r8/ReadProguardMap.java
+++ b/src/main/java/com/android/tools/r8/ReadProguardMap.java
@@ -38,7 +38,7 @@
     if (args.length == 0) {
       readProguardMapFile(DEFAULT_MAP_FILE_NAME);
     } else {
-      Arrays.asList(args).forEach((String name) -> readProguardMapFile(name));
+      Arrays.asList(args).forEach(this::readProguardMapFile);
     }
     timing.report();
   }
diff --git a/src/main/java/com/android/tools/r8/cf/CfCodePrinter.java b/src/main/java/com/android/tools/r8/cf/CfCodePrinter.java
index b4b0d5a..d56b873 100644
--- a/src/main/java/com/android/tools/r8/cf/CfCodePrinter.java
+++ b/src/main/java/com/android/tools/r8/cf/CfCodePrinter.java
@@ -452,9 +452,9 @@
 
   @Override
   public void print(CfFrame frame) {
-    String keys = join(frame.getLocals().keySet(), ",", BraceType.NONE);
-    String values = join(frame.getLocals().values(), ",", BraceType.NONE, this::frameTypeType);
-    String stack = join(frame.getStack(), ",", BraceType.NONE, this::frameTypeType);
+    String keys = join(frame.getLocals().keySet(), ",");
+    String values = join(",", frame.getLocals().values(), this::frameTypeType);
+    String stack = join(",", frame.getStack(), this::frameTypeType);
     printNewInstruction(
         "CfFrame",
         "new "
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfFieldInstruction.java b/src/main/java/com/android/tools/r8/cf/code/CfFieldInstruction.java
index 6176b49..be6176b 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfFieldInstruction.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfFieldInstruction.java
@@ -65,6 +65,10 @@
     return visitor.visit(this, other.asFieldInstruction(), CfFieldInstruction::specify);
   }
 
+  public boolean isFieldGet() {
+    return opcode == Opcodes.GETFIELD || opcode == Opcodes.GETSTATIC;
+  }
+
   @Override
   public CfFieldInstruction asFieldInstruction() {
     return this;
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 6c7f76f..00d4c93 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
@@ -170,6 +170,10 @@
         !method.name.toString().equals(Constants.INSTANCE_INITIALIZER_NAME);
   }
 
+  public boolean isInvokeStatic() {
+    return opcode == Opcodes.INVOKESTATIC;
+  }
+
   public boolean isInvokeVirtual() {
     return opcode == Opcodes.INVOKEVIRTUAL;
   }
diff --git a/src/main/java/com/android/tools/r8/code/Instruction.java b/src/main/java/com/android/tools/r8/code/Instruction.java
index 977dafd..ccfd713 100644
--- a/src/main/java/com/android/tools/r8/code/Instruction.java
+++ b/src/main/java/com/android/tools/r8/code/Instruction.java
@@ -332,6 +332,9 @@
   @Override
   public final void acceptHashing(HashingVisitor visitor) {
     // Rather than traverse the full instruction, the compare ID will likely give a reasonable hash.
+    // TODO(b/158159959): This will likely lead to a lot of distinct synthetics hashing to the same
+    //  hash as many have the same instruction pattern such as an invoke of the impl method or a
+    //  field access.
     visitor.visitInt(getCompareToId());
   }
 
diff --git a/src/main/java/com/android/tools/r8/dex/FileWriter.java b/src/main/java/com/android/tools/r8/dex/FileWriter.java
index ef6b7cd..bbe4a90 100644
--- a/src/main/java/com/android/tools/r8/dex/FileWriter.java
+++ b/src/main/java/com/android/tools/r8/dex/FileWriter.java
@@ -55,7 +55,7 @@
 import com.android.tools.r8.naming.MemberNaming.Signature;
 import com.android.tools.r8.naming.NamingLens;
 import com.android.tools.r8.position.MethodPosition;
-import com.android.tools.r8.synthesis.SyntheticItems;
+import com.android.tools.r8.synthesis.SyntheticNaming;
 import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.DexVersion;
 import com.android.tools.r8.utils.InternalOptions;
@@ -323,7 +323,7 @@
     for (DexType type : mapping.getTypes()) {
       if (type.isClassType()) {
         assert DexString.isValidSimpleName(apiLevel, type.getName());
-        assert SyntheticItems.verifyNotInternalSynthetic(type);
+        assert SyntheticNaming.verifyNotInternalSynthetic(type);
       }
     }
 
diff --git a/src/main/java/com/android/tools/r8/dex/InheritanceClassInDexDistributor.java b/src/main/java/com/android/tools/r8/dex/InheritanceClassInDexDistributor.java
index 8e8750a..1183590 100644
--- a/src/main/java/com/android/tools/r8/dex/InheritanceClassInDexDistributor.java
+++ b/src/main/java/com/android/tools/r8/dex/InheritanceClassInDexDistributor.java
@@ -548,7 +548,7 @@
   private void updateGroupsNumberOfIds(List<ClassGroup> groups) {
     Collection<Future<?>> updateIdsTasks = new ArrayList<>(groups.size());
     for (ClassGroup group : groups) {
-      updateIdsTasks.add(executorService.submit(() -> group.updateNumbersOfIds()));
+      updateIdsTasks.add(executorService.submit(group::updateNumbersOfIds));
     }
     try {
       ThreadUtils.awaitFutures(updateIdsTasks);
diff --git a/src/main/java/com/android/tools/r8/dex/VirtualFile.java b/src/main/java/com/android/tools/r8/dex/VirtualFile.java
index 917f0de..7da0bc5 100644
--- a/src/main/java/com/android/tools/r8/dex/VirtualFile.java
+++ b/src/main/java/com/android/tools/r8/dex/VirtualFile.java
@@ -28,7 +28,7 @@
 import com.android.tools.r8.naming.ClassNameMapper;
 import com.android.tools.r8.naming.NamingLens;
 import com.android.tools.r8.shaking.MainDexClasses;
-import com.android.tools.r8.synthesis.SyntheticItems;
+import com.android.tools.r8.synthesis.SyntheticNaming;
 import com.android.tools.r8.utils.DescriptorUtils;
 import com.android.tools.r8.utils.FileUtils;
 import com.android.tools.r8.utils.InternalOptions;
@@ -667,7 +667,7 @@
 
     @Override
     public boolean addType(DexType type) {
-      assert SyntheticItems.verifyNotInternalSynthetic(type);
+      assert SyntheticNaming.verifyNotInternalSynthetic(type);
       return types.add(type);
     }
 
@@ -790,7 +790,7 @@
 
     @Override
     public boolean addType(DexType type) {
-      assert SyntheticItems.verifyNotInternalSynthetic(type);
+      assert SyntheticNaming.verifyNotInternalSynthetic(type);
       return maybeInsert(type, types, base.types);
     }
 
diff --git a/src/main/java/com/android/tools/r8/errors/dontwarn/DontWarnConfiguration.java b/src/main/java/com/android/tools/r8/errors/dontwarn/DontWarnConfiguration.java
new file mode 100644
index 0000000..ff4a73d
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/errors/dontwarn/DontWarnConfiguration.java
@@ -0,0 +1,43 @@
+// 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.errors.dontwarn;
+
+import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.shaking.ProguardConfiguration;
+import com.android.tools.r8.utils.InternalOptions;
+import java.util.Set;
+
+public abstract class DontWarnConfiguration {
+
+  public static DontWarnConfiguration create(ProguardConfiguration proguardConfiguration) {
+    if (proguardConfiguration != null && proguardConfiguration.hasDontWarnPatterns()) {
+      return new NonEmptyDontWarnConfiguration(
+          proguardConfiguration.getDontWarnPatterns(new Witness()));
+    }
+    return empty();
+  }
+
+  public static EmptyDontWarnConfiguration empty() {
+    return new EmptyDontWarnConfiguration();
+  }
+
+  public abstract Set<DexType> getNonMatches(Set<DexType> types);
+
+  public final boolean matches(DexClass clazz) {
+    return matches(clazz.getType());
+  }
+
+  public abstract boolean matches(DexType type);
+
+  public abstract boolean validate(InternalOptions options);
+
+  // Witness that can only be instantiated by the DontWarnConfiguration, to ensure that the dont
+  // warn patterns are only accessed via the DontWarnConfiguration.
+  public static class Witness {
+
+    private Witness() {}
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/errors/dontwarn/EmptyDontWarnConfiguration.java b/src/main/java/com/android/tools/r8/errors/dontwarn/EmptyDontWarnConfiguration.java
new file mode 100644
index 0000000..e115168
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/errors/dontwarn/EmptyDontWarnConfiguration.java
@@ -0,0 +1,27 @@
+// 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.errors.dontwarn;
+
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.utils.InternalOptions;
+import java.util.Set;
+
+public class EmptyDontWarnConfiguration extends DontWarnConfiguration {
+
+  @Override
+  public Set<DexType> getNonMatches(Set<DexType> types) {
+    return types;
+  }
+
+  @Override
+  public boolean matches(DexType type) {
+    return false;
+  }
+
+  @Override
+  public boolean validate(InternalOptions options) {
+    return true;
+  }
+}
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
new file mode 100644
index 0000000..e96cb83
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/errors/dontwarn/NonEmptyDontWarnConfiguration.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.errors.dontwarn;
+
+import static java.util.Collections.emptySet;
+
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.shaking.ProguardClassFilter;
+import com.android.tools.r8.shaking.ProguardClassNameList;
+import com.android.tools.r8.utils.InternalOptions;
+import com.google.common.collect.Sets;
+import java.util.IdentityHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+public class NonEmptyDontWarnConfiguration extends DontWarnConfiguration {
+
+  private final List<ProguardClassNameList> dontWarnPatterns;
+  private final Map<ProguardClassNameList, Set<DexType>> matchedDontWarnPatterns =
+      new IdentityHashMap<>();
+
+  NonEmptyDontWarnConfiguration(ProguardClassFilter dontWarnPatterns) {
+    assert dontWarnPatterns != null;
+    assert !dontWarnPatterns.isEmpty();
+    this.dontWarnPatterns = dontWarnPatterns.getPatterns();
+  }
+
+  @Override
+  public Set<DexType> getNonMatches(Set<DexType> types) {
+    Set<DexType> nonMatches = Sets.newIdentityHashSet();
+    for (DexType type : types) {
+      if (!matches(type)) {
+        nonMatches.add(type);
+      }
+    }
+    return nonMatches;
+  }
+
+  @Override
+  public boolean matches(DexType type) {
+    for (ProguardClassNameList dontWarnPattern : dontWarnPatterns) {
+      if (dontWarnPattern.matches(type)) {
+        recordMatch(dontWarnPattern, type);
+        return true;
+      }
+    }
+    return false;
+  }
+
+  private void recordMatch(ProguardClassNameList dontWarnPattern, DexType type) {
+    if (InternalOptions.assertionsEnabled()) {
+      matchedDontWarnPatterns
+          .computeIfAbsent(dontWarnPattern, ignore -> Sets.newIdentityHashSet())
+          .add(type);
+    }
+  }
+
+  @Override
+  public boolean validate(InternalOptions options) {
+    assert options.testing.allowUnnecessaryDontWarnWildcards
+        || validateNoUnnecessaryDontWarnWildcards();
+    assert options.testing.allowUnusedDontWarnRules || validateNoUnusedDontWarnPatterns();
+    return true;
+  }
+
+  public boolean validateNoUnnecessaryDontWarnWildcards() {
+    for (ProguardClassNameList dontWarnPattern : dontWarnPatterns) {
+      assert !dontWarnPattern.hasWildcards()
+              || matchedDontWarnPatterns.getOrDefault(dontWarnPattern, emptySet()).size() != 1
+          : "Unexpected unnecessary wildcard in -dontwarn "
+              + dontWarnPattern.toString()
+              + " (only matches "
+              + matchedDontWarnPatterns.get(dontWarnPattern).iterator().next().getTypeName()
+              + ")";
+    }
+    return true;
+  }
+
+  public boolean validateNoUnusedDontWarnPatterns() {
+    for (ProguardClassNameList dontWarnPattern : dontWarnPatterns) {
+      assert matchedDontWarnPatterns.containsKey(dontWarnPattern)
+          : "Unexpected unused rule -dontwarn " + dontWarnPattern.toString();
+    }
+    return true;
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/graph/AppInfo.java b/src/main/java/com/android/tools/r8/graph/AppInfo.java
index cfacdbb..302dff4 100644
--- a/src/main/java/com/android/tools/r8/graph/AppInfo.java
+++ b/src/main/java/com/android/tools/r8/graph/AppInfo.java
@@ -123,11 +123,6 @@
     }
   }
 
-  public Collection<DexProgramClass> synthesizedClasses() {
-    assert checkIfObsolete();
-    return syntheticItems.getPendingSyntheticClasses();
-  }
-
   public Collection<DexProgramClass> classes() {
     assert checkIfObsolete();
     return app.classes();
diff --git a/src/main/java/com/android/tools/r8/graph/AppInfoWithClassHierarchy.java b/src/main/java/com/android/tools/r8/graph/AppInfoWithClassHierarchy.java
index 56b1a53..8ff4d1e 100644
--- a/src/main/java/com/android/tools/r8/graph/AppInfoWithClassHierarchy.java
+++ b/src/main/java/com/android/tools/r8/graph/AppInfoWithClassHierarchy.java
@@ -15,12 +15,15 @@
 import com.android.tools.r8.graph.ResolutionResult.IncompatibleClassResult;
 import com.android.tools.r8.graph.ResolutionResult.NoSuchMethodResult;
 import com.android.tools.r8.graph.ResolutionResult.SingleResolutionResult;
+import com.android.tools.r8.ir.analysis.type.InterfaceCollection;
+import com.android.tools.r8.ir.analysis.type.InterfaceCollection.Builder;
 import com.android.tools.r8.ir.desugar.LambdaDescriptor;
 import com.android.tools.r8.shaking.MainDexClasses;
 import com.android.tools.r8.shaking.MissingClasses;
 import com.android.tools.r8.synthesis.CommittedItems;
 import com.android.tools.r8.synthesis.SyntheticItems;
 import com.android.tools.r8.utils.ListUtils;
+import com.android.tools.r8.utils.Pair;
 import com.android.tools.r8.utils.SetUtils;
 import com.android.tools.r8.utils.TraversalContinuation;
 import com.android.tools.r8.utils.TriConsumer;
@@ -287,31 +290,78 @@
   }
 
   /** Collect all interfaces that this type directly or indirectly implements. */
-  public Set<DexType> implementedInterfaces(DexType type) {
+  public InterfaceCollection implementedInterfaces(DexType type) {
     assert type.isClassType();
     DexClass clazz = definitionFor(type);
     if (clazz == null) {
-      return Collections.emptySet();
+      return InterfaceCollection.empty();
     }
 
     // Fast path for a type below object with no interfaces.
     if (clazz.superType == dexItemFactory().objectType && clazz.interfaces.isEmpty()) {
-      return clazz.isInterface() ? Collections.singleton(type) : Collections.emptySet();
+      return clazz.isInterface()
+          ? InterfaceCollection.singleton(type)
+          : InterfaceCollection.empty();
     }
 
     // Slow path traverses the full super type hierarchy.
-    Set<DexType> interfaces = Sets.newIdentityHashSet();
+    Builder builder = InterfaceCollection.builder();
     if (clazz.isInterface()) {
-      interfaces.add(type);
+      builder.addInterface(type, true);
     }
-    forEachSuperType(
-        clazz,
-        (superType, subclass, isInterface) -> {
-          if (isInterface) {
-            interfaces.add(superType);
+    // First find all interface leafs from the class super-type chain.
+    Set<DexType> seenAndKnown = Sets.newIdentityHashSet();
+    Deque<Pair<DexClass, Boolean>> worklist = new ArrayDeque<>();
+    {
+      DexClass implementor = clazz;
+      while (implementor != null) {
+        for (DexType iface : implementor.interfaces) {
+          if (seenAndKnown.contains(iface)) {
+            continue;
           }
-        });
-    return interfaces;
+          boolean isKnown =
+              InterfaceCollection.isKnownToImplement(iface, implementor.getType(), options());
+          builder.addInterface(iface, isKnown);
+          if (isKnown) {
+            seenAndKnown.add(iface);
+          }
+          DexClass definition = definitionFor(iface);
+          if (definition != null && !definition.interfaces.isEmpty()) {
+            worklist.add(new Pair<>(definition, isKnown));
+          }
+        }
+        if (implementor.superType == null
+            || implementor.superType == options().dexItemFactory().objectType) {
+          break;
+        }
+        implementor = definitionFor(implementor.superType);
+      }
+    }
+    // Second complete the worklist of interfaces. All paths must be visited as an interface may
+    // be unknown on one but not on another.
+    while (!worklist.isEmpty()) {
+      Pair<DexClass, Boolean> item = worklist.poll();
+      DexClass implementor = item.getFirst();
+      assert !implementor.interfaces.isEmpty();
+      for (DexType itf : implementor.interfaces) {
+        if (seenAndKnown.contains(itf)) {
+          continue;
+        }
+        // A derived interface is known only if the full chain leading to it is known.
+        boolean isKnown =
+            item.getSecond()
+                && InterfaceCollection.isKnownToImplement(itf, implementor.getType(), options());
+        builder.addInterface(itf, isKnown);
+        if (isKnown) {
+          seenAndKnown.add(itf);
+        }
+        DexClass definition = definitionFor(itf);
+        if (definition != null && !definition.interfaces.isEmpty()) {
+          worklist.add(new Pair<>(definition, isKnown));
+        }
+      }
+    }
+    return builder.build();
   }
 
   public boolean isExternalizable(DexType type) {
diff --git a/src/main/java/com/android/tools/r8/graph/AppServices.java b/src/main/java/com/android/tools/r8/graph/AppServices.java
index c0c3976..bb61928 100644
--- a/src/main/java/com/android/tools/r8/graph/AppServices.java
+++ b/src/main/java/com/android/tools/r8/graph/AppServices.java
@@ -342,8 +342,7 @@
       }
 
       private void warn(String message, DexType type, Origin origin) {
-        if (options.getProguardConfiguration() == null
-            || !options.getProguardConfiguration().getDontWarnPatterns().matches(type)) {
+        if (!appView.getDontWarnConfiguration().matches(type)) {
           options.reporter.warning(new StringDiagnostic(message, origin));
         }
       }
diff --git a/src/main/java/com/android/tools/r8/graph/AppView.java b/src/main/java/com/android/tools/r8/graph/AppView.java
index 531c78a..489d0b6 100644
--- a/src/main/java/com/android/tools/r8/graph/AppView.java
+++ b/src/main/java/com/android/tools/r8/graph/AppView.java
@@ -4,6 +4,7 @@
 
 package com.android.tools.r8.graph;
 
+import com.android.tools.r8.errors.dontwarn.DontWarnConfiguration;
 import com.android.tools.r8.features.ClassToFeatureSplitMap;
 import com.android.tools.r8.graph.DexValue.DexValueString;
 import com.android.tools.r8.graph.GraphLens.NonIdentityGraphLens;
@@ -57,6 +58,7 @@
   private T appInfo;
   private AppInfoWithClassHierarchy appInfoForDesugaring;
   private AppServices appServices;
+  private final DontWarnConfiguration dontWarnConfiguration;
   private final WholeProgramOptimizations wholeProgramOptimizations;
   private GraphLens graphLens;
   private InitClassLens initClassLens;
@@ -107,6 +109,7 @@
       PrefixRewritingMapper mapper) {
     assert appInfo != null;
     this.appInfo = appInfo;
+    this.dontWarnConfiguration = DontWarnConfiguration.create(options().getProguardConfiguration());
     this.wholeProgramOptimizations = wholeProgramOptimizations;
     this.graphLens = GraphLens.getIdentityLens();
     this.initClassLens = InitClassLens.getDefault();
@@ -251,6 +254,10 @@
     this.appServices = appServices;
   }
 
+  public DontWarnConfiguration getDontWarnConfiguration() {
+    return dontWarnConfiguration;
+  }
+
   public boolean isClassEscapingIntoLibrary(DexType type) {
     assert type.isClassType();
     return classesEscapingIntoLibrary.test(type);
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 eb83898..4d45d55 100644
--- a/src/main/java/com/android/tools/r8/graph/CfCode.java
+++ b/src/main/java/com/android/tools/r8/graph/CfCode.java
@@ -247,6 +247,9 @@
   public void acceptHashing(HashingVisitor visitor) {
     // Rather than hash the entire content, hash the sizes and each instruction "type" which
     // should provide a fast yet reasonably distinct key.
+    // TODO(b/158159959): This will likely lead to a lot of distinct synthetics hashing to the same
+    //  hash as many have the same instruction pattern such as an invoke of the impl method or a
+    //  field access.
     visitor.visitInt(instructions.size());
     visitor.visitInt(tryCatchRanges.size());
     visitor.visitInt(localVariables.size());
diff --git a/src/main/java/com/android/tools/r8/graph/ClasspathField.java b/src/main/java/com/android/tools/r8/graph/ClasspathField.java
new file mode 100644
index 0000000..b40e51c
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/graph/ClasspathField.java
@@ -0,0 +1,27 @@
+// 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;
+
+public class ClasspathField extends DexClassAndField {
+
+  public ClasspathField(DexClasspathClass holder, DexEncodedField field) {
+    super(holder, field);
+  }
+
+  @Override
+  public boolean isClasspathField() {
+    return true;
+  }
+
+  @Override
+  public ClasspathField asClasspathField() {
+    return this;
+  }
+
+  @Override
+  public boolean isClasspathMember() {
+    return true;
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/graph/ClasspathMethod.java b/src/main/java/com/android/tools/r8/graph/ClasspathMethod.java
index 9d2a941..0c58053 100644
--- a/src/main/java/com/android/tools/r8/graph/ClasspathMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/ClasspathMethod.java
@@ -23,6 +23,11 @@
   }
 
   @Override
+  public boolean isClasspathMember() {
+    return true;
+  }
+
+  @Override
   public boolean isClasspathMethod() {
     return true;
   }
diff --git a/src/main/java/com/android/tools/r8/graph/DexAnnotation.java b/src/main/java/com/android/tools/r8/graph/DexAnnotation.java
index bcb7974..3483fbb 100644
--- a/src/main/java/com/android/tools/r8/graph/DexAnnotation.java
+++ b/src/main/java/com/android/tools/r8/graph/DexAnnotation.java
@@ -14,6 +14,8 @@
 import com.android.tools.r8.graph.DexValue.DexValueString;
 import com.android.tools.r8.graph.DexValue.DexValueType;
 import com.android.tools.r8.ir.desugar.CovariantReturnTypeAnnotationTransformer;
+import com.android.tools.r8.synthesis.SyntheticNaming;
+import com.android.tools.r8.synthesis.SyntheticNaming.SyntheticKind;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.Pair;
 import com.android.tools.r8.utils.structural.StructuralItem;
@@ -399,13 +401,18 @@
   }
 
   public static DexAnnotation createAnnotationSynthesizedClass(
-      DexType synthesizingContext, DexItemFactory dexItemFactory) {
-    DexValueType value = new DexValueType(synthesizingContext);
-    DexAnnotationElement element = new DexAnnotationElement(dexItemFactory.valueString, value);
+      SyntheticKind kind, DexType synthesizingContext, DexItemFactory dexItemFactory) {
+    DexAnnotationElement kindElement =
+        new DexAnnotationElement(
+            dexItemFactory.kindString,
+            new DexValueString(dexItemFactory.createString(kind.descriptor)));
+    DexAnnotationElement typeElement =
+        new DexAnnotationElement(dexItemFactory.valueString, new DexValueType(synthesizingContext));
     return new DexAnnotation(
         VISIBILITY_BUILD,
         new DexEncodedAnnotation(
-            dexItemFactory.annotationSynthesizedClass, new DexAnnotationElement[] {element}));
+            dexItemFactory.annotationSynthesizedClass,
+            new DexAnnotationElement[] {kindElement, typeElement}));
   }
 
   public static boolean hasSynthesizedClassAnnotation(
@@ -413,7 +420,7 @@
     return getSynthesizedClassAnnotationContextType(annotations, factory) != null;
   }
 
-  public static DexType getSynthesizedClassAnnotationContextType(
+  public static Pair<SyntheticKind, DexType> getSynthesizedClassAnnotationContextType(
       DexAnnotationSet annotations, DexItemFactory factory) {
     if (annotations.size() != 1) {
       return null;
@@ -422,17 +429,31 @@
     if (annotation.annotation.type != factory.annotationSynthesizedClass) {
       return null;
     }
-    if (annotation.annotation.elements.length != 1) {
+    if (annotation.annotation.elements.length != 2) {
       return null;
     }
-    DexAnnotationElement element = annotation.annotation.elements[0];
-    if (element.name != factory.valueString) {
+    assert factory.kindString.isLessThan(factory.valueString);
+    DexAnnotationElement kindElement = annotation.annotation.elements[0];
+    if (kindElement.name != factory.kindString) {
       return null;
     }
-    if (!element.value.isDexValueType()) {
+    if (!kindElement.value.isDexValueString()) {
       return null;
     }
-    return element.value.asDexValueType().getValue();
+    SyntheticKind kind =
+        SyntheticNaming.SyntheticKind.fromDescriptor(
+            kindElement.value.asDexValueString().getValue().toString());
+    if (kind == null) {
+      return null;
+    }
+    DexAnnotationElement valueElement = annotation.annotation.elements[1];
+    if (valueElement.name != factory.valueString) {
+      return null;
+    }
+    if (!valueElement.value.isDexValueType()) {
+      return null;
+    }
+    return new Pair<>(kind, valueElement.value.asDexValueType().getValue());
   }
 
   public static DexAnnotation createAnnotationSynthesizedClassMap(
diff --git a/src/main/java/com/android/tools/r8/graph/DexApplication.java b/src/main/java/com/android/tools/r8/graph/DexApplication.java
index 6e38ed4..37b8d28 100644
--- a/src/main/java/com/android/tools/r8/graph/DexApplication.java
+++ b/src/main/java/com/android/tools/r8/graph/DexApplication.java
@@ -18,6 +18,7 @@
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
+import java.util.Comparator;
 import java.util.List;
 
 public abstract class DexApplication {
@@ -97,6 +98,8 @@
 
   abstract List<DexProgramClass> programClasses();
 
+  abstract List<DexClasspathClass> classpathClasses();
+
   public List<DexProgramClass> classes() {
     ReorderBox<DexProgramClass> box = new ReorderBox<>(programClasses());
     assert box.reorderClasses();
@@ -104,13 +107,16 @@
   }
 
   public List<DexProgramClass> classesWithDeterministicOrder() {
-    List<DexProgramClass> classes = new ArrayList<>(programClasses());
-    // We never actually sort by anything but the DexType, this is just here in case we ever change
-    // that.
-    if (options.testing.deterministicSortingBasedOnDexType) {
-      // To keep the order deterministic, we sort the classes by their type, which is a unique key.
-      classes.sort((a, b) -> a.type.compareTo(b.type));
-    }
+    return classesWithDeterministicOrder(new ArrayList<>(programClasses()));
+  }
+
+  public static <T extends DexClass> List<T> classesWithDeterministicOrder(Collection<T> classes) {
+    return classesWithDeterministicOrder(new ArrayList<>(classes));
+  }
+
+  public static <T extends DexClass> List<T> classesWithDeterministicOrder(List<T> classes) {
+    // To keep the order deterministic, we sort the classes by their type, which is a unique key.
+    classes.sort(Comparator.comparing(DexClass::getType));
     return classes;
   }
 
@@ -126,14 +132,9 @@
   }
 
   public abstract static class Builder<T extends Builder<T>> {
-    // We handle program class collection separately from classpath
-    // and library class collections. Since while we assume program
-    // class collection should always be fully loaded and thus fully
-    // represented by the map (making it easy, for example, adding
-    // new or removing existing classes), classpath and library
-    // collections will be considered monolithic collections.
 
-    final List<DexProgramClass> programClasses = new ArrayList<>();
+    private final List<DexProgramClass> programClasses = new ArrayList<>();
+    private final List<DexClasspathClass> classpathClasses = new ArrayList<>();
 
     final List<DataResourceProvider> dataResourceProviders = new ArrayList<>();
 
@@ -156,6 +157,7 @@
 
     public Builder(DexApplication application) {
       programClasses.addAll(application.programClasses());
+      classpathClasses.addAll(application.classpathClasses());
       dataResourceProviders.addAll(application.dataResourceProviders);
       proguardMap = application.getProguardMap();
       timing = application.timing;
@@ -171,13 +173,21 @@
       return self();
     }
 
-    public synchronized T replaceProgramClasses(List<DexProgramClass> newProgramClasses) {
+    public synchronized T replaceProgramClasses(Collection<DexProgramClass> newProgramClasses) {
       assert newProgramClasses != null;
       this.programClasses.clear();
       this.programClasses.addAll(newProgramClasses);
       return self();
     }
 
+    public synchronized T replaceClasspathClasses(
+        Collection<DexClasspathClass> newClasspathClasses) {
+      assert newClasspathClasses != null;
+      classpathClasses.clear();
+      classpathClasses.addAll(newClasspathClasses);
+      return self();
+    }
+
     public synchronized T addDataResourceProvider(DataResourceProvider provider) {
       dataResourceProviders.add(provider);
       return self();
@@ -193,6 +203,21 @@
       return self();
     }
 
+    public synchronized T addProgramClasses(Collection<DexProgramClass> classes) {
+      programClasses.addAll(classes);
+      return self();
+    }
+
+    public synchronized T addClasspathClass(DexClasspathClass clazz) {
+      classpathClasses.add(clazz);
+      return self();
+    }
+
+    public synchronized T addClasspathClasses(Collection<DexClasspathClass> classes) {
+      classpathClasses.addAll(classes);
+      return self();
+    }
+
     public synchronized T addSynthesizedClass(DexProgramClass synthesizedClass) {
       assert synthesizedClass.isProgramClass() : "All synthesized classes must be program classes";
       addProgramClass(synthesizedClass);
@@ -200,10 +225,14 @@
       return self();
     }
 
-    public Collection<DexProgramClass> getProgramClasses() {
+    public List<DexProgramClass> getProgramClasses() {
       return programClasses;
     }
 
+    public List<DexClasspathClass> getClasspathClasses() {
+      return classpathClasses;
+    }
+
     public Collection<DexProgramClass> getSynthesizedClasses() {
       return synthesizedClasses;
     }
diff --git a/src/main/java/com/android/tools/r8/graph/DexByteCodeWriter.java b/src/main/java/com/android/tools/r8/graph/DexByteCodeWriter.java
index d24a75b..60ab622 100644
--- a/src/main/java/com/android/tools/r8/graph/DexByteCodeWriter.java
+++ b/src/main/java/com/android/tools/r8/graph/DexByteCodeWriter.java
@@ -3,6 +3,7 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.graph;
 
+import com.android.tools.r8.dex.Marker;
 import com.android.tools.r8.utils.DescriptorUtils;
 import com.android.tools.r8.utils.InternalOptions;
 import java.io.File;
@@ -10,6 +11,7 @@
 import java.io.PrintStream;
 import java.nio.file.Files;
 import java.nio.file.Path;
+import java.util.List;
 import java.util.function.Consumer;
 
 public abstract class DexByteCodeWriter {
@@ -57,8 +59,12 @@
   }
 
   public void write(PrintStream output) throws IOException {
-    write(x -> output, x -> {
-    });
+    List<Marker> markers = application.dexItemFactory.extractMarkers();
+    System.out.println("Number of markers: " + markers.size());
+    for (Marker marker : markers) {
+      output.println(marker.toString());
+    }
+    write(x -> output, x -> {});
   }
 
   private void write(OutputStreamProvider outputStreamProvider, Consumer<PrintStream> closer)
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 22a830e..f1c0a2f 100644
--- a/src/main/java/com/android/tools/r8/graph/DexClass.java
+++ b/src/main/java/com/android/tools/r8/graph/DexClass.java
@@ -122,6 +122,11 @@
     }
   }
 
+  public abstract void accept(
+      Consumer<DexProgramClass> programClassConsumer,
+      Consumer<DexClasspathClass> classpathClassConsumer,
+      Consumer<DexLibraryClass> libraryClassConsumer);
+
   public void forEachClassMethod(Consumer<? super DexClassAndMethod> consumer) {
     forEachClassMethodMatching(alwaysTrue(), consumer);
   }
@@ -163,6 +168,10 @@
     return Iterables.concat(fields(), methods());
   }
 
+  public Iterable<DexEncodedMember<?, ?>> members(Predicate<DexEncodedMember<?, ?>> predicate) {
+    return Iterables.concat(fields(predicate), methods(predicate));
+  }
+
   public MethodCollection getMethodCollection() {
     return methodCollection;
   }
@@ -171,7 +180,7 @@
     return methodCollection.methods();
   }
 
-  public Iterable<DexEncodedMethod> methods(Predicate<DexEncodedMethod> predicate) {
+  public Iterable<DexEncodedMethod> methods(Predicate<? super DexEncodedMethod> predicate) {
     return methodCollection.methods(predicate);
   }
 
@@ -445,6 +454,15 @@
     return field;
   }
 
+  /** Find method in this class matching {@param method}. */
+  public DexClassAndField lookupClassField(DexField field) {
+    return toClassFieldOrNull(lookupField(field));
+  }
+
+  private DexClassAndField toClassFieldOrNull(DexEncodedField field) {
+    return field != null ? DexClassAndField.create(this, field) : null;
+  }
+
   /** Find field in this class matching {@param field}. */
   public DexEncodedField lookupField(DexField field) {
     DexEncodedField result = lookupInstanceField(field);
@@ -848,6 +866,10 @@
     nestHost = null;
   }
 
+  public void clearNestMembers() {
+    nestMembers.clear();
+  }
+
   public void setNestHost(DexType type) {
     assert type != null;
     this.nestHost = new NestHostClassAttribute(type);
@@ -887,6 +909,10 @@
   /** Returns kotlin class info if the class is synthesized by kotlin compiler. */
   public abstract KotlinClassLevelInfo getKotlinInfo();
 
+  public final String getTypeName() {
+    return getType().getTypeName();
+  }
+
   public boolean hasStaticFields() {
     return staticFields.length > 0;
   }
diff --git a/src/main/java/com/android/tools/r8/graph/DexClassAndField.java b/src/main/java/com/android/tools/r8/graph/DexClassAndField.java
index bba0f82..cef86d0 100644
--- a/src/main/java/com/android/tools/r8/graph/DexClassAndField.java
+++ b/src/main/java/com/android/tools/r8/graph/DexClassAndField.java
@@ -8,15 +8,20 @@
 
   DexClassAndField(DexClass holder, DexEncodedField field) {
     super(holder, field);
+    assert holder.isClasspathClass() == (this instanceof ClasspathField);
+    assert holder.isLibraryClass() == (this instanceof LibraryField);
     assert holder.isProgramClass() == (this instanceof ProgramField);
   }
 
   public static DexClassAndField create(DexClass holder, DexEncodedField field) {
     if (holder.isProgramClass()) {
       return new ProgramField(holder.asProgramClass(), field);
-    } else {
-      return new DexClassAndField(holder, field);
     }
+    if (holder.isLibraryClass()) {
+      return new LibraryField(holder.asLibraryClass(), field);
+    }
+    assert holder.isClasspathClass();
+    return new ClasspathField(holder.asClasspathClass(), field);
   }
 
   @Override
@@ -28,6 +33,22 @@
     return getReference().getType();
   }
 
+  public boolean isClasspathField() {
+    return false;
+  }
+
+  public ClasspathField asClasspathField() {
+    return null;
+  }
+
+  public boolean isLibraryField() {
+    return false;
+  }
+
+  public LibraryField asLibraryField() {
+    return null;
+  }
+
   public boolean isProgramField() {
     return false;
   }
diff --git a/src/main/java/com/android/tools/r8/graph/DexClassAndMember.java b/src/main/java/com/android/tools/r8/graph/DexClassAndMember.java
index 719e2d6..83c4334 100644
--- a/src/main/java/com/android/tools/r8/graph/DexClassAndMember.java
+++ b/src/main/java/com/android/tools/r8/graph/DexClassAndMember.java
@@ -51,6 +51,18 @@
     return holder.origin;
   }
 
+  public boolean isClasspathMember() {
+    return false;
+  }
+
+  public boolean isLibraryMember() {
+    return false;
+  }
+
+  public boolean isProgramMember() {
+    return false;
+  }
+
   public String toSourceString() {
     return getReference().toSourceString();
   }
diff --git a/src/main/java/com/android/tools/r8/graph/DexClassAndMethod.java b/src/main/java/com/android/tools/r8/graph/DexClassAndMethod.java
index 0446e09..075c1ba 100644
--- a/src/main/java/com/android/tools/r8/graph/DexClassAndMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/DexClassAndMethod.java
@@ -9,6 +9,8 @@
 
   DexClassAndMethod(DexClass holder, DexEncodedMethod method) {
     super(holder, method);
+    assert holder.isClasspathClass() == (this instanceof ClasspathMethod);
+    assert holder.isLibraryClass() == (this instanceof LibraryMethod);
     assert holder.isProgramClass() == (this instanceof ProgramMethod);
   }
 
@@ -19,12 +21,12 @@
   public static DexClassAndMethod create(DexClass holder, DexEncodedMethod method) {
     if (holder.isProgramClass()) {
       return new ProgramMethod(holder.asProgramClass(), method);
-    } else if (holder.isLibraryClass()) {
-      return new LibraryMethod(holder.asLibraryClass(), method);
-    } else {
-      assert holder.isClasspathClass();
-      return new ClasspathMethod(holder.asClasspathClass(), method);
     }
+    if (holder.isLibraryClass()) {
+      return new LibraryMethod(holder.asLibraryClass(), method);
+    }
+    assert holder.isClasspathClass();
+    return new ClasspathMethod(holder.asClasspathClass(), method);
   }
 
   public boolean isDefaultMethod() {
diff --git a/src/main/java/com/android/tools/r8/graph/DexClasspathClass.java b/src/main/java/com/android/tools/r8/graph/DexClasspathClass.java
index c97a1a4..7fb6023 100644
--- a/src/main/java/com/android/tools/r8/graph/DexClasspathClass.java
+++ b/src/main/java/com/android/tools/r8/graph/DexClasspathClass.java
@@ -3,6 +3,8 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.graph;
 
+import static com.google.common.base.Predicates.alwaysTrue;
+
 import com.android.tools.r8.ProgramResource;
 import com.android.tools.r8.ProgramResource.Kind;
 import com.android.tools.r8.dex.MixedSectionCollection;
@@ -10,12 +12,17 @@
 import com.android.tools.r8.graph.GenericSignature.ClassSignature;
 import com.android.tools.r8.kotlin.KotlinClassLevelInfo;
 import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.utils.structural.StructuralItem;
+import com.android.tools.r8.utils.structural.StructuralMapping;
+import com.android.tools.r8.utils.structural.StructuralSpecification;
 import java.util.List;
 import java.util.Set;
+import java.util.function.Consumer;
 import java.util.function.Predicate;
 import java.util.function.Supplier;
 
-public class DexClasspathClass extends DexClass implements Supplier<DexClasspathClass> {
+public class DexClasspathClass extends DexClass
+    implements Supplier<DexClasspathClass>, StructuralItem<DexClasspathClass> {
 
   public DexClasspathClass(
       DexType type,
@@ -58,6 +65,24 @@
   }
 
   @Override
+  public void accept(
+      Consumer<DexProgramClass> programClassConsumer,
+      Consumer<DexClasspathClass> classpathClassConsumer,
+      Consumer<DexLibraryClass> libraryClassConsumer) {
+    classpathClassConsumer.accept(this);
+  }
+
+  public void forEachClasspathMethod(Consumer<? super ClasspathMethod> consumer) {
+    forEachClasspathMethodMatching(alwaysTrue(), consumer);
+  }
+
+  public void forEachClasspathMethodMatching(
+      Predicate<DexEncodedMethod> predicate, Consumer<? super ClasspathMethod> consumer) {
+    methodCollection.forEachMethodMatching(
+        predicate, method -> consumer.accept(new ClasspathMethod(this, method)));
+  }
+
+  @Override
   public String toString() {
     return type.toString() + "(classpath class)";
   }
@@ -78,6 +103,10 @@
     return this;
   }
 
+  public static DexClasspathClass asClasspathClassOrNull(DexClass clazz) {
+    return clazz != null ? clazz.asClasspathClass() : null;
+  }
+
   @Override
   public boolean isNotProgramClass() {
     return true;
@@ -104,4 +133,30 @@
     }
     return !isInterface() || appView.options().classpathInterfacesMayHaveStaticInitialization;
   }
+
+  @Override
+  public DexClasspathClass self() {
+    return this;
+  }
+
+  @Override
+  public StructuralMapping<DexClasspathClass> getStructuralMapping() {
+    return DexClasspathClass::specify;
+  }
+
+  private static void specify(StructuralSpecification<DexClasspathClass, ?> spec) {
+    spec.withItem(DexClass::getType)
+        .withItem(DexClass::getSuperType)
+        .withItem(DexClass::getInterfaces)
+        .withItem(DexClass::getAccessFlags)
+        .withNullableItem(DexClass::getSourceFile)
+        .withNullableItem(DexClass::getNestHostClassAttribute)
+        .withItemCollection(DexClass::getNestMembersClassAttributes)
+        .withItem(DexDefinition::annotations)
+        // TODO(b/158159959): Make signatures structural.
+        .withAssert(c -> c.classSignature == ClassSignature.noSignature())
+        .withItemArray(c -> c.staticFields)
+        .withItemArray(c -> c.instanceFields)
+        .withItemCollection(DexClass::allMethodsSorted);
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/graph/DexEncodedField.java b/src/main/java/com/android/tools/r8/graph/DexEncodedField.java
index fc0eeb0..30d5cfd 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedField.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedField.java
@@ -203,10 +203,6 @@
     return accessFlags.isPackagePrivate();
   }
 
-  public boolean isPrivate() {
-    return accessFlags.isPrivate();
-  }
-
   public boolean isProtected() {
     return accessFlags.isProtected();
   }
diff --git a/src/main/java/com/android/tools/r8/graph/DexEncodedMember.java b/src/main/java/com/android/tools/r8/graph/DexEncodedMember.java
index bca8a6b..d5c5135 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedMember.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedMember.java
@@ -35,6 +35,10 @@
     return this;
   }
 
+  public final boolean isPrivate() {
+    return getAccessFlags().isPrivate();
+  }
+
   public abstract ProgramMember<D, R> asProgramMember(DexDefinitionSupplier definitions);
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
index 91203c8..fc56e08 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
@@ -49,7 +49,6 @@
 import com.android.tools.r8.ir.code.NumericType;
 import com.android.tools.r8.ir.code.ValueType;
 import com.android.tools.r8.ir.conversion.DexBuilder;
-import com.android.tools.r8.ir.desugar.NestBasedAccessDesugaring.DexFieldWithAccess;
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
 import com.android.tools.r8.ir.optimize.Inliner.Reason;
 import com.android.tools.r8.ir.optimize.NestUtils;
@@ -60,7 +59,8 @@
 import com.android.tools.r8.ir.optimize.inliner.WhyAreYouNotInliningReporter;
 import com.android.tools.r8.ir.regalloc.RegisterAllocator;
 import com.android.tools.r8.ir.synthetic.EmulateInterfaceSyntheticCfCodeProvider;
-import com.android.tools.r8.ir.synthetic.FieldAccessorSourceCode;
+import com.android.tools.r8.ir.synthetic.FieldAccessorBuilder;
+import com.android.tools.r8.ir.synthetic.ForwardMethodBuilder;
 import com.android.tools.r8.ir.synthetic.ForwardMethodSourceCode;
 import com.android.tools.r8.ir.synthetic.SynthesizedCode;
 import com.android.tools.r8.kotlin.KotlinMethodLevelInfo;
@@ -76,14 +76,12 @@
 import com.android.tools.r8.utils.OptionalBool;
 import com.android.tools.r8.utils.Pair;
 import com.android.tools.r8.utils.structural.CompareToVisitor;
-import com.android.tools.r8.utils.structural.CompareToVisitorWithTypeEquivalence;
 import com.android.tools.r8.utils.structural.HashingVisitor;
-import com.android.tools.r8.utils.structural.HashingVisitorWithTypeEquivalence;
 import com.android.tools.r8.utils.structural.Ordered;
-import com.android.tools.r8.utils.structural.RepresentativeMap;
+import com.android.tools.r8.utils.structural.StructuralItem;
+import com.android.tools.r8.utils.structural.StructuralMapping;
 import com.android.tools.r8.utils.structural.StructuralSpecification;
 import com.google.common.collect.ImmutableList;
-import com.google.common.hash.Hasher;
 import it.unimi.dsi.fastutil.ints.Int2ReferenceArrayMap;
 import it.unimi.dsi.fastutil.ints.Int2ReferenceMap;
 import java.util.ArrayList;
@@ -95,7 +93,8 @@
 import java.util.function.IntPredicate;
 import org.objectweb.asm.Opcodes;
 
-public class DexEncodedMethod extends DexEncodedMember<DexEncodedMethod, DexMethod> {
+public class DexEncodedMethod extends DexEncodedMember<DexEncodedMethod, DexMethod>
+    implements StructuralItem<DexEncodedMethod> {
 
   public static final String CONFIGURATION_DEBUGGING_PREFIX = "Shaking error: Missing method in ";
 
@@ -338,12 +337,22 @@
     return deprecated;
   }
 
+  @Override
+  public DexEncodedMethod self() {
+    return this;
+  }
+
+  @Override
+  public StructuralMapping<DexEncodedMethod> getStructuralMapping() {
+    return DexEncodedMethod::syntheticSpecify;
+  }
+
   // Visitor specifying the structure of the method with respect to its "synthetic" content.
   // TODO(b/171867022): Generalize this so that it determines any method in full.
   private static void syntheticSpecify(StructuralSpecification<DexEncodedMethod, ?> spec) {
     spec.withItem(m -> m.method)
         .withItem(m -> m.accessFlags)
-        .withItem(m -> m.annotations())
+        .withItem(DexDefinition::annotations)
         .withItem(m -> m.parameterAnnotationsList)
         .withNullableItem(m -> m.classFileVersion)
         .withBool(m -> m.d8R8Synthesized)
@@ -356,28 +365,12 @@
             DexEncodedMethod::hashCodeObject);
   }
 
-  public void hashSyntheticContent(Hasher hasher, RepresentativeMap map) {
-    HashingVisitorWithTypeEquivalence.run(this, hasher, map, DexEncodedMethod::syntheticSpecify);
-  }
-
-  public boolean isSyntheticContentEqual(DexEncodedMethod other) {
-    return syntheticCompareTo(other) == 0;
-  }
-
-  public int syntheticCompareTo(DexEncodedMethod other) {
-    // Consider the holder types to be equivalent, using the holder of this method as the
-    // representative.
-    RepresentativeMap map = t -> t == other.getHolderType() ? getHolderType() : t;
-    return CompareToVisitorWithTypeEquivalence.run(
-        this, other, map, DexEncodedMethod::syntheticSpecify);
-  }
-
   private static int compareCodeObject(Code code1, Code code2, CompareToVisitor visitor) {
     if (code1.isCfCode() && code2.isCfCode()) {
       return code1.asCfCode().acceptCompareTo(code2.asCfCode(), visitor);
     }
     if (code1.isDexCode() && code2.isDexCode()) {
-      return visitor.visit(code1.asDexCode(), code2.asDexCode(), DexCode::compareTo);
+      return code1.asDexCode().acceptCompareTo(code2.asDexCode(), visitor);
     }
     throw new Unreachable(
         "Unexpected attempt to compare incompatible synthetic objects: " + code1 + " and " + code2);
@@ -387,14 +380,12 @@
     if (code.isCfCode()) {
       code.asCfCode().acceptHashing(visitor);
     } else {
-      // TODO(b/158159959): Implement a more precise hashing on code objects.
-      assert code.isDexCode();
-      visitor.visitInt(code.hashCode());
+      code.asDexCode().acceptHashing(visitor);
     }
   }
 
   public DexProto getProto() {
-    return getReference().proto;
+    return getReference().getProto();
   }
 
   @Override
@@ -411,10 +402,6 @@
     return method.proto.parameters;
   }
 
-  public DexProto proto() {
-    return method.proto;
-  }
-
   public DexType returnType() {
     return method.proto.returnType;
   }
@@ -506,10 +493,6 @@
     return accessFlags.isNative();
   }
 
-  public boolean isPrivate() {
-    return accessFlags.isPrivate();
-  }
-
   public boolean isPublic() {
     return accessFlags.isPublic();
   }
@@ -1016,6 +999,10 @@
   }
 
   public CfCode buildEmptyThrowingCfCode() {
+    return buildEmptyThrowingCfCode(method);
+  }
+
+  public static CfCode buildEmptyThrowingCfCode(DexMethod method) {
     CfInstruction insn[] = {new CfConstNull(), new CfThrow()};
     return new CfCode(
         method.holder,
@@ -1201,59 +1188,47 @@
     return builder.build();
   }
 
-  public ProgramMethod toInitializerForwardingBridge(DexProgramClass holder, DexMethod newMethod) {
-    assert accessFlags.isPrivate()
+  public ProgramMethod toInitializerForwardingBridge(
+      DexProgramClass holder, DexMethod newMethod, DexItemFactory dexItemFactory) {
+    assert isPrivate()
         : "Expected to create bridge for private constructor as part of nest-based access"
             + " desugaring";
-    Builder builder = syntheticBuilder(this);
-    builder.setMethod(newMethod);
-    ForwardMethodSourceCode.Builder forwardSourceCodeBuilder =
-        ForwardMethodSourceCode.builder(newMethod);
-    forwardSourceCodeBuilder
-        .setReceiver(holder.type)
-        .setTargetReceiver(holder.type)
-        .setTarget(method)
-        .setInvokeType(Invoke.Type.DIRECT)
-        .setExtraNullParameter();
-    builder.setCode(
-        new SynthesizedCode(
-            forwardSourceCodeBuilder::build, registry -> registry.registerInvokeDirect(method)));
-    assert !builder.accessFlags.isStatic();
     assert !holder.isInterface();
-    builder.accessFlags.unsetPrivate();
-    builder.accessFlags.setSynthetic();
-    builder.accessFlags.setConstructor();
-    return new ProgramMethod(holder, builder.build());
+    return new ProgramMethod(
+        holder,
+        syntheticBuilder(this)
+            .setMethod(newMethod)
+            .setCode(
+                ForwardMethodBuilder.builder(dexItemFactory)
+                    .setNonStaticSourceWithExtraUnusedParameter(newMethod)
+                    .setConstructorTarget(getReference())
+                    .build())
+            .modifyAccessFlags(
+                accessFlags -> {
+                  assert !accessFlags.isStatic();
+                  accessFlags.unsetPrivate();
+                  accessFlags.setSynthetic();
+                  accessFlags.setConstructor();
+                })
+            .build());
   }
 
   public static ProgramMethod createFieldAccessorBridge(
-      DexFieldWithAccess fieldWithAccess, DexProgramClass holder, DexMethod newMethod) {
-    assert holder.type == fieldWithAccess.getHolder();
+      ProgramField field, boolean isGet, DexMethod newMethod) {
     MethodAccessFlags accessFlags =
         MethodAccessFlags.fromSharedAccessFlags(
             Constants.ACC_SYNTHETIC
                 | Constants.ACC_STATIC
-                | (holder.isInterface() ? Constants.ACC_PUBLIC : 0),
+                | (field.getHolder().isInterface() ? Constants.ACC_PUBLIC : 0),
             false);
-    Code code =
-        new SynthesizedCode(
-            callerPosition ->
-                new FieldAccessorSourceCode(
-                    null, newMethod, callerPosition, newMethod, fieldWithAccess),
-            registry -> {
-              if (fieldWithAccess.isInstanceGet()) {
-                registry.registerInstanceFieldRead(fieldWithAccess.getField());
-              } else if (fieldWithAccess.isStaticGet()) {
-                registry.registerStaticFieldRead(fieldWithAccess.getField());
-              } else if (fieldWithAccess.isInstancePut()) {
-                registry.registerInstanceFieldWrite(fieldWithAccess.getField());
-              } else {
-                assert fieldWithAccess.isStaticPut();
-                registry.registerStaticFieldWrite(fieldWithAccess.getField());
-              }
-            });
+    CfCode code =
+        FieldAccessorBuilder.builder()
+            .apply(isGet ? FieldAccessorBuilder::setGetter : FieldAccessorBuilder::setSetter)
+            .setField(field)
+            .setSourceMethod(newMethod)
+            .build();
     return new ProgramMethod(
-        holder,
+        field.getHolder(),
         new DexEncodedMethod(
             newMethod,
             accessFlags,
@@ -1294,35 +1269,36 @@
         true);
   }
 
-  public ProgramMethod toStaticForwardingBridge(DexProgramClass holder, DexMethod newMethod) {
-    assert accessFlags.isPrivate()
+  public ProgramMethod toStaticForwardingBridge(
+      DexProgramClass holder, DexMethod newMethod, DexItemFactory dexItemFactory) {
+    assert isPrivate()
         : "Expected to create bridge for private method as part of nest-based access desugaring";
-    Builder builder = syntheticBuilder(this);
-    builder.setMethod(newMethod);
-    ForwardMethodSourceCode.Builder forwardSourceCodeBuilder =
-        ForwardMethodSourceCode.builder(newMethod);
-    forwardSourceCodeBuilder
-        .setTargetReceiver(accessFlags.isStatic() ? null : method.holder)
-        .setTarget(method)
-        .setInvokeType(accessFlags.isStatic() ? Invoke.Type.STATIC : Invoke.Type.DIRECT)
-        .setIsInterface(holder.isInterface());
-    builder.setCode(
-        new SynthesizedCode(
-            forwardSourceCodeBuilder::build,
-            registry -> {
-              if (accessFlags.isStatic()) {
-                registry.registerInvokeStatic(method);
-              } else {
-                registry.registerInvokeDirect(method);
-              }
-            }));
-    builder.accessFlags.setSynthetic();
-    builder.accessFlags.setStatic();
-    builder.accessFlags.unsetPrivate();
-    if (holder.isInterface()) {
-      builder.accessFlags.setPublic();
-    }
-    return new ProgramMethod(holder, builder.build());
+    return new ProgramMethod(
+        holder,
+        syntheticBuilder(this)
+            .setMethod(newMethod)
+            .setCode(
+                ForwardMethodBuilder.builder(dexItemFactory)
+                    .setStaticSource(newMethod)
+                    .apply(
+                        builder -> {
+                          if (isStatic()) {
+                            builder.setStaticTarget(getReference(), holder.isInterface());
+                          } else {
+                            builder.setDirectTarget(getReference(), holder.isInterface());
+                          }
+                        })
+                    .build())
+            .modifyAccessFlags(
+                accessFlags -> {
+                  accessFlags.setSynthetic();
+                  accessFlags.setStatic();
+                  accessFlags.unsetPrivate();
+                  if (holder.isInterface()) {
+                    accessFlags.setPublic();
+                  }
+                })
+            .build());
   }
 
   public DexEncodedMethod toPrivateSyntheticMethod(DexMethod method) {
@@ -1596,12 +1572,18 @@
       return this;
     }
 
+    public Builder modifyAccessFlags(Consumer<MethodAccessFlags> consumer) {
+      consumer.accept(accessFlags);
+      return this;
+    }
+
     public void setAccessFlags(MethodAccessFlags accessFlags) {
       this.accessFlags = accessFlags.copy();
     }
 
-    public void setMethod(DexMethod method) {
+    public Builder setMethod(DexMethod method) {
       this.method = method;
+      return this;
     }
 
     public Builder setCompilationState(CompilationState compilationState) {
@@ -1686,8 +1668,9 @@
       return this;
     }
 
-    public void setCode(Code code) {
+    public Builder setCode(Code code) {
       this.code = code;
+      return this;
     }
 
     public DexEncodedMethod build() {
diff --git a/src/main/java/com/android/tools/r8/graph/DexField.java b/src/main/java/com/android/tools/r8/graph/DexField.java
index e9c2fc5..a925fcc 100644
--- a/src/main/java/com/android/tools/r8/graph/DexField.java
+++ b/src/main/java/com/android/tools/r8/graph/DexField.java
@@ -52,6 +52,11 @@
   }
 
   @Override
+  public DexClassAndField lookupMemberOnClass(DexClass clazz) {
+    return clazz != null ? clazz.lookupClassField(this) : null;
+  }
+
+  @Override
   public ProgramField lookupOnProgramClass(DexProgramClass clazz) {
     return clazz != null ? clazz.lookupProgramField(this) : null;
   }
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 e6d824a..fd33483 100644
--- a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
+++ b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
@@ -4,7 +4,6 @@
 package com.android.tools.r8.graph;
 
 import static com.android.tools.r8.ir.analysis.type.ClassTypeElement.computeLeastUpperBoundOfInterfaces;
-import static com.android.tools.r8.ir.optimize.ServiceLoaderRewriter.SERVICE_LOADER_CLASS_NAME;
 
 import com.android.tools.r8.dex.Constants;
 import com.android.tools.r8.dex.Marker;
@@ -20,12 +19,13 @@
 import com.android.tools.r8.graph.DexMethodHandle.MethodHandleType;
 import com.android.tools.r8.ir.analysis.type.ArrayTypeElement;
 import com.android.tools.r8.ir.analysis.type.ClassTypeElement;
+import com.android.tools.r8.ir.analysis.type.InterfaceCollection;
 import com.android.tools.r8.ir.analysis.type.Nullability;
 import com.android.tools.r8.ir.analysis.type.ReferenceTypeElement;
 import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.code.Position;
 import com.android.tools.r8.ir.code.Value;
-import com.android.tools.r8.ir.desugar.NestBasedAccessDesugaring;
+import com.android.tools.r8.ir.desugar.nest.NestBasedAccessDesugaring;
 import com.android.tools.r8.kotlin.Kotlin;
 import com.android.tools.r8.utils.ArrayUtils;
 import com.android.tools.r8.utils.DescriptorUtils;
@@ -44,7 +44,6 @@
 import it.unimi.dsi.fastutil.ints.Int2ReferenceOpenHashMap;
 import java.util.ArrayList;
 import java.util.Arrays;
-import java.util.Collections;
 import java.util.HashMap;
 import java.util.IdentityHashMap;
 import java.util.Iterator;
@@ -90,9 +89,9 @@
   // ReferenceTypeElement canonicalization.
   private final ConcurrentHashMap<DexType, ReferenceTypeElement> referenceTypes =
       new ConcurrentHashMap<>();
-  private final ConcurrentHashMap<DexType, Set<DexType>> classTypeInterfaces =
+  private final ConcurrentHashMap<DexType, InterfaceCollection> classTypeInterfaces =
       new ConcurrentHashMap<>();
-  public final LRUCacheTable<Set<DexType>, Set<DexType>, Set<DexType>>
+  public final LRUCacheTable<InterfaceCollection, InterfaceCollection, InterfaceCollection>
       leastUpperBoundOfInterfacesTable = LRUCacheTable.create(8, 8);
 
   boolean sorted = false;
@@ -231,6 +230,8 @@
   public final DexString iterableDescriptor = createString("Ljava/lang/Iterable;");
   public final DexString mathDescriptor = createString("Ljava/lang/Math;");
   public final DexString strictMathDescriptor = createString("Ljava/lang/StrictMath;");
+  public final DexString closeableDescriptor = createString("Ljava/io/Closeable;");
+  public final DexString zipFileDescriptor = createString("Ljava/util/zip/ZipFile;");
 
   public final DexString stringBuilderDescriptor = createString("Ljava/lang/StringBuilder;");
   public final DexString stringBufferDescriptor = createString("Ljava/lang/StringBuffer;");
@@ -292,6 +293,11 @@
       createString(Constants.TEMPORARY_INSTANCE_INITIALIZER_PREFIX);
 
   public final DexString thisName = createString("this");
+
+  // 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.
+  // The field name is unrealiable since the filed can be minified prior to this compilation.
+  // We keep enumValuesFieldName as a heuristic only.
   public final DexString enumValuesFieldName = createString("$VALUES");
 
   public final DexString enabledFieldName = createString("ENABLED");
@@ -299,6 +305,7 @@
   public final DexString throwableArrayDescriptor = createString("[Ljava/lang/Throwable;");
 
   public final DexString valueString = createString("value");
+  public final DexString kindString = createString("kind");
 
   public final DexType booleanType = createStaticallyKnownType(booleanDescriptor);
   public final DexType byteType = createStaticallyKnownType(byteDescriptor);
@@ -356,6 +363,9 @@
   public final DexType methodType = createStaticallyKnownType(methodDescriptor);
   public final DexType autoCloseableType = createStaticallyKnownType(autoCloseableDescriptor);
 
+  public final DexType closeableType = createStaticallyKnownType(closeableDescriptor);
+  public final DexType zipFileType = createStaticallyKnownType(zipFileDescriptor);
+
   public final DexType stringBuilderType = createStaticallyKnownType(stringBuilderDescriptor);
   public final DexType stringBufferType = createStaticallyKnownType(stringBufferDescriptor);
 
@@ -369,8 +379,6 @@
       createStaticallyKnownType(invocationHandlerDescriptor);
   public final DexType proxyType = createStaticallyKnownType(proxyDescriptor);
   public final DexType serviceLoaderType = createStaticallyKnownType(serviceLoaderDescriptor);
-  public final DexType serviceLoaderRewrittenClassType =
-      createStaticallyKnownType("L" + SERVICE_LOADER_CLASS_NAME + ";");
   public final DexType serviceLoaderConfigurationErrorType =
       createStaticallyKnownType(serviceLoaderConfigurationErrorDescriptor);
   public final DexType listType = createStaticallyKnownType(listDescriptor);
@@ -409,6 +417,8 @@
   public final DexType noClassDefFoundErrorType =
       createStaticallyKnownType(noClassDefFoundErrorDescriptor);
   public final DexType noSuchFieldErrorType = createStaticallyKnownType(noSuchFieldErrorDescriptor);
+  public final DexType noSuchMethodErrorType =
+      createStaticallyKnownType("Ljava/lang/NoSuchMethodError;");
   public final DexType npeType = createStaticallyKnownType(npeDescriptor);
   public final DexType reflectiveOperationExceptionType =
       createStaticallyKnownType(reflectiveOperationExceptionDescriptor);
@@ -1380,21 +1390,17 @@
       return field == nameField || field == ordinalField;
     }
 
-    public boolean isValuesMethod(DexMethod method, DexClass enumClass) {
-      assert enumClass.isEnum();
-      return method.holder == enumClass.type
-          && method.proto.returnType == enumClass.type.toArrayType(1, DexItemFactory.this)
-          && method.proto.parameters.size() == 0
-          && method.name == valuesMethodName;
+    public boolean isEnumField(DexEncodedField staticField, DexType enumType) {
+      assert staticField.isStatic();
+      return staticField.getType() == enumType && staticField.isEnum() && staticField.isFinal();
     }
 
-    public boolean isValueOfMethod(DexMethod method, DexClass enumClass) {
-      assert enumClass.isEnum();
-      return method.holder == enumClass.type
-          && method.proto.returnType == enumClass.type
-          && method.proto.parameters.size() == 1
-          && method.proto.parameters.values[0] == stringType
-          && method.name == valueOfMethodName;
+    public boolean isValuesFieldCandidate(DexEncodedField staticField, DexType enumType) {
+      assert staticField.isStatic();
+      return staticField.getType().isArrayType()
+          && staticField.getType().toArrayElementType(DexItemFactory.this) == enumType
+          && staticField.isSynthetic()
+          && staticField.isFinal();
     }
   }
 
@@ -2420,12 +2426,12 @@
               if (type.isClassType()) {
                 if (!appView.enableWholeProgramOptimizations()) {
                   // Don't reason at the level of interfaces in D8.
-                  return ClassTypeElement.create(type, nullability, Collections.emptySet());
+                  return ClassTypeElement.create(type, nullability, InterfaceCollection.empty());
                 }
                 assert appView.appInfo().hasClassHierarchy();
                 if (appView.isInterface(type).isTrue()) {
                   return ClassTypeElement.create(
-                      objectType, nullability, Collections.singleton(type));
+                      objectType, nullability, InterfaceCollection.singleton(type));
                 }
                 // In theory, `interfaces` is the least upper bound of implemented interfaces.
                 // It is expensive to walk through type hierarchy; collect implemented interfaces;
@@ -2440,12 +2446,12 @@
         .getOrCreateVariant(nullability);
   }
 
-  public Set<DexType> getOrComputeLeastUpperBoundOfImplementedInterfaces(
+  public InterfaceCollection getOrComputeLeastUpperBoundOfImplementedInterfaces(
       DexType type, AppView<? extends AppInfoWithClassHierarchy> appView) {
     return classTypeInterfaces.computeIfAbsent(
         type,
         t -> {
-          Set<DexType> itfs = appView.appInfo().implementedInterfaces(t);
+          InterfaceCollection itfs = appView.appInfo().implementedInterfaces(t);
           return computeLeastUpperBoundOfInterfaces(appView, itfs, itfs);
         });
   }
diff --git a/src/main/java/com/android/tools/r8/graph/DexLibraryClass.java b/src/main/java/com/android/tools/r8/graph/DexLibraryClass.java
index e5a8097..df36fb7 100644
--- a/src/main/java/com/android/tools/r8/graph/DexLibraryClass.java
+++ b/src/main/java/com/android/tools/r8/graph/DexLibraryClass.java
@@ -13,6 +13,7 @@
 import java.util.Arrays;
 import java.util.List;
 import java.util.Set;
+import java.util.function.Consumer;
 import java.util.function.Predicate;
 import java.util.function.Supplier;
 
@@ -85,6 +86,14 @@
   }
 
   @Override
+  public void accept(
+      Consumer<DexProgramClass> programClassConsumer,
+      Consumer<DexClasspathClass> classpathClassConsumer,
+      Consumer<DexLibraryClass> libraryClassConsumer) {
+    libraryClassConsumer.accept(this);
+  }
+
+  @Override
   public String toString() {
     return type.toString() + "(library class)";
   }
diff --git a/src/main/java/com/android/tools/r8/graph/DexMember.java b/src/main/java/com/android/tools/r8/graph/DexMember.java
index 1c02f3b..9b62855 100644
--- a/src/main/java/com/android/tools/r8/graph/DexMember.java
+++ b/src/main/java/com/android/tools/r8/graph/DexMember.java
@@ -24,6 +24,8 @@
 
   public abstract DexEncodedMember<?, ?> lookupOnClass(DexClass clazz);
 
+  public abstract DexClassAndMember<?, ?> lookupMemberOnClass(DexClass clazz);
+
   public abstract ProgramMember<?, ?> lookupOnProgramClass(DexProgramClass clazz);
 
   public abstract boolean match(R entry);
diff --git a/src/main/java/com/android/tools/r8/graph/DexMethod.java b/src/main/java/com/android/tools/r8/graph/DexMethod.java
index d35b1a0..29e9792 100644
--- a/src/main/java/com/android/tools/r8/graph/DexMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/DexMethod.java
@@ -102,6 +102,11 @@
   }
 
   @Override
+  public DexClassAndMethod lookupMemberOnClass(DexClass clazz) {
+    return clazz != null ? clazz.lookupClassMethod(this) : null;
+  }
+
+  @Override
   public ProgramMethod lookupOnProgramClass(DexProgramClass clazz) {
     return clazz != null ? clazz.lookupProgramMethod(this) : null;
   }
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 2a6ebf5..618ff5b 100644
--- a/src/main/java/com/android/tools/r8/graph/DexProgramClass.java
+++ b/src/main/java/com/android/tools/r8/graph/DexProgramClass.java
@@ -21,6 +21,9 @@
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.utils.TraversalContinuation;
 import com.android.tools.r8.utils.structural.Ordered;
+import com.android.tools.r8.utils.structural.StructuralItem;
+import com.android.tools.r8.utils.structural.StructuralMapping;
+import com.android.tools.r8.utils.structural.StructuralSpecification;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.Iterables;
 import java.util.ArrayList;
@@ -37,7 +40,7 @@
 import java.util.function.Supplier;
 
 public class DexProgramClass extends DexClass
-    implements ProgramDefinition, Supplier<DexProgramClass> {
+    implements ProgramDefinition, Supplier<DexProgramClass>, StructuralItem<DexProgramClass> {
 
   @FunctionalInterface
   public interface ChecksumSupplier {
@@ -144,6 +147,44 @@
     synthesizedDirectlyFrom.forEach(this::addSynthesizedFrom);
   }
 
+  @Override
+  public void accept(
+      Consumer<DexProgramClass> programClassConsumer,
+      Consumer<DexClasspathClass> classpathClassConsumer,
+      Consumer<DexLibraryClass> libraryClassConsumer) {
+    programClassConsumer.accept(this);
+  }
+
+  @Override
+  public DexProgramClass self() {
+    return this;
+  }
+
+  @Override
+  public StructuralMapping<DexProgramClass> getStructuralMapping() {
+    return DexProgramClass::specify;
+  }
+
+  private static void specify(StructuralSpecification<DexProgramClass, ?> spec) {
+    spec.withItem(c -> c.type)
+        .withItem(c -> c.superType)
+        .withItem(c -> c.interfaces)
+        .withItem(c -> c.accessFlags)
+        .withNullableItem(c -> c.sourceFile)
+        .withNullableItem(c -> c.initialClassFileVersion)
+        .withBool(c -> c.deprecated)
+        .withNullableItem(DexClass::getNestHostClassAttribute)
+        .withItemCollection(DexClass::getNestMembersClassAttributes)
+        .withItem(DexDefinition::annotations)
+        // TODO(b/158159959): Make signatures structural.
+        .withAssert(c -> c.classSignature == ClassSignature.noSignature())
+        .withItemArray(c -> c.staticFields)
+        .withItemArray(c -> c.instanceFields)
+        .withItemCollection(DexClass::allMethodsSorted)
+        // TODO(b/168584485): Synthesized-from is being removed (empty for new synthetics).
+        .withAssert(c -> c.synthesizedFrom.isEmpty());
+  }
+
   public void forEachProgramField(Consumer<? super ProgramField> consumer) {
     forEachField(field -> consumer.accept(new ProgramField(this, field)));
   }
@@ -171,6 +212,15 @@
     return Iterables.transform(directMethods(predicate), method -> new ProgramMethod(this, method));
   }
 
+  public Iterable<ProgramMethod> virtualProgramMethods() {
+    return Iterables.transform(virtualMethods(), method -> new ProgramMethod(this, method));
+  }
+
+  public Iterable<ProgramMethod> virtualProgramMethods(Predicate<DexEncodedMethod> predicate) {
+    return Iterables.transform(
+        virtualMethods(predicate), method -> new ProgramMethod(this, method));
+  }
+
   public Iterable<ProgramMethod> programInstanceInitializers() {
     return directProgramMethods(DexEncodedMethod::isInstanceInitializer);
   }
diff --git a/src/main/java/com/android/tools/r8/graph/DexType.java b/src/main/java/com/android/tools/r8/graph/DexType.java
index 27284ea..56f9a92 100644
--- a/src/main/java/com/android/tools/r8/graph/DexType.java
+++ b/src/main/java/com/android/tools/r8/graph/DexType.java
@@ -8,7 +8,6 @@
 import static com.android.tools.r8.ir.desugar.DesugaredLibraryWrapperSynthesizer.VIVIFIED_TYPE_WRAPPER_SUFFIX;
 import static com.android.tools.r8.ir.desugar.InterfaceMethodRewriter.COMPANION_CLASS_NAME_SUFFIX;
 import static com.android.tools.r8.ir.desugar.InterfaceMethodRewriter.EMULATE_LIBRARY_CLASS_NAME_SUFFIX;
-import static com.android.tools.r8.ir.desugar.LambdaRewriter.LAMBDA_CLASS_NAME_PREFIX;
 import static com.android.tools.r8.ir.desugar.LambdaRewriter.LAMBDA_GROUP_CLASS_NAME_PREFIX;
 import static com.android.tools.r8.ir.optimize.enums.UnboxedEnumMemberRelocator.ENUM_UNBOXING_UTILITY_CLASS_SUFFIX;
 
@@ -16,11 +15,9 @@
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.horizontalclassmerging.SyntheticArgumentClass;
 import com.android.tools.r8.ir.desugar.DesugaredLibraryRetargeter;
-import com.android.tools.r8.ir.desugar.NestBasedAccessDesugaring;
-import com.android.tools.r8.ir.desugar.TwrCloseResourceRewriter;
-import com.android.tools.r8.ir.optimize.ServiceLoaderRewriter;
+import com.android.tools.r8.ir.desugar.nest.NestBasedAccessDesugaring;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
-import com.android.tools.r8.synthesis.SyntheticItems;
+import com.android.tools.r8.synthesis.SyntheticNaming;
 import com.android.tools.r8.utils.DescriptorUtils;
 import com.android.tools.r8.utils.InternalOptions.OutlineOptions;
 import com.android.tools.r8.utils.structural.CompareToVisitor;
@@ -41,7 +38,13 @@
   // Bundletool is merging classes that may originate from a build with an old version of R8.
   // Allow merging of classes that use names from older versions of R8.
   private static List<String> OLD_SYNTHESIZED_NAMES =
-      ImmutableList.of("$r8$backportedMethods$utility", "$r8$java8methods$utility", "$-DC");
+      ImmutableList.of(
+          "$r8$backportedMethods$utility",
+          "$r8$java8methods$utility",
+          "$r8$twr$utility",
+          "$-DC",
+          "$$ServiceLoaderMethods",
+          "-$$Lambda$");
 
   public final DexString descriptor;
   private String toStringCache = null;
@@ -172,6 +175,10 @@
     classConsumer.accept(this, arg);
   }
 
+  public String getTypeName() {
+    return toSourceString();
+  }
+
   @Override
   public String toSourceString() {
     if (toStringCache == null) {
@@ -302,12 +309,6 @@
   }
 
   // TODO(b/158159959): Remove usage of name-based identification.
-  public boolean isD8R8SynthesizedLambdaClassType() {
-    String name = toSourceString();
-    return name.contains(LAMBDA_CLASS_NAME_PREFIX);
-  }
-
-  // TODO(b/158159959): Remove usage of name-based identification.
   public boolean isD8R8SynthesizedClassType() {
     String name = toSourceString();
     // The synthesized classes listed here must always be unique to a program context and thus
@@ -318,8 +319,7 @@
         || name.contains(ENUM_UNBOXING_UTILITY_CLASS_SUFFIX)
         || name.contains(SyntheticArgumentClass.SYNTHETIC_CLASS_SUFFIX)
         // New and hygienic synthesis infrastructure.
-        || name.contains(SyntheticItems.INTERNAL_SYNTHETIC_CLASS_SEPARATOR)
-        || name.contains(SyntheticItems.EXTERNAL_SYNTHETIC_CLASS_SEPARATOR)
+        || SyntheticNaming.isSyntheticName(name)
         // Only generated in core lib.
         || name.contains(EMULATE_LIBRARY_CLASS_NAME_SUFFIX)
         || name.contains(TYPE_WRAPPER_SUFFIX)
@@ -337,12 +337,9 @@
   private static boolean isSynthesizedTypeThatCouldBeDuplicated(String name) {
     // Any entry that is removed from here must be added to OLD_SYNTHESIZED_NAMES to ensure that
     // newer releases can be used to merge previous builds.
-    return name.contains(LAMBDA_CLASS_NAME_PREFIX) // Could collide.
-        || name.contains(LAMBDA_GROUP_CLASS_NAME_PREFIX) // Could collide.
+    return name.contains(LAMBDA_GROUP_CLASS_NAME_PREFIX) // Could collide.
         || name.contains(OutlineOptions.CLASS_NAME) // Global singleton.
-        || name.contains(TwrCloseResourceRewriter.UTILITY_CLASS_NAME) // Global singleton.
-        || name.contains(NestBasedAccessDesugaring.NEST_CONSTRUCTOR_NAME) // Global singleton.
-        || name.contains(ServiceLoaderRewriter.SERVICE_LOADER_CLASS_NAME); // Global singleton.
+        || name.contains(NestBasedAccessDesugaring.NEST_CONSTRUCTOR_NAME); // Global singleton.
   }
 
   private boolean oldSynthesizedName(String name) {
diff --git a/src/main/java/com/android/tools/r8/graph/DexTypeList.java b/src/main/java/com/android/tools/r8/graph/DexTypeList.java
index 076322a..07410c0 100644
--- a/src/main/java/com/android/tools/r8/graph/DexTypeList.java
+++ b/src/main/java/com/android/tools/r8/graph/DexTypeList.java
@@ -54,6 +54,10 @@
     return values.isEmpty() ? DexTypeList.empty() : new DexTypeList(values);
   }
 
+  public DexType get(int index) {
+    return values[index];
+  }
+
   public DexTypeList keepIf(Predicate<DexType> predicate) {
     DexType[] filtered = ArrayUtils.filter(DexType[].class, values, predicate);
     if (filtered != values) {
diff --git a/src/main/java/com/android/tools/r8/graph/DirectMappedDexApplication.java b/src/main/java/com/android/tools/r8/graph/DirectMappedDexApplication.java
index 5a7d18f..4f38fb2 100644
--- a/src/main/java/com/android/tools/r8/graph/DirectMappedDexApplication.java
+++ b/src/main/java/com/android/tools/r8/graph/DirectMappedDexApplication.java
@@ -63,11 +63,12 @@
     return programClasses;
   }
 
-  public Collection<DexLibraryClass> libraryClasses() {
+  public List<DexLibraryClass> libraryClasses() {
     return libraryClasses;
   }
 
-  public Collection<DexClasspathClass> classpathClasses() {
+  @Override
+  public List<DexClasspathClass> classpathClasses() {
     return classpathClasses;
   }
 
@@ -181,21 +182,19 @@
   public static class Builder extends DexApplication.Builder<Builder> {
 
     private ImmutableList<DexLibraryClass> libraryClasses;
-    private ImmutableList<DexClasspathClass> classpathClasses;
 
     Builder(LazyLoadedDexApplication application) {
       super(application);
       // As a side-effect, this will force-load all classes.
       AllClasses allClasses = application.loadAllClasses();
       libraryClasses = allClasses.getLibraryClasses();
-      classpathClasses = allClasses.getClasspathClasses();
       replaceProgramClasses(allClasses.getProgramClasses());
+      replaceClasspathClasses(allClasses.getClasspathClasses());
     }
 
     private Builder(DirectMappedDexApplication application) {
       super(application);
       libraryClasses = application.libraryClasses;
-      classpathClasses = application.classpathClasses;
     }
 
     @Override
@@ -208,20 +207,6 @@
       return self();
     }
 
-    public Builder replaceClasspathClasses(Collection<DexClasspathClass> classpathClasses) {
-      this.classpathClasses = ImmutableList.copyOf(classpathClasses);
-      return self();
-    }
-
-    public Builder addClasspathClasses(Collection<DexClasspathClass> classes) {
-      classpathClasses =
-          ImmutableList.<DexClasspathClass>builder()
-              .addAll(classpathClasses)
-              .addAll(classes)
-              .build();
-      return self();
-    }
-
     public Builder addLibraryClasses(Collection<DexLibraryClass> classes) {
       libraryClasses =
           ImmutableList.<DexLibraryClass>builder().addAll(libraryClasses).addAll(classes).build();
@@ -234,17 +219,17 @@
       // TODO(zerny): Consider not rebuilding the map if no program classes are added.
       Map<DexType, DexClass> allClasses =
           new IdentityHashMap<>(
-              programClasses.size() + classpathClasses.size() + libraryClasses.size());
+              getProgramClasses().size() + getClasspathClasses().size() + libraryClasses.size());
       // Note: writing classes in reverse priority order, so a duplicate will be correctly ordered.
       // There should never be duplicates and that is asserted in the addAll subroutine.
       addAll(allClasses, libraryClasses);
-      addAll(allClasses, classpathClasses);
-      addAll(allClasses, programClasses);
+      addAll(allClasses, getClasspathClasses());
+      addAll(allClasses, getProgramClasses());
       return new DirectMappedDexApplication(
           proguardMap,
           allClasses,
-          ImmutableList.copyOf(programClasses),
-          classpathClasses,
+          ImmutableList.copyOf(getProgramClasses()),
+          ImmutableList.copyOf(getClasspathClasses()),
           libraryClasses,
           ImmutableList.copyOf(dataResourceProviders),
           options,
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 0e3d784..ca9a57a 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_CLASS_NAME_PREFIX;
 import static com.android.tools.r8.ir.desugar.LambdaRewriter.LAMBDA_INSTANCE_FIELD_NAME;
 
 import com.android.tools.r8.errors.Unreachable;
@@ -573,6 +572,14 @@
     return builder.build();
   }
 
+  public ImmutableSet<DexField> rewriteFields(Set<DexField> fields) {
+    ImmutableSet.Builder<DexField> builder = ImmutableSet.builder();
+    for (DexField field : fields) {
+      builder.add(getRenamedFieldSignature(field));
+    }
+    return builder.build();
+  }
+
   public <T> ImmutableMap<DexField, T> rewriteFieldKeys(Map<DexField, T> map) {
     ImmutableMap.Builder<DexField, T> builder = ImmutableMap.builder();
     map.forEach((field, value) -> builder.put(getRenamedFieldSignature(field), value));
@@ -620,7 +627,7 @@
         // that they can be mapped back to the original program.
         DexField originalField = getOriginalFieldSignature(field.getReference());
         assert originalFields.contains(originalField)
-                || isD8R8SynthesizedField(originalField, dexItemFactory)
+                || isD8R8SynthesizedField(originalField, appView)
             : "Unable to map field `"
                 + field.getReference().toSourceString()
                 + "` back to original program";
@@ -638,16 +645,16 @@
     return true;
   }
 
-  private boolean isD8R8SynthesizedField(DexField field, DexItemFactory dexItemFactory) {
+  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(dexItemFactory.objectMembers.clinitField)) {
+    if (field.match(appView.dexItemFactory().objectMembers.clinitField)) {
       return true;
     }
     if (field.getName().toSourceString().equals(CLASS_ID_FIELD_NAME)) {
       return true;
     }
-    if (field.getHolderType().toSourceString().contains(LAMBDA_CLASS_NAME_PREFIX)
+    if (appView.getSyntheticItems().isSyntheticClass(field.getHolderType())
         && field.getName().toSourceString().equals(LAMBDA_INSTANCE_FIELD_NAME)) {
       return true;
     }
diff --git a/src/main/java/com/android/tools/r8/graph/JarClassFileReader.java b/src/main/java/com/android/tools/r8/graph/JarClassFileReader.java
index 88596f9..40b6445 100644
--- a/src/main/java/com/android/tools/r8/graph/JarClassFileReader.java
+++ b/src/main/java/com/android/tools/r8/graph/JarClassFileReader.java
@@ -817,8 +817,7 @@
 
     private boolean classRequiresCode() {
       return parent.classKind == ClassKind.PROGRAM
-          || (parent.application.options.enableNestBasedAccessDesugaring
-              && !parent.application.options.canUseNestBasedAccess()
+          || (!parent.application.options.canUseNestBasedAccess()
               && parent.classKind == ClassKind.CLASSPATH
               && parent.isInANest());
     }
diff --git a/src/main/java/com/android/tools/r8/graph/LazyCfCode.java b/src/main/java/com/android/tools/r8/graph/LazyCfCode.java
index 6a70cdd..ba22b16 100644
--- a/src/main/java/com/android/tools/r8/graph/LazyCfCode.java
+++ b/src/main/java/com/android/tools/r8/graph/LazyCfCode.java
@@ -105,7 +105,7 @@
   private final Origin origin;
   private JarApplicationReader application;
   private CfCode code;
-  protected ReparseContext context;
+  private ReparseContext context;
   private boolean reachabilitySensitive = false;
 
   public void markReachabilitySensitive() {
diff --git a/src/main/java/com/android/tools/r8/graph/LazyLoadedDexApplication.java b/src/main/java/com/android/tools/r8/graph/LazyLoadedDexApplication.java
index ce5ff83..ba240f6 100644
--- a/src/main/java/com/android/tools/r8/graph/LazyLoadedDexApplication.java
+++ b/src/main/java/com/android/tools/r8/graph/LazyLoadedDexApplication.java
@@ -54,6 +54,12 @@
   }
 
   @Override
+  List<DexClasspathClass> classpathClasses() {
+    classpathClasses.forceLoad(t -> true);
+    return classpathClasses.getAllClasses();
+  }
+
+  @Override
   public DexClass definitionFor(DexType type) {
     assert type.isClassType() : "Cannot lookup definition for type: " + type;
     DexClass clazz = null;
@@ -199,7 +205,7 @@
     Builder(ProgramClassConflictResolver resolver, InternalOptions options, Timing timing) {
       super(options, timing);
       this.resolver = resolver;
-      this.classpathClasses = null;
+      this.classpathClasses = ClasspathClassCollection.empty();
       this.libraryClasses = null;
     }
 
@@ -229,7 +235,7 @@
     public LazyLoadedDexApplication build() {
       return new LazyLoadedDexApplication(
           proguardMap,
-          ProgramClassCollection.create(programClasses, resolver),
+          ProgramClassCollection.create(getProgramClasses(), resolver),
           ImmutableList.copyOf(dataResourceProviders),
           classpathClasses,
           libraryClasses,
diff --git a/src/main/java/com/android/tools/r8/graph/LibraryField.java b/src/main/java/com/android/tools/r8/graph/LibraryField.java
new file mode 100644
index 0000000..49ed565
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/graph/LibraryField.java
@@ -0,0 +1,35 @@
+// 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;
+
+public class LibraryField extends DexClassAndField
+    implements LibraryMember<DexEncodedField, DexField> {
+
+  public LibraryField(DexLibraryClass holder, DexEncodedField field) {
+    super(holder, field);
+  }
+
+  @Override
+  public DexLibraryClass getHolder() {
+    DexClass holder = super.getHolder();
+    assert holder.isLibraryClass();
+    return holder.asLibraryClass();
+  }
+
+  @Override
+  public boolean isLibraryField() {
+    return true;
+  }
+
+  @Override
+  public LibraryField asLibraryField() {
+    return this;
+  }
+
+  @Override
+  public boolean isLibraryMember() {
+    return true;
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/graph/LibraryMember.java b/src/main/java/com/android/tools/r8/graph/LibraryMember.java
new file mode 100644
index 0000000..880da78
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/graph/LibraryMember.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.graph;
+
+public interface LibraryMember<D extends DexEncodedMember<D, R>, R extends DexMember<D, R>> {
+
+  D getDefinition();
+
+  DexLibraryClass getHolder();
+
+  DexType getHolderType();
+}
diff --git a/src/main/java/com/android/tools/r8/graph/LibraryMethod.java b/src/main/java/com/android/tools/r8/graph/LibraryMethod.java
index c06ebab..29cdc41 100644
--- a/src/main/java/com/android/tools/r8/graph/LibraryMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/LibraryMethod.java
@@ -4,13 +4,26 @@
 package com.android.tools.r8.graph;
 
 /** Type representing a method definition from the library and its holder. */
-public final class LibraryMethod extends DexClassAndMethod {
+public final class LibraryMethod extends DexClassAndMethod
+    implements LibraryMember<DexEncodedMethod, DexMethod> {
 
   public LibraryMethod(DexLibraryClass holder, DexEncodedMethod method) {
     super(holder, method);
   }
 
   @Override
+  public DexLibraryClass getHolder() {
+    DexClass holder = super.getHolder();
+    assert holder.isLibraryClass();
+    return holder.asLibraryClass();
+  }
+
+  @Override
+  public boolean isLibraryMember() {
+    return true;
+  }
+
+  @Override
   public boolean isLibraryMethod() {
     return true;
   }
@@ -19,11 +32,4 @@
   public LibraryMethod asLibraryMethod() {
     return this;
   }
-
-  @Override
-  public DexLibraryClass getHolder() {
-    DexClass holder = super.getHolder();
-    assert holder.isLibraryClass();
-    return holder.asLibraryClass();
-  }
 }
diff --git a/src/main/java/com/android/tools/r8/graph/MethodCollection.java b/src/main/java/com/android/tools/r8/graph/MethodCollection.java
index 9339630..ccf33aa 100644
--- a/src/main/java/com/android/tools/r8/graph/MethodCollection.java
+++ b/src/main/java/com/android/tools/r8/graph/MethodCollection.java
@@ -130,7 +130,7 @@
     return backing.methods();
   }
 
-  public Iterable<DexEncodedMethod> methods(Predicate<DexEncodedMethod> predicate) {
+  public Iterable<DexEncodedMethod> methods(Predicate<? super DexEncodedMethod> predicate) {
     return IterableUtils.filter(methods(), predicate);
   }
 
diff --git a/src/main/java/com/android/tools/r8/graph/MethodMapBacking.java b/src/main/java/com/android/tools/r8/graph/MethodMapBacking.java
index c6f7a5f..f3385af 100644
--- a/src/main/java/com/android/tools/r8/graph/MethodMapBacking.java
+++ b/src/main/java/com/android/tools/r8/graph/MethodMapBacking.java
@@ -34,7 +34,7 @@
   }
 
   public static MethodMapBacking createSorted() {
-    Comparator<Wrapper<DexMethod>> comparator = (x, y) -> x.get().compareTo(y.get());
+    Comparator<Wrapper<DexMethod>> comparator = Comparator.comparing(Wrapper::get);
     return new MethodMapBacking(new Object2ReferenceRBTreeMap<>(comparator));
   }
 
diff --git a/src/main/java/com/android/tools/r8/graph/NestHostClassAttribute.java b/src/main/java/com/android/tools/r8/graph/NestHostClassAttribute.java
index e007f4e..c9969a6 100644
--- a/src/main/java/com/android/tools/r8/graph/NestHostClassAttribute.java
+++ b/src/main/java/com/android/tools/r8/graph/NestHostClassAttribute.java
@@ -5,12 +5,19 @@
 package com.android.tools.r8.graph;
 
 import com.android.tools.r8.naming.NamingLens;
+import com.android.tools.r8.utils.structural.StructuralItem;
+import com.android.tools.r8.utils.structural.StructuralMapping;
+import com.android.tools.r8.utils.structural.StructuralSpecification;
 import org.objectweb.asm.ClassWriter;
 
-public class NestHostClassAttribute {
+public class NestHostClassAttribute implements StructuralItem<NestHostClassAttribute> {
 
   private final DexType nestHost;
 
+  private static void specify(StructuralSpecification<NestHostClassAttribute, ?> spec) {
+    spec.withItem(a -> a.nestHost);
+  }
+
   public NestHostClassAttribute(DexType nestHost) {
     this.nestHost = nestHost;
   }
@@ -27,4 +34,14 @@
     assert nestHost != null;
     writer.visitNestHost(lens.lookupInternalName(nestHost));
   }
+
+  @Override
+  public NestHostClassAttribute self() {
+    return this;
+  }
+
+  @Override
+  public StructuralMapping<NestHostClassAttribute> getStructuralMapping() {
+    return NestHostClassAttribute::specify;
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/graph/NestMemberClassAttribute.java b/src/main/java/com/android/tools/r8/graph/NestMemberClassAttribute.java
index f9d1a35..0d7c19d 100644
--- a/src/main/java/com/android/tools/r8/graph/NestMemberClassAttribute.java
+++ b/src/main/java/com/android/tools/r8/graph/NestMemberClassAttribute.java
@@ -5,14 +5,21 @@
 package com.android.tools.r8.graph;
 
 import com.android.tools.r8.naming.NamingLens;
+import com.android.tools.r8.utils.structural.StructuralItem;
+import com.android.tools.r8.utils.structural.StructuralMapping;
+import com.android.tools.r8.utils.structural.StructuralSpecification;
 import java.util.Collections;
 import java.util.List;
 import org.objectweb.asm.ClassWriter;
 
-public class NestMemberClassAttribute {
+public class NestMemberClassAttribute implements StructuralItem<NestMemberClassAttribute> {
 
   private final DexType nestMember;
 
+  private static void specify(StructuralSpecification<NestMemberClassAttribute, ?> spec) {
+    spec.withItem(a -> a.nestMember);
+  }
+
   public NestMemberClassAttribute(DexType nestMember) {
     this.nestMember = nestMember;
   }
@@ -29,4 +36,14 @@
     assert nestMember != null;
     writer.visitNestMember(lens.lookupInternalName(nestMember));
   }
+
+  @Override
+  public NestMemberClassAttribute self() {
+    return this;
+  }
+
+  @Override
+  public StructuralMapping<NestMemberClassAttribute> getStructuralMapping() {
+    return NestMemberClassAttribute::specify;
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/graph/ResolutionResult.java b/src/main/java/com/android/tools/r8/graph/ResolutionResult.java
index edb5e46..3356777 100644
--- a/src/main/java/com/android/tools/r8/graph/ResolutionResult.java
+++ b/src/main/java/com/android/tools/r8/graph/ResolutionResult.java
@@ -567,18 +567,14 @@
     public LookupTarget lookupVirtualDispatchTarget(
         LambdaDescriptor lambdaInstance, AppInfoWithClassHierarchy appInfo) {
       if (lambdaInstance.getMainMethod().match(resolvedMethod)) {
-        DexMethod method = lambdaInstance.implHandle.asMethod();
-        DexClass holder = appInfo.definitionForHolder(method);
-        if (holder == null) {
-          assert false;
-          return null;
-        }
-        DexEncodedMethod definition = holder.lookupMethod(method);
-        if (definition == null) {
+        DexMethod methodReference = lambdaInstance.implHandle.asMethod();
+        DexClass holder = appInfo.definitionForHolder(methodReference);
+        DexClassAndMethod method = methodReference.lookupMemberOnClass(holder);
+        if (method == null) {
           // The targeted method might not exist, eg, Throwable.addSuppressed in an old library.
           return null;
         }
-        return new LookupLambdaTarget(lambdaInstance, DexClassAndMethod.create(holder, definition));
+        return new LookupLambdaTarget(lambdaInstance, method);
       }
       return lookupMaximallySpecificDispatchTarget(lambdaInstance, appInfo);
     }
diff --git a/src/main/java/com/android/tools/r8/graph/TreeFixerBase.java b/src/main/java/com/android/tools/r8/graph/TreeFixerBase.java
index a10db7147..89d0bd7 100644
--- a/src/main/java/com/android/tools/r8/graph/TreeFixerBase.java
+++ b/src/main/java/com/android/tools/r8/graph/TreeFixerBase.java
@@ -4,6 +4,7 @@
 
 package com.android.tools.r8.graph;
 
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.IdentityHashMap;
@@ -15,7 +16,7 @@
   private final AppView<?> appView;
   private final DexItemFactory dexItemFactory;
 
-  private Map<DexType, DexProgramClass> newProgramClasses = null;
+  private final Map<DexType, DexProgramClass> programClassCache = new IdentityHashMap<>();
   private final Map<DexType, DexProgramClass> synthesizedFromClasses = new IdentityHashMap<>();
   private final Map<DexProto, DexProto> protoFixupCache = new IdentityHashMap<>();
 
@@ -46,6 +47,34 @@
     return to;
   }
 
+  /** Rewrite missing references */
+  public void recordFailedResolutionChanges() {
+    // In order for optimizations to correctly rewrite field and method references that do not
+    // resolve, we create a mapping from each failed resolution target to its reference reference.
+    if (!appView.appInfo().hasLiveness()) {
+      return;
+    }
+    AppInfoWithLiveness appInfoWithLiveness = appView.appInfo().withLiveness();
+    appInfoWithLiveness
+        .getFailedFieldResolutionTargets()
+        .forEach(
+            field -> {
+              DexField fixedUpField = fixupFieldReference(field);
+              if (field != fixedUpField) {
+                recordFieldChange(field, fixedUpField);
+              }
+            });
+    appInfoWithLiveness
+        .getFailedMethodResolutionTargets()
+        .forEach(
+            method -> {
+              DexMethod fixedUpMethod = fixupMethodReference(method);
+              if (method != fixedUpMethod) {
+                recordMethodChange(method, fixedUpMethod);
+              }
+            });
+  }
+
   /** Callback to allow custom handling when an encoded method changes. */
   public DexEncodedMethod recordMethodChange(DexEncodedMethod from, DexEncodedMethod to) {
     recordMethodChange(from.method, to.method);
@@ -53,13 +82,13 @@
   }
 
   /** Fixup a collection of classes. */
-  public Collection<DexProgramClass> fixupClasses(Collection<DexProgramClass> classes) {
-    assert newProgramClasses == null;
-    newProgramClasses = new IdentityHashMap<>();
+  public List<DexProgramClass> fixupClasses(Collection<DexProgramClass> classes) {
+    List<DexProgramClass> newProgramClasses = new ArrayList<>();
     for (DexProgramClass clazz : classes) {
-      newProgramClasses.computeIfAbsent(clazz.getType(), ignore -> fixupClass(clazz));
+      newProgramClasses.add(
+          programClassCache.computeIfAbsent(clazz.getType(), ignore -> fixupClass(clazz)));
     }
-    return newProgramClasses.values();
+    return newProgramClasses;
   }
 
   // Should remain private as the correctness of the fixup requires the lazy 'newProgramClasses'.
@@ -70,7 +99,7 @@
             clazz.getOriginKind(),
             clazz.getOrigin(),
             clazz.getAccessFlags(),
-            fixupType(clazz.superType),
+            clazz.superType == null ? null : fixupType(clazz.superType),
             fixupTypeList(clazz.interfaces),
             clazz.getSourceFile(),
             fixupNestHost(clazz.getNestHostClassAttribute()),
@@ -250,7 +279,6 @@
   // Should remain private as its correctness relies on the setup of 'newProgramClasses'.
   private Collection<DexProgramClass> fixupSynthesizedFrom(
       Collection<DexProgramClass> synthesizedFrom) {
-    assert newProgramClasses != null;
     if (synthesizedFrom.isEmpty()) {
       return synthesizedFrom;
     }
@@ -261,7 +289,7 @@
       //  is no longer in the application?
       Map<DexType, DexProgramClass> classes =
           appView.appInfo().definitionForWithoutExistenceAssert(clazz.getType()) != null
-              ? newProgramClasses
+              ? programClassCache
               : synthesizedFromClasses;
       DexProgramClass newClass =
           classes.computeIfAbsent(clazz.getType(), ignore -> fixupClass(clazz));
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/ClassMerger.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/ClassMerger.java
index 0a6a9b9..11001de 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/ClassMerger.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/ClassMerger.java
@@ -172,7 +172,7 @@
     return dexItemFactory.createFreshMethodName(
         method.getDefinition().method.name.toSourceString(),
         method.getHolderType(),
-        method.getDefinition().proto(),
+        method.getDefinition().getProto(),
         group.getTarget().getType(),
         classMethodsBuilder::isFresh);
   }
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/ClassMethodsBuilder.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/ClassMethodsBuilder.java
index 13c01eb..b87c6ad 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/ClassMethodsBuilder.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/ClassMethodsBuilder.java
@@ -35,9 +35,9 @@
 
   public void setClassMethods(DexProgramClass clazz) {
     assert virtualMethods.stream().allMatch(method -> method.getHolderType() == clazz.type);
-    assert virtualMethods.stream().allMatch(method -> method.belongsToVirtualPool());
+    assert virtualMethods.stream().allMatch(DexEncodedMethod::belongsToVirtualPool);
     assert directMethods.stream().allMatch(method -> method.getHolderType() == clazz.type);
-    assert directMethods.stream().allMatch(method -> method.belongsToDirectPool());
+    assert directMethods.stream().allMatch(DexEncodedMethod::belongsToDirectPool);
     clazz.setVirtualMethods(virtualMethods);
     clazz.setDirectMethods(directMethods);
   }
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/ConstructorMerger.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/ConstructorMerger.java
index b4e9887..13673fa 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/ConstructorMerger.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/ConstructorMerger.java
@@ -45,7 +45,7 @@
 
     // Constructors should not be empty and all constructors should have the same prototype.
     assert !constructors.isEmpty();
-    assert constructors.stream().map(DexEncodedMethod::proto).distinct().count() == 1;
+    assert constructors.stream().map(DexEncodedMethod::getProto).distinct().count() == 1;
 
     this.dexItemFactory = appView.dexItemFactory();
   }
@@ -113,7 +113,7 @@
         dexItemFactory.createFreshMethodName(
             TEMPORARY_INSTANCE_INITIALIZER_PREFIX,
             constructor.getHolderType(),
-            constructor.proto(),
+            constructor.getProto(),
             group.getTarget().getType(),
             classMethodsBuilder::isFresh);
 
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/FieldAccessAnalysis.java b/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/FieldAccessAnalysis.java
index 015b182..2b5e70f 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/FieldAccessAnalysis.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/FieldAccessAnalysis.java
@@ -62,7 +62,7 @@
 
   public void recordFieldAccesses(
       IRCode code, OptimizationFeedback feedback, MethodProcessor methodProcessor) {
-    if (!methodProcessor.isPrimary()) {
+    if (!methodProcessor.isPrimaryMethodProcessor()) {
       return;
     }
 
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/StaticFieldValueAnalysis.java b/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/StaticFieldValueAnalysis.java
index 281215f..fc01602 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/StaticFieldValueAnalysis.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/StaticFieldValueAnalysis.java
@@ -12,9 +12,6 @@
 import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexClassAndMethod;
 import com.android.tools.r8.graph.DexEncodedField;
-import com.android.tools.r8.graph.DexField;
-import com.android.tools.r8.graph.DexItemFactory;
-import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.DexValue;
 import com.android.tools.r8.graph.DexValue.DexValueNull;
 import com.android.tools.r8.ir.analysis.type.ClassTypeElement;
@@ -26,6 +23,7 @@
 import com.android.tools.r8.ir.analysis.value.NullOrAbstractValue;
 import com.android.tools.r8.ir.analysis.value.ObjectState;
 import com.android.tools.r8.ir.analysis.value.SingleFieldValue;
+import com.android.tools.r8.ir.analysis.value.UnknownValue;
 import com.android.tools.r8.ir.code.ArrayPut;
 import com.android.tools.r8.ir.code.FieldInstruction;
 import com.android.tools.r8.ir.code.IRCode;
@@ -40,10 +38,13 @@
 import com.android.tools.r8.ir.optimize.info.field.InstanceFieldInitializationInfoCollection;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.utils.Timing;
+import java.util.IdentityHashMap;
+import java.util.Map;
 
 public class StaticFieldValueAnalysis extends FieldValueAnalysis {
 
   private final StaticFieldValues.Builder builder;
+  private final Map<Value, AbstractValue> computedValues = new IdentityHashMap<>();
 
   private StaticFieldValueAnalysis(
       AppView<AppInfoWithLiveness> appView, IRCode code, OptimizationFeedback feedback) {
@@ -63,7 +64,7 @@
     timing.begin("Analyze class initializer");
     StaticFieldValues result =
         new StaticFieldValueAnalysis(appView.withLiveness(), code, feedback)
-            .analyze(classInitializerDefaultsResult, code.context().getHolderType());
+            .analyze(classInitializerDefaultsResult);
     timing.end();
     return result;
   }
@@ -78,8 +79,7 @@
     return this;
   }
 
-  StaticFieldValues analyze(
-      ClassInitializerDefaultsResult classInitializerDefaultsResult, DexType holderType) {
+  StaticFieldValues analyze(ClassInitializerDefaultsResult classInitializerDefaultsResult) {
     computeFieldOptimizationInfo(classInitializerDefaultsResult);
     return builder.build();
   }
@@ -218,16 +218,41 @@
       return null;
     }
     assert !value.hasAliasedValue();
-    if (isEnumValuesArray(value)) {
+    if (value.isPhi()) {
+      return null;
+    }
+    if (value.definition.isNewArrayEmpty()) {
       return computeSingleEnumFieldValueForValuesArray(value);
     }
-    return computeSingleEnumFieldValueForInstance(value);
+    if (value.definition.isNewInstance()) {
+      return computeSingleEnumFieldValueForInstance(value);
+    }
+    return null;
   }
 
   private SingleFieldValue computeSingleEnumFieldValueForValuesArray(Value value) {
-    if (!value.isDefinedByInstructionSatisfying(Instruction::isNewArrayEmpty)) {
+    if (!value.definition.isNewArrayEmpty()) {
       return null;
     }
+    AbstractValue valuesValue = computedValues.get(value);
+    if (valuesValue != null) {
+      // This implicitely answers null if the value could not get computed.
+      if (valuesValue.isSingleFieldValue()) {
+        SingleFieldValue fieldValue = valuesValue.asSingleFieldValue();
+        if (fieldValue.getState().isEnumValuesObjectState()) {
+          return fieldValue;
+        }
+      }
+      return null;
+    }
+    SingleFieldValue singleFieldValue = internalComputeSingleEnumFieldValueForValuesArray(value);
+    computedValues.put(
+        value, singleFieldValue == null ? UnknownValue.getInstance() : singleFieldValue);
+    return singleFieldValue;
+  }
+
+  private SingleFieldValue internalComputeSingleEnumFieldValueForValuesArray(Value value) {
+    assert value.isDefinedByInstructionSatisfying(Instruction::isNewArrayEmpty);
 
     NewArrayEmpty newArrayEmpty = value.definition.asNewArrayEmpty();
     if (newArrayEmpty.type.toBaseType(appView.dexItemFactory()) != context.getHolder().type) {
@@ -267,7 +292,9 @@
             // We need the state of all fields for the analysis to be valuable.
             return null;
           }
-          assert verifyValuesArrayIndexMatchesOrdinal(index, objectState);
+          if (!valuesArrayIndexMatchesOrdinal(index, objectState)) {
+            return null;
+          }
           if (valuesState[index] != null) {
             return null;
           }
@@ -326,24 +353,25 @@
     return ObjectState.empty();
   }
 
-  private boolean verifyValuesArrayIndexMatchesOrdinal(int ordinal, ObjectState objectState) {
+  private boolean valuesArrayIndexMatchesOrdinal(int ordinal, ObjectState objectState) {
     DexEncodedField ordinalField =
         appView
             .appInfo()
             .resolveField(appView.dexItemFactory().enumMembers.ordinalField, context)
             .getResolvedField();
-    assert ordinalField != null;
+    if (ordinalField == null) {
+      return false;
+    }
     AbstractValue ordinalState = objectState.getAbstractFieldValue(ordinalField);
-    assert ordinalState != null;
-    assert ordinalState.isSingleNumberValue();
-    assert ordinalState.asSingleNumberValue().getIntValue() == ordinal;
-    return true;
+    if (ordinalState == null || !ordinalState.isSingleNumberValue()) {
+      return false;
+    }
+    int intValue = ordinalState.asSingleNumberValue().getIntValue();
+    return intValue == ordinal;
   }
 
   private SingleFieldValue computeSingleEnumFieldValueForInstance(Value value) {
-    if (!value.isDefinedByInstructionSatisfying(Instruction::isNewInstance)) {
-      return null;
-    }
+    assert value.isDefinedByInstructionSatisfying(Instruction::isNewInstance);
 
     NewInstance newInstance = value.definition.asNewInstance();
     // Some enums have direct subclasses, and the subclass is instantiated here.
@@ -459,30 +487,7 @@
   }
 
   private boolean isEnumValuesArray(Value value) {
-    assert context.getHolder().isEnum();
-    DexItemFactory dexItemFactory = appView.dexItemFactory();
-    DexField valuesField =
-        dexItemFactory.createField(
-            context.getHolderType(),
-            context.getHolderType().toArrayType(1, dexItemFactory),
-            dexItemFactory.enumValuesFieldName);
-
-    Value root = value.getAliasedValue();
-    if (root.isPhi()) {
-      return false;
-    }
-
-    Instruction definition = root.definition;
-    if (definition.isNewArrayEmpty()) {
-      for (Instruction user : root.aliasedUsers()) {
-        if (user.isStaticPut() && user.asStaticPut().getField() == valuesField) {
-          return true;
-        }
-      }
-    } else if (definition.isStaticGet()) {
-      return definition.asStaticGet().getField() == valuesField;
-    }
-
-    return false;
+    SingleFieldValue singleFieldValue = computeSingleEnumFieldValueForValuesArray(value);
+    return singleFieldValue != null && singleFieldValue.getState().isEnumValuesObjectState();
   }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/StaticFieldValues.java b/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/StaticFieldValues.java
index b2dcdcd..65e1ea3 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/StaticFieldValues.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/StaticFieldValues.java
@@ -37,17 +37,10 @@
   // All the abstract values stored here may match a pinned field, using them requires therefore
   // to check the field is not pinned or prove it is no longer pinned.
   public static class EnumStaticFieldValues extends StaticFieldValues {
-    private final ImmutableMap<DexField, AbstractValue> enumAbstractValues;
-    private final DexField valuesField;
-    private final AbstractValue valuesAbstractValue;
+    private final ImmutableMap<DexField, ObjectState> enumAbstractValues;
 
-    public EnumStaticFieldValues(
-        ImmutableMap<DexField, AbstractValue> enumAbstractValues,
-        DexField valuesField,
-        AbstractValue valuesAbstractValue) {
+    public EnumStaticFieldValues(ImmutableMap<DexField, ObjectState> enumAbstractValues) {
       this.enumAbstractValues = enumAbstractValues;
-      this.valuesField = valuesField;
-      this.valuesAbstractValue = valuesAbstractValue;
     }
 
     static StaticFieldValues.Builder builder() {
@@ -55,33 +48,38 @@
     }
 
     public static class Builder extends StaticFieldValues.Builder {
-      private final ImmutableMap.Builder<DexField, AbstractValue> enumAbstractValuesBuilder =
+      private final ImmutableMap.Builder<DexField, ObjectState> enumObjectStateBuilder =
           ImmutableMap.builder();
-      private DexField valuesFields;
-      private AbstractValue valuesAbstractValue;
+      private AbstractValue valuesCandidateAbstractValue;
 
       Builder() {}
 
       @Override
       public void recordStaticField(
           DexEncodedField staticField, AbstractValue value, DexItemFactory factory) {
-        // TODO(b/166532388): Stop relying on the values name.
-        if (staticField.getName() == factory.enumValuesFieldName) {
-          valuesFields = staticField.field;
-          valuesAbstractValue = value;
-        } else if (staticField.isEnum()) {
-          enumAbstractValuesBuilder.put(staticField.field, value);
+        if (factory.enumMembers.isValuesFieldCandidate(staticField, staticField.getHolderType())) {
+          if (value.isSingleFieldValue()
+              && value.asSingleFieldValue().getState().isEnumValuesObjectState()) {
+            assert valuesCandidateAbstractValue == null
+                || valuesCandidateAbstractValue.equals(value);
+            valuesCandidateAbstractValue = value;
+            enumObjectStateBuilder.put(staticField.field, value.asSingleFieldValue().getState());
+          }
+        } else if (factory.enumMembers.isEnumField(staticField, staticField.getHolderType())) {
+          if (value.isSingleFieldValue() && !value.asSingleFieldValue().getState().isEmpty()) {
+            enumObjectStateBuilder.put(staticField.field, value.asSingleFieldValue().getState());
+          }
         }
       }
 
       @Override
       public StaticFieldValues build() {
-        ImmutableMap<DexField, AbstractValue> enumAbstractValues =
-            enumAbstractValuesBuilder.build();
-        if (valuesAbstractValue == null && enumAbstractValues.isEmpty()) {
+        ImmutableMap<DexField, ObjectState> enumAbstractValues = enumObjectStateBuilder.build();
+        if (enumAbstractValues.isEmpty()) {
           return EmptyStaticValues.getInstance();
         }
-        return new EnumStaticFieldValues(enumAbstractValues, valuesFields, valuesAbstractValue);
+        assert enumAbstractValues.values().stream().noneMatch(ObjectState::isEmpty);
+        return new EnumStaticFieldValues(enumAbstractValues);
       }
     }
 
@@ -96,20 +94,7 @@
     }
 
     public ObjectState getObjectStateForPossiblyPinnedField(DexField field) {
-      AbstractValue fieldValue = enumAbstractValues.get(field);
-      if (fieldValue == null || fieldValue.isZero()) {
-        return null;
-      }
-      if (fieldValue.isSingleFieldValue()) {
-        return fieldValue.asSingleFieldValue().getState();
-      }
-      assert fieldValue.isUnknown();
-      return ObjectState.empty();
-    }
-
-    public AbstractValue getValuesAbstractValueForPossiblyPinnedField(DexField field) {
-      assert valuesField == field || valuesAbstractValue == null;
-      return valuesAbstractValue;
+      return enumAbstractValues.get(field);
     }
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/type/ClassTypeElement.java b/src/main/java/com/android/tools/r8/ir/analysis/type/ClassTypeElement.java
index eeeea70..144f465 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/type/ClassTypeElement.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/type/ClassTypeElement.java
@@ -8,19 +8,21 @@
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.ir.analysis.type.InterfaceCollection.Builder;
+import com.android.tools.r8.utils.BooleanBox;
+import com.android.tools.r8.utils.Box;
+import com.android.tools.r8.utils.OptionalBool;
+import com.android.tools.r8.utils.Pair;
 import com.android.tools.r8.utils.SetUtils;
-import com.google.common.collect.ImmutableSet;
 import java.util.ArrayDeque;
 import java.util.ArrayList;
 import java.util.Collection;
-import java.util.Collections;
-import java.util.HashSet;
+import java.util.Comparator;
 import java.util.IdentityHashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.Objects;
 import java.util.Queue;
-import java.util.Set;
 import java.util.function.Function;
 import java.util.stream.Collectors;
 
@@ -28,14 +30,14 @@
 
   // Least upper bound of interfaces that this class type is implementing.
   // Lazily computed on demand via DexItemFactory, where the canonicalized set will be maintained.
-  private Set<DexType> lazyInterfaces;
+  private InterfaceCollection lazyInterfaces;
   private AppView<? extends AppInfoWithClassHierarchy> appView;
   // On-demand link between other nullability-variants.
   private final NullabilityVariants<ClassTypeElement> variants;
   private final DexType type;
 
   public static ClassTypeElement create(
-      DexType classType, Nullability nullability, Set<DexType> interfaces) {
+      DexType classType, Nullability nullability, InterfaceCollection interfaces) {
     assert interfaces != null;
     return NullabilityVariants.create(
         nullability,
@@ -55,7 +57,7 @@
   private ClassTypeElement(
       DexType classType,
       Nullability nullability,
-      Set<DexType> interfaces,
+      InterfaceCollection interfaces,
       NullabilityVariants<ClassTypeElement> variants,
       AppView<? extends AppInfoWithClassHierarchy> appView) {
     super(nullability);
@@ -71,7 +73,7 @@
     return type;
   }
 
-  public Set<DexType> getInterfaces() {
+  public InterfaceCollection getInterfaces() {
     if (lazyInterfaces == null) {
       assert appView != null;
       lazyInterfaces =
@@ -105,8 +107,8 @@
   @Override
   public boolean isBasedOnMissingClass(AppView<? extends AppInfoWithClassHierarchy> appView) {
     return appView.appInfo().isMissingOrHasMissingSuperType(getClassType())
-        || getInterfaces().stream()
-            .anyMatch(type -> appView.appInfo().isMissingOrHasMissingSuperType(type));
+        || getInterfaces()
+            .anyMatch((iface, isKnown) -> appView.appInfo().isMissingOrHasMissingSuperType(iface));
   }
 
   @Override
@@ -131,13 +133,17 @@
     builder.append(" ");
     builder.append(type);
     builder.append(" {");
-    Set<DexType> interfaces = getInterfaces();
-    if (interfaces != null) {
-      List<DexType> sortedInterfaces = new ArrayList<>(interfaces);
-      sortedInterfaces.sort(DexType::compareTo);
-      builder.append(
-          sortedInterfaces.stream().map(DexType::toString).collect(Collectors.joining(", ")));
-    }
+    InterfaceCollection interfaces = getInterfaces();
+    List<Pair<DexType, Boolean>> sortedInterfaces = interfaces.getInterfaceList();
+    sortedInterfaces.sort(Comparator.comparing(Pair::getFirst));
+    builder.append(
+        sortedInterfaces.stream()
+            .map(
+                pair ->
+                    pair.getSecond()
+                        ? pair.getFirst().toString()
+                        : ("maybe(" + pair.getFirst() + ")"))
+            .collect(Collectors.joining(", ")));
     builder.append("}");
     return builder.toString();
   }
@@ -165,36 +171,42 @@
 
     // For most types there will not have been a change thus we iterate without allocating a new
     // set for holding modified interfaces.
-    boolean hasChangedInterfaces = false;
-    DexClass interfaceToClassChange = null;
-    for (DexType iface : getInterfaces()) {
-      DexType substitutedType = mapping.apply(iface);
-      if (iface != substitutedType) {
-        hasChangedInterfaces = true;
-        DexClass mappedClass = appView.definitionFor(substitutedType);
-        if (!mappedClass.isInterface()) {
-          if (interfaceToClassChange != null && mappedClass != interfaceToClassChange) {
-            throw new CompilationError(
-                "More than one interface has changed to a class: "
-                    + interfaceToClassChange
-                    + " and "
-                    + mappedClass);
-          }
-          interfaceToClassChange = mappedClass;
-        }
-      }
-    }
-    if (hasChangedInterfaces) {
-      if (interfaceToClassChange != null) {
-        assert !interfaceToClassChange.isInterface();
+    BooleanBox hasChangedInterfaces = new BooleanBox();
+    Box<DexClass> interfaceToClassChange = new Box<>();
+    getInterfaces()
+        .forEach(
+            (iface, isKnown) -> {
+              DexType substitutedType = mapping.apply(iface);
+              if (iface != substitutedType) {
+                hasChangedInterfaces.set();
+                DexClass mappedClass = appView.definitionFor(substitutedType);
+                if (!mappedClass.isInterface()) {
+                  if (interfaceToClassChange.isSet()
+                      && mappedClass != interfaceToClassChange.get()) {
+                    throw new CompilationError(
+                        "More than one interface has changed to a class: "
+                            + interfaceToClassChange.get()
+                            + " and "
+                            + mappedClass);
+                  }
+                  interfaceToClassChange.set(mappedClass);
+                }
+              }
+            });
+    if (hasChangedInterfaces.get()) {
+      if (interfaceToClassChange.isSet()) {
+        assert !interfaceToClassChange.get().isInterface();
         assert type == appView.dexItemFactory().objectType;
-        return create(interfaceToClassChange.type, nullability, appView);
+        return create(interfaceToClassChange.get().type, nullability, appView);
       } else {
-        Set<DexType> newInterfaces = new HashSet<>();
-        for (DexType iface : lazyInterfaces) {
-          newInterfaces.add(mapping.apply(iface));
-        }
-        return create(mappedType, nullability, newInterfaces);
+        Builder builder = InterfaceCollection.builder();
+        lazyInterfaces.forEach(
+            (iface, isKnown) -> {
+              DexType rewritten = mapping.apply(iface);
+              assert iface == rewritten || isKnown : "Rewritten implies program types thus known.";
+              builder.addInterface(rewritten, isKnown);
+            });
+        return create(mappedType, nullability, builder.build());
       }
     }
     return this;
@@ -210,15 +222,15 @@
               ? getClassType()
               : appView.dexItemFactory().objectType,
           nullability,
-          Collections.emptySet());
+          InterfaceCollection.empty());
     }
     DexType lubType =
         computeLeastUpperBoundOfClasses(
             appView.appInfo().withClassHierarchy(), getClassType(), other.getClassType());
-    Set<DexType> c1lubItfs = getInterfaces();
-    Set<DexType> c2lubItfs = other.getInterfaces();
-    Set<DexType> lubItfs = null;
-    if (c1lubItfs.size() == c2lubItfs.size() && c1lubItfs.containsAll(c2lubItfs)) {
+    InterfaceCollection c1lubItfs = getInterfaces();
+    InterfaceCollection c2lubItfs = other.getInterfaces();
+    InterfaceCollection lubItfs = null;
+    if (c1lubItfs.equals(c2lubItfs)) {
       lubItfs = c1lubItfs;
     }
     if (lubItfs == null) {
@@ -228,9 +240,85 @@
     return ClassTypeElement.create(lubType, nullability, lubItfs);
   }
 
-  private enum InterfaceMarker {
-    LEFT,
-    RIGHT
+  /**
+   * Internal marker for finding the LUB between sets of interfaces.
+   *
+   * <p>The marker is used both as the identification of which side the traversal is on and if that
+   * item is known to always be present. That use denotes a immutable use fo the marker and reuses
+   * the static constants defined below. When traversing the interface super chains each point is
+   * mapped to a mutable marking that keeps track of what paths have reached it. The mutable use is
+   * allocated with 'createEmpty' and updated with 'merge'.
+   */
+  private static class InterfaceMarker {
+
+    // Each side is tracked with a three-valued marking.
+    // Note that the value FALSE is not part of the possible three values, only:
+    //   FALSE: not marked / not present.
+    //   TRUE: marked and known to be present.
+    //   UNKNOWN: marked and unknown if actually present.
+    private OptionalBool left;
+    private OptionalBool right;
+
+    static final InterfaceMarker LEFT_KNOWN =
+        new InterfaceMarker(OptionalBool.TRUE, OptionalBool.FALSE);
+    static final InterfaceMarker LEFT_UNKNOWN =
+        new InterfaceMarker(OptionalBool.UNKNOWN, OptionalBool.FALSE);
+    static final InterfaceMarker RIGHT_KNOWN =
+        new InterfaceMarker(OptionalBool.FALSE, OptionalBool.TRUE);
+    static final InterfaceMarker RIGHT_UNKNOWN =
+        new InterfaceMarker(OptionalBool.FALSE, OptionalBool.UNKNOWN);
+
+    static InterfaceMarker forLeft(boolean isKnown) {
+      return isKnown ? LEFT_KNOWN : LEFT_UNKNOWN;
+    }
+
+    static InterfaceMarker forRight(boolean isKnown) {
+      return isKnown ? RIGHT_KNOWN : RIGHT_UNKNOWN;
+    }
+
+    static InterfaceMarker createUnmarked() {
+      return new InterfaceMarker(OptionalBool.FALSE, OptionalBool.FALSE);
+    }
+
+    public InterfaceMarker(OptionalBool left, OptionalBool right) {
+      this.left = left;
+      this.right = right;
+      assert !isMarkedOnBothSides();
+    }
+
+    boolean isMarked() {
+      return left.isPossiblyTrue() || right.isPossiblyTrue();
+    }
+
+    boolean isMarkedOnBothSides() {
+      return left.isPossiblyTrue() && right.isPossiblyTrue();
+    }
+
+    static OptionalBool knownIfAnyIsKnown(OptionalBool v1, OptionalBool v2) {
+      assert v1.isPossiblyTrue() || v2.isPossiblyTrue();
+      return v1.isTrue() || v2.isTrue() ? OptionalBool.TRUE : OptionalBool.UNKNOWN;
+    }
+
+    boolean knownIfBothAreKnown() {
+      assert isMarkedOnBothSides();
+      return left.isTrue() && right.isTrue();
+    }
+
+    boolean merge(InterfaceMarker marker) {
+      assert marker.isMarked();
+      assert !marker.isMarkedOnBothSides();
+      if (marker.left.isPossiblyTrue()) {
+        OptionalBool oldLeft = left;
+        left = knownIfAnyIsKnown(left, marker.left);
+        // Only continue if the other side is absent and this side changed.
+        return right.isFalse() && left != oldLeft;
+      } else {
+        OptionalBool oldRight = right;
+        right = knownIfAnyIsKnown(right, marker.right);
+        // Only continue if the other side is absent and this side changed.
+        return left.isFalse() && right != oldRight;
+      }
+    }
   }
 
   private static class InterfaceWithMarker {
@@ -287,12 +375,15 @@
     return objectType;
   }
 
-  public static Set<DexType> computeLeastUpperBoundOfInterfaces(
-      AppView<? extends AppInfoWithClassHierarchy> appView, Set<DexType> s1, Set<DexType> s2) {
+  public static InterfaceCollection computeLeastUpperBoundOfInterfaces(
+      AppView<? extends AppInfoWithClassHierarchy> appView,
+      InterfaceCollection s1,
+      InterfaceCollection s2) {
     if (s1.isEmpty() || s2.isEmpty()) {
-      return Collections.emptySet();
+      return InterfaceCollection.empty();
     }
-    Set<DexType> cached = appView.dexItemFactory().leastUpperBoundOfInterfacesTable.get(s1, s2);
+    InterfaceCollection cached =
+        appView.dexItemFactory().leastUpperBoundOfInterfacesTable.get(s1, s2);
     if (cached != null) {
       return cached;
     }
@@ -300,58 +391,46 @@
     if (cached != null) {
       return cached;
     }
-    Map<DexType, Set<InterfaceMarker>> seen = new IdentityHashMap<>();
+    Map<DexType, InterfaceMarker> seen = new IdentityHashMap<>();
     Queue<InterfaceWithMarker> worklist = new ArrayDeque<>();
-    for (DexType itf1 : s1) {
-      worklist.add(new InterfaceWithMarker(itf1, InterfaceMarker.LEFT));
-    }
-    for (DexType itf2 : s2) {
-      worklist.add(new InterfaceWithMarker(itf2, InterfaceMarker.RIGHT));
-    }
+    s1.forEach(
+        (itf1, isKnown) ->
+            worklist.add(new InterfaceWithMarker(itf1, InterfaceMarker.forLeft(isKnown))));
+    s2.forEach(
+        (itf2, isKnown) ->
+            worklist.add(new InterfaceWithMarker(itf2, InterfaceMarker.forRight(isKnown))));
+
     while (!worklist.isEmpty()) {
       InterfaceWithMarker item = worklist.poll();
       DexType itf = item.itf;
       InterfaceMarker marker = item.marker;
-      Set<InterfaceMarker> markers = seen.computeIfAbsent(itf, k -> new HashSet<>());
-      // If this interface is a lower one in this set, skip.
-      if (markers.contains(marker)) {
-        continue;
-      }
-      // If this interface is already visited by the other set, add marker for this set and skip.
-      if (markers.size() == 1) {
-        markers.add(marker);
-        continue;
-      }
-      // Otherwise, this type is freshly visited.
-      markers.add(marker);
-      // Put super interfaces into the worklist.
-      DexClass itfClass = appView.definitionFor(itf);
-      if (itfClass != null) {
-        for (DexType superItf : itfClass.interfaces.values) {
-          markers = seen.computeIfAbsent(superItf, k -> new HashSet<>());
-          if (!markers.contains(marker)) {
+      InterfaceMarker marking = seen.computeIfAbsent(itf, k -> InterfaceMarker.createUnmarked());
+      if (marking.merge(marker)) {
+        // Put super interfaces into the worklist.
+        DexClass itfClass = appView.definitionFor(itf);
+        if (itfClass != null) {
+          for (DexType superItf : itfClass.interfaces.values) {
             worklist.add(new InterfaceWithMarker(superItf, marker));
           }
         }
       }
     }
 
-    ImmutableSet.Builder<DexType> commonBuilder = ImmutableSet.builder();
-    for (Map.Entry<DexType, Set<InterfaceMarker>> entry : seen.entrySet()) {
-      // Keep commonly visited interfaces only
-      if (entry.getValue().size() < 2) {
-        continue;
-      }
-      commonBuilder.add(entry.getKey());
-    }
-    Set<DexType> commonlyVisited = commonBuilder.build();
+    List<Pair<DexType, Boolean>> commonlyVisited = new ArrayList<>(seen.size());
+    seen.forEach(
+        (itf, marking) -> {
+          // Keep commonly visited interfaces only
+          if (marking.isMarkedOnBothSides()) {
+            commonlyVisited.add(new Pair<>(itf, marking.knownIfBothAreKnown()));
+          }
+        });
 
-    ImmutableSet.Builder<DexType> lubBuilder = ImmutableSet.builder();
-    for (DexType itf : commonlyVisited) {
+    Builder lubBuilder = InterfaceCollection.builder();
+    for (Pair<DexType, Boolean> entry : commonlyVisited) {
       // If there is a strict sub interface of this interface, it is not the least element.
       boolean notTheLeast = false;
-      for (DexType other : commonlyVisited) {
-        if (appView.appInfo().isStrictSubtypeOf(other, itf)) {
+      for (Pair<DexType, Boolean> other : commonlyVisited) {
+        if (appView.appInfo().isStrictSubtypeOf(other.getFirst(), entry.getFirst())) {
           notTheLeast = true;
           break;
         }
@@ -359,11 +438,11 @@
       if (notTheLeast) {
         continue;
       }
-      lubBuilder.add(itf);
+      lubBuilder.addInterface(entry.getFirst(), entry.getSecond());
     }
-    Set<DexType> lub = lubBuilder.build();
+    InterfaceCollection lub = lubBuilder.build();
     // Cache the computation result only if the given two sets of interfaces are different.
-    if (s1.size() != s2.size() || !s1.containsAll(s2)) {
+    if (!s1.equals(s2)) {
       synchronized (appView.dexItemFactory().leastUpperBoundOfInterfacesTable) {
         appView.dexItemFactory().leastUpperBoundOfInterfacesTable.put(s1, s2, lub);
       }
@@ -386,14 +465,6 @@
     if (!type.equals(other.type)) {
       return false;
     }
-    Set<DexType> thisInterfaces = getInterfaces();
-    Set<DexType> otherInterfaces = other.getInterfaces();
-    if (thisInterfaces == otherInterfaces) {
-      return true;
-    }
-    if (thisInterfaces.size() != otherInterfaces.size()) {
-      return false;
-    }
-    return thisInterfaces.containsAll(otherInterfaces);
+    return getInterfaces().equals(other.getInterfaces());
   }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/type/InterfaceCollection.java b/src/main/java/com/android/tools/r8/ir/analysis/type/InterfaceCollection.java
new file mode 100644
index 0000000..7b80c82
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/analysis/type/InterfaceCollection.java
@@ -0,0 +1,163 @@
+// 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.analysis.type;
+
+import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.OptionalBool;
+import com.android.tools.r8.utils.Pair;
+import it.unimi.dsi.fastutil.objects.Reference2BooleanMap;
+import it.unimi.dsi.fastutil.objects.Reference2BooleanMap.Entry;
+import it.unimi.dsi.fastutil.objects.Reference2BooleanMaps;
+import it.unimi.dsi.fastutil.objects.Reference2BooleanOpenHashMap;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.function.BiConsumer;
+import java.util.function.BiPredicate;
+
+public class InterfaceCollection {
+
+  public static boolean isKnownToImplement(
+      DexType iface, DexType implementor, InternalOptions options) {
+    if (options.canHaveZipFileWithMissingCloseableBug()
+        && implementor == options.dexItemFactory().zipFileType
+        && iface == options.dexItemFactory().closeableType) {
+      return false;
+    }
+    return true;
+  }
+
+  public static class Builder {
+    private Reference2BooleanMap<DexType> interfaces = new Reference2BooleanOpenHashMap<>();
+
+    private Builder() {}
+
+    public Builder addInterface(DexType iface, DexClass implementor, InternalOptions options) {
+      return addInterface(
+          iface,
+          !implementor.isLibraryClass()
+              || isKnownToImplement(iface, implementor.getType(), options));
+    }
+
+    public Builder addInterface(DexType iface, DexType implementor, InternalOptions options) {
+      return addInterface(iface, isKnownToImplement(iface, implementor, options));
+    }
+
+    public Builder addInterface(DexType type, boolean isKnown) {
+      interfaces.compute(
+          type,
+          (existingType, existingIsKnown) ->
+              // If the entry is new 'existingIsKnown == null', so we join with (null or true).
+              (existingIsKnown == null || existingIsKnown) && isKnown);
+      return this;
+    }
+
+    public InterfaceCollection build() {
+      if (interfaces.isEmpty()) {
+        return InterfaceCollection.empty();
+      }
+      return new InterfaceCollection(interfaces);
+    }
+  }
+
+  private static final InterfaceCollection EMPTY =
+      new InterfaceCollection(Reference2BooleanMaps.emptyMap());
+
+  public static InterfaceCollection empty() {
+    return EMPTY;
+  }
+
+  public static InterfaceCollection singleton(DexType type) {
+    return new InterfaceCollection(Reference2BooleanMaps.singleton(type, true));
+  }
+
+  public static Builder builder() {
+    return new Builder();
+  }
+
+  /**
+   * Set of interfaces mapping to an optional presence.
+   *
+   * <ul>
+   *   <li>An unmapped type is known to not be present.
+   *   <li>A type mapped to true is known to always be present.
+   *   <li>A type mapped to false is not always known to be present.
+   */
+  private final Reference2BooleanMap<DexType> interfaces;
+
+  private InterfaceCollection(Reference2BooleanMap<DexType> interfaces) {
+    assert interfaces != null;
+    this.interfaces = interfaces;
+  }
+
+  public boolean isEmpty() {
+    return interfaces.isEmpty();
+  }
+
+  public int size() {
+    return interfaces.size();
+  }
+
+  @Override
+  public boolean equals(Object o) {
+    if (this == o) {
+      return true;
+    }
+    if (!(o instanceof InterfaceCollection)) {
+      return false;
+    }
+    InterfaceCollection that = (InterfaceCollection) o;
+    return interfaces.equals(that.interfaces);
+  }
+
+  @Override
+  public int hashCode() {
+    return interfaces.hashCode();
+  }
+
+  public void forEach(BiConsumer<DexType, Boolean> fn) {
+    interfaces.forEach(fn::accept);
+  }
+
+  public boolean anyMatch(BiPredicate<DexType, Boolean> fn) {
+    for (Entry<DexType> entry : interfaces.reference2BooleanEntrySet()) {
+      if (fn.test(entry.getKey(), entry.getBooleanValue())) {
+        return true;
+      }
+    }
+    return false;
+  }
+
+  public List<Pair<DexType, Boolean>> getInterfaceList() {
+    List<Pair<DexType, Boolean>> list = new ArrayList<>(interfaces.size());
+    interfaces.forEach((iface, isKnown) -> list.add(new Pair<>(iface, isKnown)));
+    return list;
+  }
+
+  public boolean hasSingleKnownInterface() {
+    DexType singleKnownInterface = getSingleKnownInterface();
+    return singleKnownInterface != null;
+  }
+
+  public DexType getSingleKnownInterface() {
+    if (interfaces.size() != 1) {
+      return null;
+    }
+    DexType type = interfaces.keySet().iterator().next();
+    return interfaces.getBoolean(type) ? type : null;
+  }
+
+  public OptionalBool contains(DexType type) {
+    Boolean value = interfaces.get(type);
+    if (value == null) {
+      return OptionalBool.FALSE;
+    }
+    return value ? OptionalBool.TRUE : OptionalBool.unknown();
+  }
+
+  public boolean containsKnownInterface(DexType type) {
+    return contains(type).isTrue();
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/type/TypeAnalysis.java b/src/main/java/com/android/tools/r8/ir/analysis/type/TypeAnalysis.java
index 94b9489..d06fd44 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/type/TypeAnalysis.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/type/TypeAnalysis.java
@@ -18,7 +18,6 @@
 import java.util.Comparator;
 import java.util.Deque;
 import java.util.List;
-import java.util.Set;
 
 public class TypeAnalysis {
 
@@ -181,9 +180,9 @@
       ClassTypeElement classType = receiverUpperBoundType.asClassType();
       DexType refinedType = classType.getClassType();
       if (refinedType == appView.dexItemFactory().objectType) {
-        Set<DexType> interfaces = classType.getInterfaces();
-        if (interfaces.size() == 1) {
-          refinedType = interfaces.iterator().next();
+        DexType singleKnownInterface = classType.getInterfaces().getSingleKnownInterface();
+        if (singleKnownInterface != null) {
+          refinedType = singleKnownInterface;
         }
       }
       if (appView.appInfo().isSubtype(refinedType, staticReceiverType)) {
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/type/TypeElement.java b/src/main/java/com/android/tools/r8/ir/analysis/type/TypeElement.java
index a2c8d57..9736b13 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/type/TypeElement.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/type/TypeElement.java
@@ -147,11 +147,7 @@
    * @return {@code true} if {@param this} is strictly less than {@param other}.
    */
   public boolean strictlyLessThan(TypeElement other, AppView<?> appView) {
-    if (equals(other)) {
-      return false;
-    }
-    TypeElement lub = join(other, appView);
-    return !equals(lub) && other.equals(lub);
+    return !equals(other) && internalLessThan(other, appView);
   }
 
   /**
@@ -163,7 +159,13 @@
    * @return {@code true} if {@param this} is less than or equal to {@param other}.
    */
   public boolean lessThanOrEqual(TypeElement other, AppView<?> appView) {
-    return equals(other) || strictlyLessThan(other, appView);
+    return equals(other) || internalLessThan(other, appView);
+  }
+
+  private boolean internalLessThan(TypeElement other, AppView<?> appView) {
+    // The equals check has already been done by callers, so only the join is computed.
+    TypeElement lub = join(other, appView);
+    return !equals(lub) && other.equals(lub);
   }
 
   /**
diff --git a/src/main/java/com/android/tools/r8/ir/code/BasicBlockInstructionListIterator.java b/src/main/java/com/android/tools/r8/ir/code/BasicBlockInstructionListIterator.java
index a1c4e7b..d0e0027 100644
--- a/src/main/java/com/android/tools/r8/ir/code/BasicBlockInstructionListIterator.java
+++ b/src/main/java/com/android/tools/r8/ir/code/BasicBlockInstructionListIterator.java
@@ -227,9 +227,9 @@
     for (Value value : current.inValues()) {
       value.removeUser(current);
     }
-    if (current.outValue() != null && current.outValue().isUsed()) {
+    if (current.hasUsedOutValue()) {
       assert newInstruction.outValue() != null;
-      if (affectedValues != null) {
+      if (affectedValues != null && newInstruction.getOutType() != current.getOutType()) {
         current.outValue().addAffectedValuesTo(affectedValues);
       }
       current.outValue().replaceUsers(newInstruction.outValue());
@@ -243,6 +243,7 @@
     listIterator.add(newInstruction);
     current.clearBlock();
     metadata.record(newInstruction);
+    current = newInstruction;
   }
 
   @Override
@@ -402,6 +403,69 @@
   }
 
   @Override
+  public void replaceCurrentInstructionWithThrow(
+      AppView<?> appView,
+      IRCode code,
+      ListIterator<BasicBlock> blockIterator,
+      Value exceptionValue,
+      Set<BasicBlock> blocksToRemove,
+      Set<Value> affectedValues) {
+    if (current == null) {
+      throw new IllegalStateException();
+    }
+
+    Instruction toBeReplaced = current;
+    InternalOptions options = appView.options();
+
+    BasicBlock block = toBeReplaced.getBlock();
+    assert !blocksToRemove.contains(block);
+    assert affectedValues != null;
+
+    // Split the block before the instruction that should be replaced by `throw exceptionValue`.
+    previous();
+
+    BasicBlock throwBlock;
+    if (block.hasCatchHandlers() && !toBeReplaced.instructionTypeCanThrow()) {
+      // We need to insert the throw instruction in a block of its own, so split the current block
+      // into three blocks, where the intermediate block only contains a goto instruction.
+      throwBlock = splitCopyCatchHandlers(code, blockIterator, options);
+      throwBlock.listIterator(code).split(code, blockIterator, true);
+    } else {
+      splitCopyCatchHandlers(code, blockIterator, options);
+      throwBlock = block;
+    }
+
+    // Unlink all blocks that are dominated by the unique normal successor of the throw block.
+    blocksToRemove.addAll(
+        throwBlock.unlink(
+            throwBlock.getUniqueNormalSuccessor(),
+            new DominatorTree(code, MAY_HAVE_UNREACHABLE_BLOCKS),
+            affectedValues));
+
+    InstructionListIterator throwBlockInstructionIterator;
+    if (throwBlock == block) {
+      throwBlockInstructionIterator = this;
+      previous();
+      next();
+    } else {
+      throwBlockInstructionIterator = throwBlock.listIterator(code, 1);
+    }
+    assert !throwBlockInstructionIterator.hasNext();
+
+    // Replace the instruction by throw.
+    Throw throwInstruction = new Throw(exceptionValue);
+    if (hasInsertionPosition()) {
+      throwInstruction.setPosition(position);
+    } else if (toBeReplaced.getPosition().isSome()) {
+      throwInstruction.setPosition(toBeReplaced.getPosition());
+    } else {
+      assert !toBeReplaced.instructionTypeCanThrow();
+      throwInstruction.setPosition(Position.syntheticNone());
+    }
+    throwBlockInstructionIterator.replaceCurrentInstruction(throwInstruction);
+  }
+
+  @Override
   public void replaceCurrentInstructionWithThrowNull(
       AppView<? extends AppInfoWithClassHierarchy> appView,
       IRCode code,
@@ -563,8 +627,9 @@
       IRCode code, ListIterator<BasicBlock> blockIterator, InternalOptions options) {
     BasicBlock splitBlock = split(code, blockIterator, false);
     assert !block.hasCatchHandlers();
-    assert splitBlock.hasCatchHandlers();
-    block.copyCatchHandlers(code, blockIterator, splitBlock, options);
+    if (splitBlock.hasCatchHandlers()) {
+      block.copyCatchHandlers(code, blockIterator, splitBlock, options);
+    }
     return splitBlock;
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/code/FieldInstruction.java b/src/main/java/com/android/tools/r8/ir/code/FieldInstruction.java
index 7ec3198..fa42fcd 100644
--- a/src/main/java/com/android/tools/r8/ir/code/FieldInstruction.java
+++ b/src/main/java/com/android/tools/r8/ir/code/FieldInstruction.java
@@ -64,8 +64,18 @@
 
   public boolean instructionInstanceCanThrow(
       AppView<?> appView, ProgramMethod context, SideEffectAssumption assumption) {
-    SuccessfulFieldResolutionResult resolutionResult =
-        appView.appInfo().resolveField(field, context).asSuccessfulResolution();
+    return internalInstructionInstanceCanThrow(
+        appView,
+        context,
+        assumption,
+        appView.appInfo().resolveField(field, context).asSuccessfulResolution());
+  }
+
+  boolean internalInstructionInstanceCanThrow(
+      AppView<?> appView,
+      ProgramMethod context,
+      SideEffectAssumption assumption,
+      SuccessfulFieldResolutionResult resolutionResult) {
     if (resolutionResult == null) {
       return true;
     }
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 01cf2ba..efc52d1 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
@@ -596,9 +596,13 @@
       if (instruction.outValue != null && instruction.outValue.getType().isClassType()) {
         ClassTypeElement classTypeLattice = instruction.outValue.getType().asClassType();
         assert !verticallyMergedClasses.hasBeenMergedIntoSubtype(classTypeLattice.getClassType());
-        for (DexType itf : classTypeLattice.getInterfaces()) {
-          assert !verticallyMergedClasses.hasBeenMergedIntoSubtype(itf);
-        }
+        assert !classTypeLattice
+            .getInterfaces()
+            .anyMatch(
+                (itf, isKnown) -> {
+                  assert !verticallyMergedClasses.hasBeenMergedIntoSubtype(itf);
+                  return false;
+                });
       }
     }
     return true;
diff --git a/src/main/java/com/android/tools/r8/ir/code/IRCodeInstructionListIterator.java b/src/main/java/com/android/tools/r8/ir/code/IRCodeInstructionListIterator.java
index 757a581..4e223924 100644
--- a/src/main/java/com/android/tools/r8/ir/code/IRCodeInstructionListIterator.java
+++ b/src/main/java/com/android/tools/r8/ir/code/IRCodeInstructionListIterator.java
@@ -85,6 +85,17 @@
   }
 
   @Override
+  public void replaceCurrentInstructionWithThrow(
+      AppView<?> appView,
+      IRCode code,
+      ListIterator<BasicBlock> blockIterator,
+      Value exceptionValue,
+      Set<BasicBlock> blocksToRemove,
+      Set<Value> affectedValues) {
+    throw new Unimplemented();
+  }
+
+  @Override
   public void replaceCurrentInstructionWithThrowNull(
       AppView<? extends AppInfoWithClassHierarchy> appView,
       IRCode code,
diff --git a/src/main/java/com/android/tools/r8/ir/code/InstancePut.java b/src/main/java/com/android/tools/r8/ir/code/InstancePut.java
index c38c5c0..20377f3 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InstancePut.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InstancePut.java
@@ -19,6 +19,7 @@
 import com.android.tools.r8.graph.DexEncodedField;
 import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.FieldResolutionResult.SuccessfulFieldResolutionResult;
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.analysis.ClassInitializationAnalysis;
 import com.android.tools.r8.ir.analysis.ClassInitializationAnalysis.AnalysisAssumption;
@@ -121,12 +122,13 @@
       AppView<AppInfoWithLiveness> appViewWithLiveness = appView.withLiveness();
       AppInfoWithLiveness appInfoWithLiveness = appViewWithLiveness.appInfo();
 
-      if (instructionInstanceCanThrow(appView, context, assumption)) {
+      SuccessfulFieldResolutionResult resolutionResult =
+          appInfoWithLiveness.resolveField(getField()).asSuccessfulResolution();
+      if (internalInstructionInstanceCanThrow(appView, context, assumption, resolutionResult)) {
         return true;
       }
 
-      DexEncodedField encodedField =
-          appInfoWithLiveness.resolveField(getField()).getResolvedField();
+      DexEncodedField encodedField = resolutionResult.getResolvedField();
       assert encodedField != null : "NoSuchFieldError (resolution failure) should be caught.";
 
       if (encodedField.type().isAlwaysNull(appViewWithLiveness)) {
diff --git a/src/main/java/com/android/tools/r8/ir/code/InstructionListIterator.java b/src/main/java/com/android/tools/r8/ir/code/InstructionListIterator.java
index 6b17a04..32b1f29 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InstructionListIterator.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InstructionListIterator.java
@@ -120,6 +120,14 @@
   void replaceCurrentInstructionWithStaticGet(
       AppView<?> appView, IRCode code, DexField field, Set<Value> affectedValues);
 
+  void replaceCurrentInstructionWithThrow(
+      AppView<?> appView,
+      IRCode code,
+      ListIterator<BasicBlock> blockIterator,
+      Value exceptionValue,
+      Set<BasicBlock> blocksToRemove,
+      Set<Value> affectedValues);
+
   /**
    * Replace the current instruction with null throwing instructions.
    *
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokeCustom.java b/src/main/java/com/android/tools/r8/ir/code/InvokeCustom.java
index 29b8a3b..1ef9cf5 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InvokeCustom.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InvokeCustom.java
@@ -12,6 +12,8 @@
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.analysis.type.ClassTypeElement;
+import com.android.tools.r8.ir.analysis.type.InterfaceCollection;
+import com.android.tools.r8.ir.analysis.type.InterfaceCollection.Builder;
 import com.android.tools.r8.ir.analysis.type.Nullability;
 import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.conversion.CfBuilder;
@@ -19,9 +21,7 @@
 import com.android.tools.r8.ir.desugar.LambdaDescriptor;
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
 import com.android.tools.r8.ir.optimize.InliningConstraints;
-import com.google.common.collect.ImmutableSet;
 import java.util.List;
-import java.util.Set;
 
 public final class InvokeCustom extends Invoke {
 
@@ -44,19 +44,18 @@
   }
 
   private static boolean verifyLambdaInterfaces(
-      TypeElement returnType, Set<DexType> lambdaInterfaceSet, DexType objectType) {
-    Set<DexType> primaryInterfaces = returnType.asClassType().getInterfaces();
+      TypeElement returnType, InterfaceCollection lambdaInterfaceSet, DexType objectType) {
+    InterfaceCollection primaryInterfaces = returnType.asClassType().getInterfaces();
     if (returnType.asClassType().getClassType() == objectType) {
-      assert primaryInterfaces.size() == 1;
       // The interfaces returned by the LambdaDescriptor assumed to already contain the primary
       // interface. If they're both singleton lists they must be identical and we can return the
       // primary return type.
-      assert lambdaInterfaceSet.contains(primaryInterfaces.iterator().next());
+      assert lambdaInterfaceSet.containsKnownInterface(primaryInterfaces.getSingleKnownInterface());
     } else {
       // We arrive here if the primary interface is a missing class. In that case the
       // returnType will be the missing type as the class type.
       assert primaryInterfaces.isEmpty();
-      assert lambdaInterfaceSet.contains(returnType.asClassType().getClassType());
+      assert lambdaInterfaceSet.containsKnownInterface(returnType.asClassType().getClassType());
     }
     return true;
   }
@@ -76,20 +75,21 @@
     // The primary return type is either an interface or a missing type.
     assert returnType instanceof ClassTypeElement;
 
-    Set<DexType> primaryInterfaces = returnType.asClassType().getInterfaces();
+    InterfaceCollection primaryInterfaces = returnType.asClassType().getInterfaces();
     DexType objectType = appView.dexItemFactory().objectType;
 
     if (returnType.asClassType().getClassType() == objectType) {
-      assert primaryInterfaces.size() == 1;
+      assert primaryInterfaces.hasSingleKnownInterface();
       // Shortcut for the common case: single interface. Save creating a new lattice type.
       if (lambdaInterfaces.size() == 1) {
-        assert lambdaInterfaces.get(0) == primaryInterfaces.iterator().next();
+        assert lambdaInterfaces.get(0) == primaryInterfaces.getSingleKnownInterface();
         return returnType;
       }
     }
 
-    Set<DexType> lambdaInterfaceSet =
-        ImmutableSet.<DexType>builder().addAll(lambdaInterfaces).build();
+    Builder builder = InterfaceCollection.builder();
+    lambdaInterfaces.forEach(iface -> builder.addInterface(iface, true));
+    InterfaceCollection lambdaInterfaceSet = builder.build();
 
     assert verifyLambdaInterfaces(returnType, lambdaInterfaceSet, objectType);
 
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokeMethod.java b/src/main/java/com/android/tools/r8/ir/code/InvokeMethod.java
index 9d7d69b..0d9d21a 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InvokeMethod.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InvokeMethod.java
@@ -4,6 +4,7 @@
 package com.android.tools.r8.ir.code;
 
 import static com.android.tools.r8.graph.DexProgramClass.asProgramClassOrNull;
+import static com.android.tools.r8.ir.analysis.type.Nullability.maybeNull;
 
 import com.android.tools.r8.cf.LoadStoreHelper;
 import com.android.tools.r8.cf.TypeVerificationHelper;
@@ -21,6 +22,7 @@
 import com.android.tools.r8.ir.analysis.modeling.LibraryMethodReadSetModeling;
 import com.android.tools.r8.ir.analysis.type.ClassTypeElement;
 import com.android.tools.r8.ir.analysis.type.TypeAnalysis;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.analysis.value.AbstractValue;
 import com.android.tools.r8.ir.analysis.value.UnknownValue;
 import com.android.tools.r8.ir.optimize.DefaultInliningOracle;
@@ -253,6 +255,11 @@
       return self();
     }
 
+    public B setFreshOutValue(AppView<?> appView, ValueFactory factory) {
+      return super.setFreshOutValue(
+          factory, TypeElement.fromDexType(method.getReturnType(), maybeNull(), appView));
+    }
+
     public B setSingleArgument(Value argument) {
       return setArguments(ImmutableList.of(argument));
     }
diff --git a/src/main/java/com/android/tools/r8/ir/code/LinearFlowInstructionListIterator.java b/src/main/java/com/android/tools/r8/ir/code/LinearFlowInstructionListIterator.java
index bbb6d27..3e61866 100644
--- a/src/main/java/com/android/tools/r8/ir/code/LinearFlowInstructionListIterator.java
+++ b/src/main/java/com/android/tools/r8/ir/code/LinearFlowInstructionListIterator.java
@@ -100,6 +100,18 @@
   }
 
   @Override
+  public void replaceCurrentInstructionWithThrow(
+      AppView<?> appView,
+      IRCode code,
+      ListIterator<BasicBlock> blockIterator,
+      Value exceptionValue,
+      Set<BasicBlock> blocksToRemove,
+      Set<Value> affectedValues) {
+    currentBlockIterator.replaceCurrentInstructionWithThrow(
+        appView, code, blockIterator, exceptionValue, blocksToRemove, affectedValues);
+  }
+
+  @Override
   public void replaceCurrentInstructionWithThrowNull(
       AppView<? extends AppInfoWithClassHierarchy> appView,
       IRCode code,
diff --git a/src/main/java/com/android/tools/r8/ir/code/StaticPut.java b/src/main/java/com/android/tools/r8/ir/code/StaticPut.java
index 82d67b0..1017f53 100644
--- a/src/main/java/com/android/tools/r8/ir/code/StaticPut.java
+++ b/src/main/java/com/android/tools/r8/ir/code/StaticPut.java
@@ -18,6 +18,7 @@
 import com.android.tools.r8.graph.DexEncodedField;
 import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.FieldResolutionResult.SuccessfulFieldResolutionResult;
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.analysis.ClassInitializationAnalysis;
 import com.android.tools.r8.ir.analysis.ClassInitializationAnalysis.AnalysisAssumption;
@@ -28,7 +29,6 @@
 import com.android.tools.r8.ir.optimize.InliningConstraints;
 import com.android.tools.r8.ir.regalloc.RegisterAllocator;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
-import com.android.tools.r8.shaking.ProguardMemberRule;
 
 public class StaticPut extends FieldInstruction implements StaticFieldInstruction {
 
@@ -99,22 +99,14 @@
     if (appView.appInfo().hasLiveness()) {
       AppView<AppInfoWithLiveness> appViewWithLiveness = appView.withLiveness();
       AppInfoWithLiveness appInfoWithLiveness = appViewWithLiveness.appInfo();
-      // MemberValuePropagation will replace the field read only if the target field has bound
-      // -assumevalues rule whose return value is *single*.
-      //
-      // Note that, in principle, class initializer of the field's holder may have side effects.
-      // However, with -assumevalues, we assume that the developer wants to remove field accesses.
-      ProguardMemberRule rule = appInfoWithLiveness.assumedValues.get(getField());
-      if (rule != null && rule.getReturnValue().isSingleValue()) {
-        return false;
-      }
 
-      if (instructionInstanceCanThrow(appView, context, assumption)) {
+      SuccessfulFieldResolutionResult resolutionResult =
+          appInfoWithLiveness.resolveField(getField()).asSuccessfulResolution();
+      if (internalInstructionInstanceCanThrow(appView, context, assumption, resolutionResult)) {
         return true;
       }
 
-      DexEncodedField encodedField =
-          appInfoWithLiveness.resolveField(getField()).getResolvedField();
+      DexEncodedField encodedField = resolutionResult.getResolvedField();
       assert encodedField != null : "NoSuchFieldError (resolution failure) should be caught.";
 
       boolean isDeadProtoExtensionField =
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 de727c4..5df390f 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
@@ -407,7 +407,7 @@
       if (!getMethod().isStatic()) {
         firstLocalIndex++;
       }
-      for (DexType value : getMethod().proto().parameters.values) {
+      for (DexType value : getMethod().getProto().parameters.values) {
         firstLocalIndex++;
         if (value.isLongType() || value.isDoubleType()) {
           firstLocalIndex++;
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
new file mode 100644
index 0000000..e747891
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/conversion/ClassConverter.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.ir.conversion;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexApplication;
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.utils.ThreadUtils;
+import com.google.common.collect.Sets;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
+
+public abstract class ClassConverter {
+
+  private final IRConverter converter;
+  private final D8MethodProcessor methodProcessor;
+
+  ClassConverter(IRConverter converter, D8MethodProcessor methodProcessor) {
+    this.converter = converter;
+    this.methodProcessor = methodProcessor;
+  }
+
+  public static ClassConverter create(
+      AppView<?> appView, IRConverter converter, D8MethodProcessor methodProcessor) {
+    return appView.options().desugarSpecificOptions().allowAllDesugaredInput
+        ? new LibraryDesugaredClassConverter(appView, converter, methodProcessor)
+        : new DefaultClassConverter(converter, methodProcessor);
+  }
+
+  public void convertClasses(DexApplication application, ExecutorService executorService)
+      throws ExecutionException {
+    internalConvertClasses(application, executorService);
+    notifyAllClassesConverted();
+  }
+
+  private void internalConvertClasses(DexApplication application, ExecutorService executorService)
+      throws ExecutionException {
+    List<DexProgramClass> classes = application.classes();
+    while (!classes.isEmpty()) {
+      Set<DexType> seenNestHosts = Sets.newIdentityHashSet();
+      List<DexProgramClass> deferred = new ArrayList<>(classes.size() / 2);
+      List<DexProgramClass> wave = new ArrayList<>(classes.size());
+      for (DexProgramClass clazz : classes) {
+        if (clazz.isInANest() && !seenNestHosts.add(clazz.getNestHost())) {
+          deferred.add(clazz);
+        } else {
+          wave.add(clazz);
+        }
+      }
+      ThreadUtils.processItems(wave, this::convertClass, executorService);
+      classes = deferred;
+    }
+    methodProcessor.awaitMethodProcessing();
+  }
+
+  abstract void convertClass(DexProgramClass clazz);
+
+  void convertMethods(DexProgramClass clazz) {
+    converter.convertMethods(clazz, methodProcessor);
+  }
+
+  abstract void notifyAllClassesConverted();
+
+  static class DefaultClassConverter extends ClassConverter {
+
+    DefaultClassConverter(IRConverter converter, D8MethodProcessor methodProcessor) {
+      super(converter, methodProcessor);
+    }
+
+    @Override
+    void convertClass(DexProgramClass clazz) {
+      convertMethods(clazz);
+    }
+
+    @Override
+    void notifyAllClassesConverted() {
+      // Intentionally empty.
+    }
+  }
+
+  static class LibraryDesugaredClassConverter extends ClassConverter {
+
+    private final AppView<?> appView;
+    private final Set<DexType> alreadyLibraryDesugared = Sets.newConcurrentHashSet();
+
+    LibraryDesugaredClassConverter(
+        AppView<?> appView, IRConverter converter, D8MethodProcessor methodProcessor) {
+      super(converter, methodProcessor);
+      this.appView = appView;
+    }
+
+    @Override
+    void convertClass(DexProgramClass clazz) {
+      // Classes which has already been through library desugaring will not go through IR
+      // processing again.
+      LibraryDesugaredChecker libraryDesugaredChecker = new LibraryDesugaredChecker(appView);
+      if (libraryDesugaredChecker.isClassLibraryDesugared(clazz)) {
+        alreadyLibraryDesugared.add(clazz.getType());
+      } else {
+        convertMethods(clazz);
+      }
+    }
+
+    @Override
+    void notifyAllClassesConverted() {
+      appView.setAlreadyLibraryDesugared(alreadyLibraryDesugared);
+    }
+  }
+}
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
new file mode 100644
index 0000000..61ea47c
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/conversion/D8MethodProcessor.java
@@ -0,0 +1,50 @@
+// Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.ir.conversion;
+
+import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.ir.optimize.info.OptimizationFeedbackIgnore;
+import com.android.tools.r8.utils.ThreadUtils;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Future;
+
+public class D8MethodProcessor extends MethodProcessor {
+
+  private final IRConverter converter;
+  private final ExecutorService executorService;
+  private final List<Future<?>> futures = Collections.synchronizedList(new ArrayList<>());
+
+  public D8MethodProcessor(IRConverter converter, ExecutorService executorService) {
+    this.converter = converter;
+    this.executorService = executorService;
+  }
+
+  public void awaitMethodProcessing() throws ExecutionException {
+    ThreadUtils.awaitFutures(futures);
+    futures.clear();
+  }
+
+  @Override
+  public boolean shouldApplyCodeRewritings(ProgramMethod method) {
+    return true;
+  }
+
+  @Override
+  public void scheduleMethodForProcessingAfterCurrentWave(ProgramMethod method) {
+    futures.add(
+        ThreadUtils.processAsynchronously(
+            () ->
+                converter.rewriteCode(method, OptimizationFeedbackIgnore.getInstance(), this, null),
+            executorService));
+  }
+
+  public boolean verifyAllMethodsProcessed() {
+    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 27a8c72..1bee2d4 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
@@ -2402,7 +2402,7 @@
     }
 
     // Move all normal successors to the new block.
-    currentBlockInfo.normalSuccessors.forEach((Integer offset) -> info.addNormalSuccessor(offset));
+    currentBlockInfo.normalSuccessors.forEach(info::addNormalSuccessor);
     currentBlockInfo.normalSuccessors.clear();
 
     // Link the two blocks.
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 d08eb3e..dd9bee9 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
@@ -45,7 +45,6 @@
 import com.android.tools.r8.ir.code.Value;
 import com.android.tools.r8.ir.desugar.BackportedMethodRewriter;
 import com.android.tools.r8.ir.desugar.CovariantReturnTypeAnnotationTransformer;
-import com.android.tools.r8.ir.desugar.D8NestBasedAccessDesugaring;
 import com.android.tools.r8.ir.desugar.DesugaredLibraryAPIConverter;
 import com.android.tools.r8.ir.desugar.DesugaredLibraryAPIConverter.Mode;
 import com.android.tools.r8.ir.desugar.DesugaredLibraryRetargeter;
@@ -54,6 +53,8 @@
 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.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;
@@ -108,12 +109,10 @@
 import com.android.tools.r8.utils.collections.SortedProgramMethodSet;
 import com.google.common.base.Suppliers;
 import com.google.common.collect.ImmutableList;
-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.Set;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.atomic.AtomicBoolean;
@@ -240,7 +239,9 @@
           new DesugaredLibraryAPIConverter(appView, Mode.GENERATE_CALLBACKS_AND_WRAPPERS);
       this.backportedMethodRewriter = new BackportedMethodRewriter(appView);
       this.twrCloseResourceRewriter =
-          enableTwrCloseResourceDesugaring() ? new TwrCloseResourceRewriter(appView, this) : null;
+          TwrCloseResourceRewriter.enableTwrCloseResourceDesugaring(appView.options())
+              ? new TwrCloseResourceRewriter(appView)
+              : null;
       this.d8NestBasedAccessDesugaring =
           options.shouldDesugarNests() ? new D8NestBasedAccessDesugaring(appView) : null;
       this.lambdaMerger = null;
@@ -274,8 +275,9 @@
             ? new InterfaceMethodRewriter(appView, this)
             : null;
     this.twrCloseResourceRewriter =
-        (options.desugarState == DesugarState.ON && enableTwrCloseResourceDesugaring())
-            ? new TwrCloseResourceRewriter(appView, this)
+        (TwrCloseResourceRewriter.enableTwrCloseResourceDesugaring(options)
+                && !appView.enableWholeProgramOptimizations())
+            ? new TwrCloseResourceRewriter(appView)
             : null;
     this.backportedMethodRewriter =
         (options.desugarState == DesugarState.ON && !appView.enableWholeProgramOptimizations())
@@ -322,7 +324,9 @@
         this.identifierNameStringMarker = null;
       }
       this.devirtualizer =
-          options.enableDevirtualization ? new Devirtualizer(appViewWithLiveness) : null;
+          options.enableDevirtualization
+              ? new Devirtualizer(appViewWithLiveness, mainDexClasses)
+              : null;
       this.typeChecker = new TypeChecker(appViewWithLiveness, VerifyTypesHelper.create(appView));
       this.d8NestBasedAccessDesugaring = null;
       this.serviceLoaderRewriter =
@@ -382,38 +386,34 @@
     this(AppView.createForD8(appInfo), timing, printer, MainDexTracingResult.NONE);
   }
 
-  private boolean enableTwrCloseResourceDesugaring() {
-    return enableTryWithResourcesDesugaring() && !options.canUseTwrCloseResourceMethod();
-  }
-
-  private boolean enableTryWithResourcesDesugaring() {
-    switch (options.tryWithResourcesDesugaring) {
-      case Off:
-        return false;
-      case Auto:
-        return !options.canUseSuppressedExceptions();
-    }
-    throw new Unreachable();
-  }
-
   private void removeLambdaDeserializationMethods() {
     if (lambdaRewriter != null) {
       lambdaRewriter.removeLambdaDeserializationMethods(appView.appInfo().classes());
     }
   }
 
-  private void desugarNestBasedAccess(Builder<?> builder, ExecutorService executorService)
-      throws ExecutionException {
+  private void synthesizeBridgesForNestBasedAccessesOnClasspath(
+      MethodProcessor methodProcessor, ExecutorService executorService) throws ExecutionException {
     if (d8NestBasedAccessDesugaring != null) {
-      d8NestBasedAccessDesugaring.desugarNestBasedAccess(builder, executorService, this);
+      d8NestBasedAccessDesugaring.synthesizeBridgesForNestBasedAccessesOnClasspath(
+          methodProcessor, executorService);
     }
   }
 
-  private void synthesizeLambdaClasses(Builder<?> builder, ExecutorService executorService)
-      throws ExecutionException {
+  private void finalizeNestBasedAccessDesugaring(Builder<?> builder) {
+    if (d8NestBasedAccessDesugaring != null) {
+      DexProgramClass nestConstructor = d8NestBasedAccessDesugaring.synthesizeNestConstructor();
+      if (nestConstructor != null) {
+        builder.addProgramClass(nestConstructor);
+      }
+      d8NestBasedAccessDesugaring.reportDesugarDependencies();
+    }
+  }
+
+  private void synthesizeLambdaClasses(ExecutorService executorService) throws ExecutionException {
     if (lambdaRewriter != null) {
       assert !appView.enableWholeProgramOptimizations();
-      lambdaRewriter.finalizeLambdaDesugaringForD8(builder, this, executorService);
+      lambdaRewriter.finalizeLambdaDesugaringForD8(this, executorService);
     }
   }
 
@@ -435,16 +435,16 @@
       Flavor includeAllResources,
       ExecutorService executorService)
       throws ExecutionException {
+    assert !appView.getSyntheticItems().hasPendingSyntheticClasses();
     if (interfaceMethodRewriter != null) {
       interfaceMethodRewriter.desugarInterfaceMethods(
           builder, includeAllResources, executorService);
     }
   }
 
-  private void synthesizeTwrCloseResourceUtilityClass(
-      Builder<?> builder, ExecutorService executorService) throws ExecutionException {
+  private void processTwrCloseResourceUtilityMethods() {
     if (twrCloseResourceRewriter != null) {
-      twrCloseResourceRewriter.synthesizeUtilityClass(builder, executorService, options);
+      twrCloseResourceRewriter.processSynthesizedMethods(this);
     }
   }
 
@@ -489,41 +489,27 @@
     DexApplication application = appView.appInfo().app();
     timing.begin("IR conversion");
 
-    if (appView.options().desugarSpecificOptions().allowAllDesugaredInput) {
-      // Classes which has already been through library desugaring will not go through IR
-      // processing again.
-      LibraryDesugaredChecker libraryDesugaredChecker = new LibraryDesugaredChecker(appView);
-      Set<DexType> alreadyLibraryDesugared = Sets.newConcurrentHashSet();
-      ThreadUtils.processItems(
-          application.classes(),
-          clazz -> {
-            if (libraryDesugaredChecker.isClassLibraryDesugared(clazz)) {
-              if (appView.options().desugarSpecificOptions().allowAllDesugaredInput) {
-                alreadyLibraryDesugared.add(clazz.getType());
-              } else {
-                throw new CompilationError(
-                    "Code for "
-                        + clazz.getType().getDescriptor()
-                        + "has already been library desugared.");
-              }
-            } else {
-              convertMethods(clazz);
-            }
-          },
-          executor);
-      appView.setAlreadyLibraryDesugared(alreadyLibraryDesugared);
-    } else {
-      ThreadUtils.processItems(application.classes(), this::convertMethods, executor);
-    }
+    convertClasses(application, executor);
 
     // Build a new application with jumbo string info,
-    Builder<?> builder = application.builder();
-    builder.setHighestSortingString(highestSortingString);
+    Builder<?> builder = application.builder().setHighestSortingString(highestSortingString);
 
-    desugarNestBasedAccess(builder, executor);
-    synthesizeLambdaClasses(builder, executor);
+    finalizeNestBasedAccessDesugaring(builder);
+
+    // Synthesize lambda classes and commit to the app in full.
+    synthesizeLambdaClasses(executor);
+    processTwrCloseResourceUtilityMethods();
+    if (appView.getSyntheticItems().hasPendingSyntheticClasses()) {
+      appView.setAppInfo(
+          new AppInfo(
+              appView.appInfo().getSyntheticItems().commit(builder.build()),
+              appView.appInfo().getMainDexClasses()));
+      application = appView.appInfo().app();
+      builder = application.builder();
+      builder.setHighestSortingString(highestSortingString);
+    }
+
     desugarInterfaceMethods(builder, ExcludeDexResources, executor);
-    synthesizeTwrCloseResourceUtilityClass(builder, executor);
     processSynthesizedJava8UtilityClasses(executor);
     synthesizeRetargetClass(builder, executor);
     synthesizeInvokeSpecialBridges(executor);
@@ -532,14 +518,24 @@
 
     timing.end();
 
-    DexApplication app = builder.build();
+    application = builder.build();
     appView.setAppInfo(
         new AppInfo(
-            appView.appInfo().getSyntheticItems().commit(app),
+            appView.appInfo().getSyntheticItems().commit(application),
             appView.appInfo().getMainDexClasses()));
   }
 
-  private void convertMethods(DexProgramClass clazz) {
+  private void convertClasses(DexApplication application, ExecutorService executorService)
+      throws ExecutionException {
+    D8MethodProcessor methodProcessor = new D8MethodProcessor(this, executorService);
+    ClassConverter classConverter = ClassConverter.create(appView, this, methodProcessor);
+    classConverter.convertClasses(application, executorService);
+
+    synthesizeBridgesForNestBasedAccessesOnClasspath(methodProcessor, executorService);
+    methodProcessor.awaitMethodProcessing();
+  }
+
+  void convertMethods(DexProgramClass clazz, MethodProcessor methodProcessor) {
     boolean isReachabilitySensitive = clazz.hasReachabilitySensitiveAnnotation(options.itemFactory);
     // When converting all methods on a class always convert <clinit> first.
     DexEncodedMethod classInitializer = clazz.getClassInitializer();
@@ -547,14 +543,14 @@
       classInitializer
           .getMutableOptimizationInfo()
           .setReachabilitySensitive(isReachabilitySensitive);
-      convertMethod(new ProgramMethod(clazz, classInitializer));
+      convertMethod(new ProgramMethod(clazz, classInitializer), methodProcessor);
     }
     clazz.forEachProgramMethodMatching(
         definition -> !definition.isClassInitializer(),
         method -> {
           DexEncodedMethod definition = method.getDefinition();
           definition.getMutableOptimizationInfo().setReachabilitySensitive(isReachabilitySensitive);
-          convertMethod(method);
+          convertMethod(method, methodProcessor);
         });
     // The class file version is downgraded after compilation. Some of the desugaring might need
     // the initial class file version to determine how far a method can be downgraded.
@@ -564,7 +560,7 @@
     }
   }
 
-  private void convertMethod(ProgramMethod method) {
+  private void convertMethod(ProgramMethod method, MethodProcessor methodProcessor) {
     DexEncodedMethod definition = method.getDefinition();
     if (definition.hasClassFileVersion()) {
       definition.downgradeClassFileVersion(
@@ -583,8 +579,7 @@
     if (options.isGeneratingClassFiles()
         || !(options.passthroughDexCode && definition.getCode().isDexCode())) {
       // We do not process in call graph order, so anything could be a leaf.
-      rewriteCode(
-          method, simpleOptimizationFeedback, OneTimeMethodProcessor.create(method, appView), null);
+      rewriteCode(method, simpleOptimizationFeedback, methodProcessor, null);
     } else {
       assert definition.getCode().isDexCode();
     }
@@ -760,6 +755,7 @@
     } else {
       appView.setUnboxedEnums(EnumDataMap.empty());
     }
+
     if (!options.debug) {
       new TrivialFieldAccessReprocessor(appView.withLiveness(), postMethodProcessorBuilder)
           .run(executorService, feedback, timing);
@@ -768,7 +764,7 @@
     timing.begin("IR conversion phase 2");
     graphLensForIR = appView.graphLens();
     PostMethodProcessor postMethodProcessor =
-        postMethodProcessorBuilder.build(appView.withLiveness(), executorService, timing);
+        postMethodProcessorBuilder.build(appView, executorService, timing);
     if (postMethodProcessor != null) {
       assert !options.debug;
       postMethodProcessor.forEachWaveWithExtension(feedback, executorService);
@@ -793,6 +789,14 @@
       appView.clearCodeRewritings();
     }
 
+    // Commit synthetics before creating a builder (otherwise the builder will not include the
+    // synthetics.)
+    if (appView.getSyntheticItems().hasPendingSyntheticClasses()) {
+      appView.setAppInfo(
+          appView
+              .appInfo()
+              .rebuildWithLiveness(appView.getSyntheticItems().commit(appView.appInfo().app())));
+    }
     // Build a new application with jumbo string info.
     Builder<?> builder = appView.appInfo().app().builder();
     builder.setHighestSortingString(highestSortingString);
@@ -802,7 +806,6 @@
     feedback.updateVisibleOptimizationInfo();
 
     printPhase("Utility classes synthesis");
-    synthesizeTwrCloseResourceUtilityClass(builder, executorService);
     processSynthesizedJava8UtilityClasses(executorService);
     synthesizeRetargetClass(builder, executorService);
     synthesizeEnumUnboxingUtilityMethods(executorService);
@@ -814,11 +817,9 @@
     printPhase("Desugared library API Conversion finalization");
     generateDesugaredLibraryAPIWrappers(builder, executorService);
 
-    if (serviceLoaderRewriter != null && serviceLoaderRewriter.getSynthesizedClass() != null) {
-      appView.appInfo().addSynthesizedClass(serviceLoaderRewriter.getSynthesizedClass(), true);
+    if (serviceLoaderRewriter != null) {
       processSynthesizedServiceLoaderMethods(
-          serviceLoaderRewriter.getSynthesizedClass(), executorService);
-      builder.addSynthesizedClass(serviceLoaderRewriter.getSynthesizedClass());
+          serviceLoaderRewriter.getServiceLoadMethods(), executorService);
     }
 
     // Update optimization info for all synthesized methods at once.
@@ -959,11 +960,10 @@
   }
 
   private void processSynthesizedServiceLoaderMethods(
-      DexProgramClass synthesizedClass, ExecutorService executorService) throws ExecutionException {
+      List<ProgramMethod> serviceLoadMethods, ExecutorService executorService)
+      throws ExecutionException {
     ThreadUtils.processItems(
-        synthesizedClass::forEachProgramMethod,
-        this::forEachSynthesizedServiceLoaderMethod,
-        executorService);
+        serviceLoadMethods, this::forEachSynthesizedServiceLoaderMethod, executorService);
   }
 
   private void forEachSynthesizedServiceLoaderMethod(ProgramMethod method) {
@@ -1135,7 +1135,7 @@
     }
   }
 
-  private Timing rewriteCode(
+  Timing rewriteCode(
       ProgramMethod method,
       OptimizationFeedback feedback,
       MethodProcessor methodProcessor,
@@ -1162,7 +1162,7 @@
           method.toSourceString(),
           logCode(options, method.getDefinition()));
     }
-    boolean didDesugar = desugar(method);
+    boolean didDesugar = desugar(method, methodProcessor);
     if (Log.ENABLED && didDesugar) {
       Log.debug(
           getClass(),
@@ -1185,7 +1185,7 @@
     return optimize(code, feedback, methodProcessor, methodProcessingId);
   }
 
-  private boolean desugar(ProgramMethod method) {
+  private boolean desugar(ProgramMethod method, MethodProcessor methodProcessor) {
     if (options.desugarState != DesugarState.ON) {
       return false;
     }
@@ -1194,13 +1194,17 @@
     }
     boolean didDesugar = false;
     Supplier<AppInfoWithClassHierarchy> lazyAppInfo =
-        Suppliers.memoize(() -> appView.appInfoForDesugaring());
+        Suppliers.memoize(appView::appInfoForDesugaring);
     if (lambdaRewriter != null) {
       didDesugar |= lambdaRewriter.desugarLambdas(method, lazyAppInfo.get()) > 0;
     }
     if (backportedMethodRewriter != null) {
       didDesugar |= backportedMethodRewriter.desugar(method, lazyAppInfo.get());
     }
+    if (d8NestBasedAccessDesugaring != null) {
+      NestBridgeConsumer bridgeConsumer = NestBridgeConsumer.createForD8(methodProcessor);
+      didDesugar |= d8NestBasedAccessDesugaring.desugar(method, bridgeConsumer);
+    }
     return didDesugar;
   }
 
@@ -1247,10 +1251,15 @@
       timing.end();
     }
 
-    assert !method.isProcessed() || !isDebugMode;
+    assert !method.isProcessed() || !isDebugMode
+        : "Method already processed: "
+            + context.toSourceString()
+            + System.lineSeparator()
+            + ExceptionUtils.getMainStackTrace();
     assert !method.isProcessed()
-        || !appView.enableWholeProgramOptimizations()
-        || !appView.appInfo().withLiveness().isNeverReprocessMethod(method.method);
+            || !appView.enableWholeProgramOptimizations()
+            || !appView.appInfo().withLiveness().isNeverReprocessMethod(method.method)
+        : "Illegal reprocessing due to -neverreprocess rule: " + context.toSourceString();
 
     if (lambdaMerger != null) {
       timing.begin("Merge lambdas");
@@ -1474,7 +1483,7 @@
     deadCodeRemover.run(code, timing);
     assert code.isConsistentSSA();
 
-    if (options.desugarState == DesugarState.ON && enableTryWithResourcesDesugaring()) {
+    if (options.desugarState == DesugarState.ON && options.enableTryWithResourcesDesugaring()) {
       timing.begin("Rewrite Throwable suppresed methods");
       codeRewriter.rewriteThrowableAddAndGetSuppressed(code);
       timing.end();
@@ -1530,18 +1539,9 @@
 
     previous = printMethod(code, "IR after class inlining (SSA)", previous);
 
-    if (d8NestBasedAccessDesugaring != null) {
-      timing.begin("Desugar nest access");
-      d8NestBasedAccessDesugaring.rewriteNestBasedAccesses(context, code, appView);
-      timing.end();
-      assert code.isConsistentSSA();
-    }
-
-    previous = printMethod(code, "IR after nest based access desugaring (SSA)", previous);
-
     if (interfaceMethodRewriter != null) {
       timing.begin("Rewrite interface methods");
-      interfaceMethodRewriter.rewriteMethodReferences(code);
+      interfaceMethodRewriter.rewriteMethodReferences(code, methodProcessor, methodProcessingId);
       timing.end();
       assert code.isConsistentSSA();
     }
@@ -1550,7 +1550,8 @@
 
     // This pass has to be after interfaceMethodRewriter and BackportedMethodRewriter.
     if (desugaredLibraryAPIConverter != null
-        && (!appView.enableWholeProgramOptimizations() || methodProcessor.isPrimary())) {
+        && (!appView.enableWholeProgramOptimizations()
+            || methodProcessor.isPrimaryMethodProcessor())) {
       timing.begin("Desugar library API");
       desugaredLibraryAPIConverter.desugar(code);
       timing.end();
@@ -1561,7 +1562,7 @@
 
     if (twrCloseResourceRewriter != null) {
       timing.begin("Rewrite TWR close");
-      twrCloseResourceRewriter.rewriteMethodCode(code);
+      twrCloseResourceRewriter.rewriteIR(code);
       timing.end();
     }
 
@@ -1580,7 +1581,7 @@
 
     // TODO(b/140766440): an ideal solution would be puttting CodeOptimization for this into
     //  the list for primary processing only.
-    if (options.outline.enabled && outliner != null && methodProcessor.isPrimary()) {
+    if (options.outline.enabled && outliner != null && methodProcessor.isPrimaryMethodProcessor()) {
       timing.begin("Identify outlines");
       outliner.getOutlineMethodIdentifierGenerator().accept(code);
       timing.end();
@@ -1633,7 +1634,7 @@
       timing.end();
     }
 
-    if (enumUnboxer != null && methodProcessor.isPrimary()) {
+    if (enumUnboxer != null && methodProcessor.isPrimaryMethodProcessor()) {
       enumUnboxer.analyzeEnums(code);
     }
 
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/MethodProcessor.java b/src/main/java/com/android/tools/r8/ir/conversion/MethodProcessor.java
index 86ff63f..ef9a31c 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/MethodProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/MethodProcessor.java
@@ -8,25 +8,13 @@
 
 public abstract class MethodProcessor {
 
-  public enum Phase {
-    ONE_TIME,
-    PRIMARY,
-    POST
-  }
-
   protected SortedProgramMethodSet wave;
   protected SortedProgramMethodSet waveExtension = SortedProgramMethodSet.createConcurrent();
 
-  public abstract Phase getPhase();
-
   public abstract boolean shouldApplyCodeRewritings(ProgramMethod method);
 
-  public boolean isPrimary() {
-    return getPhase() == Phase.PRIMARY;
-  }
-
-  public boolean isPost() {
-    return getPhase() == Phase.POST;
+  public boolean isPrimaryMethodProcessor() {
+    return false;
   }
 
   public CallSiteInformation getCallSiteInformation() {
@@ -41,7 +29,7 @@
     waveExtension.add(method);
   }
 
-  protected final void prepareForWaveExtensionProcessing() {
+  protected void prepareForWaveExtensionProcessing() {
     if (waveExtension.isEmpty()) {
       wave = SortedProgramMethodSet.empty();
     } else {
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 f348214..c44ce41 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
@@ -18,7 +18,7 @@
 
 class NeedsIRDesugarUseRegistry extends UseRegistry {
 
-  private boolean needsDesugarging = false;
+  private boolean needsDesugaring = false;
   private final AppView appView;
   private final BackportedMethodRewriter backportedMethodRewriter;
   private final DesugaredLibraryRetargeter desugaredLibraryRetargeter;
@@ -40,15 +40,15 @@
   }
 
   public boolean needsDesugaring() {
-    return needsDesugarging;
+    return needsDesugaring;
   }
 
   @Override
   public void registerInitClass(DexType type) {
-    if (!needsDesugarging
+    if (!needsDesugaring
         && desugaredLibraryAPIConverter != null
         && desugaredLibraryAPIConverter.canConvert(type)) {
-      needsDesugarging = true;
+      needsDesugaring = true;
     }
   }
 
@@ -67,31 +67,38 @@
     registerDesugaredLibraryAPIConverter(method);
   }
 
+  private void registerTwrCloseResourceRewriting(DexMethod method) {
+    if (!needsDesugaring) {
+      needsDesugaring =
+          TwrCloseResourceRewriter.isTwrCloseResourceMethod(method, appView.dexItemFactory());
+    }
+  }
+
   private void registerBackportedMethodRewriting(DexMethod method) {
-    if (!needsDesugarging) {
-      needsDesugarging = backportedMethodRewriter.needsDesugaring(method);
+    if (!needsDesugaring) {
+      needsDesugaring = backportedMethodRewriter.needsDesugaring(method);
     }
   }
 
   private void registerInterfaceMethodRewriting(DexMethod method, boolean isInvokeSuper) {
-    if (!needsDesugarging) {
-      needsDesugarging =
+    if (!needsDesugaring) {
+      needsDesugaring =
           interfaceMethodRewriter != null
               && interfaceMethodRewriter.needsRewriting(method, isInvokeSuper, appView);
     }
   }
 
   private void registerDesugaredLibraryAPIConverter(DexMethod method) {
-    if (!needsDesugarging) {
-      needsDesugarging =
+    if (!needsDesugaring) {
+      needsDesugaring =
           desugaredLibraryAPIConverter != null
               && desugaredLibraryAPIConverter.shouldRewriteInvoke(method);
     }
   }
 
   private void registerLibraryRetargeting(DexMethod method, boolean b) {
-    if (!needsDesugarging) {
-      needsDesugarging =
+    if (!needsDesugaring) {
+      needsDesugaring =
           desugaredLibraryRetargeter != null
               && desugaredLibraryRetargeter.getRetargetedMethod(method, b) != null;
     }
@@ -99,9 +106,7 @@
 
   @Override
   public void registerInvokeStatic(DexMethod method) {
-    if (!needsDesugarging) {
-      needsDesugarging = TwrCloseResourceRewriter.isSynthesizedCloseResourceMethod(method, appView);
-    }
+    registerTwrCloseResourceRewriting(method);
     registerBackportedMethodRewriting(method);
     registerLibraryRetargeting(method, false);
     registerInterfaceMethodRewriting(method, false);
@@ -118,7 +123,7 @@
   @Override
   public void registerInvokeStatic(DexMethod method, boolean itf) {
     if (itf) {
-      needsDesugarging = true;
+      needsDesugaring = true;
     }
     registerInvokeStatic(method);
   }
@@ -126,7 +131,7 @@
   @Override
   public void registerCallSite(DexCallSite callSite) {
     super.registerCallSite(callSite);
-    needsDesugarging = true;
+    needsDesugaring = true;
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/OneTimeMethodProcessor.java b/src/main/java/com/android/tools/r8/ir/conversion/OneTimeMethodProcessor.java
index bb3f07c..a8c2e57 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/OneTimeMethodProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/OneTimeMethodProcessor.java
@@ -52,11 +52,6 @@
     return true;
   }
 
-  @Override
-  public Phase getPhase() {
-    return Phase.ONE_TIME;
-  }
-
   public <E extends Exception> void forEachWaveWithExtension(
       ThrowingBiConsumer<ProgramMethod, MethodProcessingId, E> consumer) throws E {
     while (!wave.isEmpty()) {
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/PostMethodProcessor.java b/src/main/java/com/android/tools/r8/ir/conversion/PostMethodProcessor.java
index 3d86936..649edfd 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/PostMethodProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/PostMethodProcessor.java
@@ -37,14 +37,14 @@
 
   private final AppView<AppInfoWithLiveness> appView;
   private final Collection<CodeOptimization> defaultCodeOptimizations;
-  private final Map<DexEncodedMethod, Collection<CodeOptimization>> methodsMap;
+  private final Map<DexMethod, Collection<CodeOptimization>> methodsMap;
   private final Deque<SortedProgramMethodSet> waves;
   private final ProgramMethodSet processed = ProgramMethodSet.create();
 
   private PostMethodProcessor(
       AppView<AppInfoWithLiveness> appView,
       Collection<CodeOptimization> defaultCodeOptimizations,
-      Map<DexEncodedMethod, Collection<CodeOptimization>> methodsMap,
+      Map<DexMethod, Collection<CodeOptimization>> methodsMap,
       CallGraph callGraph) {
     this.appView = appView;
     this.defaultCodeOptimizations = defaultCodeOptimizations;
@@ -53,11 +53,6 @@
   }
 
   @Override
-  public Phase getPhase() {
-    return Phase.POST;
-  }
-
-  @Override
   public boolean shouldApplyCodeRewritings(ProgramMethod method) {
     assert !wave.contains(method);
     return !processed.contains(method);
@@ -68,7 +63,7 @@
     private final Collection<CodeOptimization> defaultCodeOptimizations;
     private final LongLivedProgramMethodSetBuilder<?> methodsToReprocess =
         LongLivedProgramMethodSetBuilder.createForIdentitySet();
-    private final Map<DexEncodedMethod, Collection<CodeOptimization>> optimizationsMap =
+    private final Map<DexMethod, Collection<CodeOptimization>> optimizationsMap =
         new IdentityHashMap<>();
 
     Builder(Collection<CodeOptimization> defaultCodeOptimizations) {
@@ -85,7 +80,7 @@
         methodsToReprocess.add(method);
         optimizationsMap
             .computeIfAbsent(
-                method.getDefinition(),
+                method.getReference(),
                 // Optimization order might matter, hence a collection that preserves orderings.
                 k -> new LinkedHashSet<>())
             .addAll(codeOptimizations);
@@ -110,13 +105,11 @@
     // according to the graph lens.
     public void rewrittenWithLens(AppView<AppInfoWithLiveness> appView, GraphLens applied) {
       methodsToReprocess.rewrittenWithLens(appView, applied);
-      Map<DexEncodedMethod, Collection<CodeOptimization>> newOptimizationsMap =
-          new IdentityHashMap<>();
+      Map<DexMethod, Collection<CodeOptimization>> newOptimizationsMap = new IdentityHashMap<>();
       optimizationsMap.forEach(
           (method, optimizations) ->
               newOptimizationsMap.put(
-                  appView.graphLens().mapDexEncodedMethod(method, appView, applied),
-                  optimizations));
+                  appView.graphLens().getRenamedMethodSignature(method, applied), optimizations));
       optimizationsMap.clear();
       optimizationsMap.putAll(newOptimizationsMap);
     }
@@ -164,9 +157,13 @@
   }
 
   @Override
-  public void scheduleMethodForProcessingAfterCurrentWave(ProgramMethod method) {
-    super.scheduleMethodForProcessingAfterCurrentWave(method);
-    methodsMap.put(method.getDefinition(), defaultCodeOptimizations);
+  protected void prepareForWaveExtensionProcessing() {
+    waveExtension.forEach(
+        method -> {
+          assert !methodsMap.containsKey(method.getReference());
+          methodsMap.put(method.getReference(), defaultCodeOptimizations);
+        });
+    super.prepareForWaveExtensionProcessing();
   }
 
   void forEachWaveWithExtension(OptimizationFeedback feedback, ExecutorService executorService)
@@ -182,7 +179,7 @@
             wave,
             (method, index) -> {
               Collection<CodeOptimization> codeOptimizations =
-                  methodsMap.get(method.getDefinition());
+                  methodsMap.get(method.getReference());
               assert codeOptimizations != null && !codeOptimizations.isEmpty();
               forEachMethod(
                   method, codeOptimizations, feedback, methodProcessingIds.get(method, index));
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/PrimaryMethodProcessor.java b/src/main/java/com/android/tools/r8/ir/conversion/PrimaryMethodProcessor.java
index dd4c0a0..cb288b0 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/PrimaryMethodProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/PrimaryMethodProcessor.java
@@ -62,8 +62,8 @@
   }
 
   @Override
-  public Phase getPhase() {
-    return Phase.PRIMARY;
+  public boolean isPrimaryMethodProcessor() {
+    return true;
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/BackportedMethodRewriter.java b/src/main/java/com/android/tools/r8/ir/desugar/BackportedMethodRewriter.java
index 40b18c9..994e2ba 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/BackportedMethodRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/BackportedMethodRewriter.java
@@ -7,7 +7,6 @@
 import com.android.tools.r8.cf.code.CfInstruction;
 import com.android.tools.r8.cf.code.CfInvoke;
 import com.android.tools.r8.dex.ApplicationReader;
-import com.android.tools.r8.dex.Constants;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.AppInfo;
 import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
@@ -17,7 +16,6 @@
 import com.android.tools.r8.graph.DexApplication;
 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.DexProto;
 import com.android.tools.r8.graph.DexString;
 import com.android.tools.r8.graph.DexType;
@@ -33,12 +31,16 @@
 import com.android.tools.r8.ir.desugar.backports.NumericMethodRewrites;
 import com.android.tools.r8.ir.desugar.backports.ObjectsMethodRewrites;
 import com.android.tools.r8.ir.desugar.backports.OptionalMethodRewrites;
+import com.android.tools.r8.synthesis.SyntheticNaming;
 import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.AndroidApp;
+import com.android.tools.r8.utils.IntBox;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.InternalOptions.DesugarState;
 import com.android.tools.r8.utils.StringDiagnostic;
 import com.android.tools.r8.utils.Timing;
+import com.google.common.hash.Hasher;
+import com.google.common.hash.Hashing;
 import java.io.IOException;
 import java.util.ArrayList;
 import java.util.Collections;
@@ -51,6 +53,7 @@
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.ExecutorService;
 import java.util.function.Consumer;
+import java.util.function.Supplier;
 import org.objectweb.asm.Opcodes;
 
 public final class BackportedMethodRewriter {
@@ -128,6 +131,14 @@
     }
     CfCode code = method.getDefinition().getCode().asCfCode();
     ListIterator<CfInstruction> iterator = code.getInstructions().listIterator();
+    // TODO(b/172194101): Make this part of a unique context construction.
+    IntBox nextBackportId = new IntBox();
+    Supplier<String> methodIdSupplier =
+        () -> {
+          Hasher hasher = Hashing.sha256().newHasher();
+          method.getReference().hash(hasher);
+          return "$" + hasher.hash().toString() + "$" + nextBackportId.getAndIncrement();
+        };
     boolean replaced = false;
     while (iterator.hasNext()) {
       CfInvoke invoke = iterator.next().asInvoke();
@@ -144,7 +155,7 @@
           iterator = mutableInstructions.listIterator(iterator.previousIndex());
           iterator.next();
         }
-        provider.rewriteInvoke(invoke, iterator, method.getHolder(), appInfo, consumer);
+        provider.rewriteInvoke(invoke, iterator, method, appInfo, consumer, methodIdSupplier);
         replaced = true;
       }
     }
@@ -1348,9 +1359,10 @@
     public abstract void rewriteInvoke(
         CfInvoke invoke,
         ListIterator<CfInstruction> iterator,
-        DexProgramClass context,
+        ProgramMethod context,
         AppInfoWithClassHierarchy appInfo,
-        Consumer<ProgramMethod> registerSynthesizedMethod);
+        Consumer<ProgramMethod> registerSynthesizedMethod,
+        Supplier<String> methodIdProvider);
   }
 
   private static final class InvokeRewriter extends MethodProvider {
@@ -1366,9 +1378,10 @@
     public void rewriteInvoke(
         CfInvoke invoke,
         ListIterator<CfInstruction> iterator,
-        DexProgramClass context,
+        ProgramMethod context,
         AppInfoWithClassHierarchy appInfo,
-        Consumer<ProgramMethod> registerSynthesizedMethod) {
+        Consumer<ProgramMethod> registerSynthesizedMethod,
+        Supplier<String> methodIdProvider) {
       rewriter.rewrite(invoke, iterator, appInfo.dexItemFactory());
     }
   }
@@ -1392,31 +1405,33 @@
     public void rewriteInvoke(
         CfInvoke invoke,
         ListIterator<CfInstruction> iterator,
-        DexProgramClass context,
+        ProgramMethod context,
         AppInfoWithClassHierarchy appInfo,
-        Consumer<ProgramMethod> registerSynthesizedMethod) {
-      ProgramMethod method = getSyntheticMethod(context, appInfo);
+        Consumer<ProgramMethod> registerSynthesizedMethod,
+        Supplier<String> methodIdProvider) {
+      ProgramMethod method = getSyntheticMethod(context, methodIdProvider, appInfo);
       registerSynthesizedMethod.accept(method);
       iterator.remove();
       iterator.add(new CfInvoke(Opcodes.INVOKESTATIC, method.getReference(), false));
     }
 
     private ProgramMethod getSyntheticMethod(
-        DexProgramClass context, AppInfoWithClassHierarchy appInfo) {
+        ProgramMethod context,
+        Supplier<String> methodIdProvider,
+        AppInfoWithClassHierarchy appInfo) {
       return appInfo
           .getSyntheticItems()
           .createMethod(
+              SyntheticNaming.SyntheticKind.BACKPORT,
               context,
               appInfo.dexItemFactory(),
               builder ->
                   builder
                       .setProto(getProto(appInfo.dexItemFactory()))
-                      .setAccessFlags(
-                          MethodAccessFlags.fromSharedAccessFlags(
-                              Constants.ACC_PUBLIC | Constants.ACC_STATIC | Constants.ACC_SYNTHETIC,
-                              false))
+                      .setAccessFlags(MethodAccessFlags.createPublicStaticSynthetic())
                       .setCode(
-                          methodSig -> generateTemplateMethod(appInfo.app().options, methodSig)));
+                          methodSig -> generateTemplateMethod(appInfo.app().options, methodSig)),
+              methodIdProvider);
     }
 
     public DexProto getProto(DexItemFactory itemFactory) {
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/D8NestBasedAccessDesugaring.java b/src/main/java/com/android/tools/r8/ir/desugar/D8NestBasedAccessDesugaring.java
deleted file mode 100644
index 787915f..0000000
--- a/src/main/java/com/android/tools/r8/ir/desugar/D8NestBasedAccessDesugaring.java
+++ /dev/null
@@ -1,173 +0,0 @@
-// Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
-
-package com.android.tools.r8.ir.desugar;
-
-import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.DexApplication;
-import com.android.tools.r8.graph.DexClass;
-import com.android.tools.r8.graph.DexEncodedField;
-import com.android.tools.r8.graph.DexEncodedMethod;
-import com.android.tools.r8.graph.DexMethod;
-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.code.BasicBlock;
-import com.android.tools.r8.ir.code.FieldInstruction;
-import com.android.tools.r8.ir.code.IRCode;
-import com.android.tools.r8.ir.code.Instruction;
-import com.android.tools.r8.ir.code.InstructionListIterator;
-import com.android.tools.r8.ir.code.InvokeDirect;
-import com.android.tools.r8.ir.code.InvokeMethod;
-import com.android.tools.r8.ir.code.InvokeStatic;
-import com.android.tools.r8.ir.code.Value;
-import com.android.tools.r8.ir.conversion.IRConverter;
-import com.android.tools.r8.utils.ThreadUtils;
-import com.android.tools.r8.utils.collections.SortedProgramMethodSet;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.List;
-import java.util.ListIterator;
-import java.util.Map;
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.ExecutionException;
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Future;
-
-// Summary:
-// - Process all methods compiled rewriting nest based access (Methods processed concurrently).
-// - Process classes on class path in reachable nests to find bridges to add
-//    in Program classes (Nests processed concurrently).
-// - Add bridges and nest constructor class (Sequential).
-// - Optimize bridges (Bridges processed concurrently).
-public class D8NestBasedAccessDesugaring extends NestBasedAccessDesugaring {
-
-  // Maps a nest host to a class met which has that nest host.
-  // The value is used because the nest host might be missing.
-  private final Map<DexType, DexProgramClass> metNestHosts = new ConcurrentHashMap<>();
-
-  public D8NestBasedAccessDesugaring(AppView<?> appView) {
-    super(appView);
-  }
-
-  public void rewriteNestBasedAccesses(ProgramMethod method, IRCode code, AppView<?> appView) {
-    if (!method.getHolder().isInANest()) {
-      return;
-    }
-    metNestHosts.put(method.getHolder().getNestHost(), method.getHolder());
-
-    ListIterator<BasicBlock> blocks = code.listIterator();
-    while (blocks.hasNext()) {
-      BasicBlock block = blocks.next();
-      InstructionListIterator instructions = block.listIterator(code);
-      while (instructions.hasNext()) {
-        Instruction instruction = instructions.next();
-        if (instruction.isInvokeMethod()) {
-          InvokeMethod invokeMethod = instruction.asInvokeMethod();
-          DexMethod invokedMethod = invokeMethod.getInvokedMethod();
-          if (!invokedMethod.holder.isClassType()) {
-            continue;
-          }
-          // Since we only need to desugar accesses to private methods, and all accesses to private
-          // methods must be accessing the private method directly on its holder, we can lookup the
-          // method on the holder instead of resolving the method.
-          DexClass holder = appView.definitionForHolder(invokedMethod);
-          DexEncodedMethod definition = invokedMethod.lookupOnClass(holder);
-          if (definition != null && invokeRequiresRewriting(definition, method)) {
-            DexMethod bridge = ensureInvokeBridge(definition);
-            if (definition.isInstanceInitializer()) {
-              instructions.previous();
-              Value extraNullValue =
-                  instructions.insertConstNullInstruction(code, appView.options());
-              instructions.next();
-              List<Value> parameters = new ArrayList<>(invokeMethod.arguments());
-              parameters.add(extraNullValue);
-              instructions.replaceCurrentInstruction(
-                  new InvokeDirect(bridge, invokeMethod.outValue(), parameters));
-            } else {
-              instructions.replaceCurrentInstruction(
-                  new InvokeStatic(bridge, invokeMethod.outValue(), invokeMethod.arguments()));
-            }
-          }
-        } else if (instruction.isFieldInstruction()) {
-          // 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.
-          FieldInstruction fieldInstruction = instruction.asFieldInstruction();
-          DexClass holder = appView.definitionForHolder(fieldInstruction.getField());
-          DexEncodedField field = fieldInstruction.getField().lookupOnClass(holder);
-          if (field != null && fieldAccessRequiresRewriting(field, method)) {
-            if (instruction.isInstanceGet() || instruction.isStaticGet()) {
-              DexMethod bridge = ensureFieldAccessBridge(field, true);
-              instructions.replaceCurrentInstruction(
-                  new InvokeStatic(bridge, instruction.outValue(), instruction.inValues()));
-            } else {
-              assert instruction.isInstancePut() || instruction.isStaticPut();
-              DexMethod bridge = ensureFieldAccessBridge(field, false);
-              instructions.replaceCurrentInstruction(
-                  new InvokeStatic(bridge, instruction.outValue(), instruction.inValues()));
-            }
-          }
-        }
-      }
-    }
-  }
-
-  private void processNestsConcurrently(ExecutorService executorService) throws ExecutionException {
-    List<Future<?>> futures = new ArrayList<>();
-    for (DexProgramClass clazz : metNestHosts.values()) {
-      futures.add(asyncProcessNest(clazz, executorService));
-    }
-    ThreadUtils.awaitFutures(futures);
-  }
-
-  private void addDeferredBridges() {
-    addDeferredBridges(bridges.values());
-    addDeferredBridges(getFieldBridges.values());
-    addDeferredBridges(putFieldBridges.values());
-  }
-
-  private void addDeferredBridges(Collection<ProgramMethod> bridges) {
-    for (ProgramMethod bridge : bridges) {
-      bridge.getHolder().addMethod(bridge.getDefinition());
-    }
-  }
-
-  private void optimizeDeferredBridgesConcurrently(
-      ExecutorService executorService, IRConverter converter) throws ExecutionException {
-    SortedProgramMethodSet methods = SortedProgramMethodSet.create();
-    methods.addAll(bridges.values());
-    methods.addAll(getFieldBridges.values());
-    methods.addAll(putFieldBridges.values());
-    converter.processMethodsConcurrently(methods, executorService);
-  }
-
-  public void desugarNestBasedAccess(
-      DexApplication.Builder<?> builder, ExecutorService executorService, IRConverter converter)
-      throws ExecutionException {
-    if (metNestHosts.isEmpty()) {
-      return;
-    }
-    processNestsConcurrently(executorService);
-    addDeferredBridges();
-    synthesizeNestConstructor(builder);
-    optimizeDeferredBridgesConcurrently(executorService, converter);
-  }
-
-  // In D8, programClass are processed on the fly so they do not need to be processed again here.
-  @Override
-  protected boolean shouldProcessClassInNest(DexClass clazz, List<DexType> nest) {
-    return clazz.isClasspathClass();
-  }
-
-  @Override
-  void reportMissingNestHost(DexClass clazz) {
-    appView.options().nestDesugaringWarningMissingNestHost(clazz);
-  }
-
-  @Override
-  void reportIncompleteNest(List<DexType> nest) {
-    appView.options().nestDesugaringWarningIncompleteNest(nest, appView);
-  }
-}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryAPIConverter.java b/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryAPIConverter.java
index cf85276..860af39 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryAPIConverter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryAPIConverter.java
@@ -178,7 +178,7 @@
         || definition.isLibraryMethodOverride().isFalse()) {
       return false;
     }
-    if (!appView.rewritePrefix.hasRewrittenTypeInSignature(definition.proto(), appView)
+    if (!appView.rewritePrefix.hasRewrittenTypeInSignature(definition.getProto(), appView)
         || appView
             .options()
             .desugaredLibraryConfiguration
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryRetargeter.java b/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryRetargeter.java
index 3dc74ac..a6606ed 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryRetargeter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryRetargeter.java
@@ -43,6 +43,7 @@
 import com.android.tools.r8.ir.code.InvokeStatic;
 import com.android.tools.r8.ir.conversion.IRConverter;
 import com.android.tools.r8.origin.SynthesizedOrigin;
+import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.StringDiagnostic;
 import com.android.tools.r8.utils.WorkList;
 import com.android.tools.r8.utils.collections.DexClassAndMethodSet;
@@ -64,6 +65,7 @@
 
 public class DesugaredLibraryRetargeter {
 
+  private static final String RETARGET_PACKAGE = "retarget/";
   public static final String DESUGAR_LIB_RETARGET_CLASS_NAME_PREFIX =
       "$r8$retargetLibraryMember$virtualDispatch";
 
@@ -76,14 +78,27 @@
   // Non final virtual library methods requiring generation of emulated dispatch.
   private final DexClassAndMethodSet emulatedDispatchMethods = DexClassAndMethodSet.create();
 
+  private final String packageAndClassDescriptorPrefix;
+
   public DesugaredLibraryRetargeter(AppView<?> appView) {
     this.appView = appView;
+    packageAndClassDescriptorPrefix =
+        getRetargetPackageAndClassPrefixDescriptor(appView.options().desugaredLibraryConfiguration);
     if (appView.options().desugaredLibraryConfiguration.getRetargetCoreLibMember().isEmpty()) {
       return;
     }
     new RetargetingSetup().setUpRetargeting();
   }
 
+  public static boolean isRetargetType(DexType type, InternalOptions options) {
+    if (options.desugaredLibraryConfiguration == null) {
+      return false;
+    }
+    return type.toDescriptorString()
+        .startsWith(
+            getRetargetPackageAndClassPrefixDescriptor(options.desugaredLibraryConfiguration));
+  }
+
   public static void checkForAssumedLibraryTypes(AppView<?> appView) {
     Map<DexString, Map<DexType, DexType>> retargetCoreLibMember =
         appView.options().desugaredLibraryConfiguration.getRetargetCoreLibMember();
@@ -347,8 +362,10 @@
   private class RetargetingSetup {
 
     private void setUpRetargeting() {
+      DesugaredLibraryConfiguration desugaredLibraryConfiguration =
+          appView.options().desugaredLibraryConfiguration;
       Map<DexString, Map<DexType, DexType>> retargetCoreLibMember =
-          appView.options().desugaredLibraryConfiguration.getRetargetCoreLibMember();
+          desugaredLibraryConfiguration.getRetargetCoreLibMember();
       for (DexString methodName : retargetCoreLibMember.keySet()) {
         for (DexType inType : retargetCoreLibMember.get(methodName).keySet()) {
           DexClass typeClass = appView.definitionFor(inType);
@@ -377,6 +394,22 @@
           }
         }
       }
+      if (desugaredLibraryConfiguration.isLibraryCompilation()) {
+        // TODO(b/177977763): This is only a workaround rewriting invokes of j.u.Arrays.deepEquals0
+        // to j.u.DesugarArrays.deepEquals0.
+        DexItemFactory itemFactory = appView.options().dexItemFactory();
+        DexString name = itemFactory.createString("deepEquals0");
+        DexProto proto =
+            itemFactory.createProto(
+                itemFactory.booleanType, itemFactory.objectType, itemFactory.objectType);
+        DexMethod source =
+            itemFactory.createMethod(
+                itemFactory.createType(itemFactory.arraysDescriptor), proto, name);
+        DexMethod target =
+            itemFactory.createMethod(
+                itemFactory.createType("Ljava/util/DesugarArrays;"), proto, name);
+        retargetLibraryMember.put(source, target);
+      }
     }
 
     private boolean isEmulatedInterfaceDispatch(DexClassAndMethod method) {
@@ -668,14 +701,17 @@
     return dispatchTypeFor(method, "dispatchHolder");
   }
 
+  private static String getRetargetPackageAndClassPrefixDescriptor(
+      DesugaredLibraryConfiguration config) {
+    return "L"
+        + config.getSynthesizedLibraryClassesPackagePrefix()
+        + RETARGET_PACKAGE
+        + DESUGAR_LIB_RETARGET_CLASS_NAME_PREFIX;
+  }
+
   private DexType dispatchTypeFor(DexClassAndMethod method, String suffix) {
     String descriptor =
-        "L"
-            + appView
-                .options()
-                .desugaredLibraryConfiguration
-                .getSynthesizedLibraryClassesPackagePrefix()
-            + DESUGAR_LIB_RETARGET_CLASS_NAME_PREFIX
+        packageAndClassDescriptorPrefix
             + '$'
             + method.getHolderType().getName()
             + '$'
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 1529d2f..d2225e6 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
@@ -13,7 +13,6 @@
 
 import com.android.tools.r8.DesugarGraphConsumer;
 import com.android.tools.r8.cf.CfVersion;
-import com.android.tools.r8.dex.Constants;
 import com.android.tools.r8.errors.CompilationError;
 import com.android.tools.r8.errors.Unimplemented;
 import com.android.tools.r8.graph.AppInfo;
@@ -24,6 +23,7 @@
 import com.android.tools.r8.graph.DexCallSite;
 import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexClassAndMethod;
+import com.android.tools.r8.graph.DexClasspathClass;
 import com.android.tools.r8.graph.DexEncodedField;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexItemFactory;
@@ -36,9 +36,13 @@
 import com.android.tools.r8.graph.DexValue;
 import com.android.tools.r8.graph.GenericSignature;
 import com.android.tools.r8.graph.GenericSignature.ClassSignature;
+import com.android.tools.r8.graph.GenericSignature.MethodTypeSignature;
 import com.android.tools.r8.graph.MethodAccessFlags;
+import com.android.tools.r8.graph.MethodCollection;
+import com.android.tools.r8.graph.ParameterAnnotationsList;
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.graph.ResolutionResult.SingleResolutionResult;
+import com.android.tools.r8.ir.analysis.type.TypeAnalysis;
 import com.android.tools.r8.ir.code.BasicBlock;
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.Instruction;
@@ -49,18 +53,24 @@
 import com.android.tools.r8.ir.code.InvokeMethodWithReceiver;
 import com.android.tools.r8.ir.code.InvokeStatic;
 import com.android.tools.r8.ir.code.InvokeSuper;
+import com.android.tools.r8.ir.code.Value;
 import com.android.tools.r8.ir.conversion.IRConverter;
+import com.android.tools.r8.ir.conversion.MethodProcessingId;
+import com.android.tools.r8.ir.conversion.MethodProcessor;
 import com.android.tools.r8.ir.desugar.DefaultMethodsHelper.Collection;
 import com.android.tools.r8.ir.desugar.InterfaceProcessor.InterfaceProcessorNestedGraphLens;
+import com.android.tools.r8.ir.optimize.UtilityMethodsForCodeOptimizations;
+import com.android.tools.r8.ir.optimize.UtilityMethodsForCodeOptimizations.UtilityMethodForCodeOptimizations;
 import com.android.tools.r8.ir.synthetic.ForwardMethodBuilder;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.origin.SynthesizedOrigin;
 import com.android.tools.r8.position.MethodPosition;
 import com.android.tools.r8.shaking.MainDexClasses;
+import com.android.tools.r8.synthesis.SyntheticNaming;
+import com.android.tools.r8.synthesis.SyntheticNaming.SyntheticKind;
 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.ObjectUtils;
 import com.android.tools.r8.utils.Pair;
 import com.android.tools.r8.utils.StringDiagnostic;
 import com.android.tools.r8.utils.collections.SortedProgramMethodSet;
@@ -251,15 +261,21 @@
 
   // Rewrites the references to static and default interface methods.
   // NOTE: can be called for different methods concurrently.
-  public void rewriteMethodReferences(IRCode code) {
+  public void rewriteMethodReferences(
+      IRCode code, MethodProcessor methodProcessor, MethodProcessingId methodProcessingId) {
     ProgramMethod context = code.context();
     if (synthesizedMethods.contains(context)) {
       return;
     }
 
+    Set<Value> affectedValues = Sets.newIdentityHashSet();
+    Set<BasicBlock> blocksToRemove = Sets.newIdentityHashSet();
     ListIterator<BasicBlock> blocks = code.listIterator();
     while (blocks.hasNext()) {
       BasicBlock block = blocks.next();
+      if (blocksToRemove.contains(block)) {
+        continue;
+      }
       InstructionListIterator instructions = block.listIterator(code);
       while (instructions.hasNext()) {
         Instruction instruction = instructions.next();
@@ -271,7 +287,15 @@
             rewriteInvokeDirect(instruction.asInvokeDirect(), instructions, context);
             break;
           case INVOKE_STATIC:
-            rewriteInvokeStatic(instruction.asInvokeStatic(), instructions, context);
+            rewriteInvokeStatic(
+                instruction.asInvokeStatic(),
+                code,
+                blocks,
+                instructions,
+                affectedValues,
+                blocksToRemove,
+                methodProcessor,
+                methodProcessingId);
             break;
           case INVOKE_SUPER:
             rewriteInvokeSuper(instruction.asInvokeSuper(), instructions, context);
@@ -287,6 +311,14 @@
         }
       }
     }
+
+    code.removeBlocks(blocksToRemove);
+
+    if (!affectedValues.isEmpty()) {
+      new TypeAnalysis(appView).narrowing(affectedValues);
+    }
+
+    assert code.isConsistentSSA();
   }
 
   private void rewriteInvokeCustom(InvokeCustom invoke, ProgramMethod context) {
@@ -327,16 +359,16 @@
           getMethodOrigin(context.getReference()));
     }
 
-    DexEncodedMethod directTarget = clazz.lookupMethod(method);
+    DexClassAndMethod directTarget = clazz.lookupClassMethod(method);
     if (directTarget != null) {
       // 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.
       instructions.replaceCurrentInstruction(
           new InvokeStatic(
-              directTarget.isPrivateMethod()
-                  ? privateAsMethodOfCompanionClass(method)
-                  : defaultAsMethodOfCompanionClass(method),
+              directTarget.getDefinition().isPrivateMethod()
+                  ? privateAsMethodOfCompanionClass(directTarget)
+                  : defaultAsMethodOfCompanionClass(directTarget),
               invoke.outValue(),
               invoke.arguments()));
     } else {
@@ -347,7 +379,7 @@
         // This is a invoke-direct call to a virtual method.
         instructions.replaceCurrentInstruction(
             new InvokeStatic(
-                defaultAsMethodOfCompanionClass(virtualTarget.getDefinition().method),
+                defaultAsMethodOfCompanionClass(virtualTarget),
                 invoke.outValue(),
                 invoke.arguments()));
       } else {
@@ -359,13 +391,21 @@
   }
 
   private void rewriteInvokeStatic(
-      InvokeStatic invoke, InstructionListIterator instructions, ProgramMethod context) {
+      InvokeStatic invoke,
+      IRCode code,
+      ListIterator<BasicBlock> blockIterator,
+      InstructionListIterator instructions,
+      Set<Value> affectedValues,
+      Set<BasicBlock> blocksToRemove,
+      MethodProcessor methodProcessor,
+      MethodProcessingId methodProcessingId) {
     DexMethod invokedMethod = invoke.getInvokedMethod();
     if (appView.getSyntheticItems().isPendingSynthetic(invokedMethod.holder)) {
       // We did not create this code yet, but it will not require rewriting.
       return;
     }
 
+    ProgramMethod context = code.context();
     DexClass clazz = appView.definitionFor(invokedMethod.holder, context);
     if (clazz == null) {
       // NOTE: leave unchanged those calls to undefined targets. This may lead to runtime
@@ -406,17 +446,13 @@
             appView
                 .getSyntheticItems()
                 .createMethod(
+                    SyntheticNaming.SyntheticKind.STATIC_INTERFACE_CALL,
                     context.getHolder(),
                     factory,
                     syntheticMethodBuilder ->
                         syntheticMethodBuilder
                             .setProto(invokedMethod.proto)
-                            .setAccessFlags(
-                                MethodAccessFlags.fromSharedAccessFlags(
-                                    Constants.ACC_PUBLIC
-                                        | Constants.ACC_STATIC
-                                        | Constants.ACC_SYNTHETIC,
-                                    false))
+                            .setAccessFlags(MethodAccessFlags.createPublicStaticSynthetic())
                             .setCode(
                                 m ->
                                     ForwardMethodBuilder.builder(factory)
@@ -436,13 +472,43 @@
         // When leaving static interface method invokes upgrade the class file version.
         context.getDefinition().upgradeClassFileVersion(CfVersion.V1_8);
       }
-    } else {
+      return;
+    }
+
+    SingleResolutionResult resolutionResult =
+        appView
+            .appInfoForDesugaring()
+            .resolveMethodOnInterface(clazz, invokedMethod)
+            .asSingleResolution();
+    if (resolutionResult != null && resolutionResult.getResolvedMethod().isStatic()) {
       instructions.replaceCurrentInstruction(
           new InvokeStatic(
-              staticAsMethodOfCompanionClass(invokedMethod),
+              staticAsMethodOfCompanionClass(resolutionResult.getResolutionPair()),
               invoke.outValue(),
               invoke.arguments()));
+      return;
     }
+
+    // Replace by throw new IncompatibleClassChangeError/NoSuchMethodError.
+    UtilityMethodForCodeOptimizations throwMethod =
+        resolutionResult == null
+            ? UtilityMethodsForCodeOptimizations.synthesizeThrowNoSuchMethodErrorMethod(
+                appView, context, methodProcessingId)
+            : UtilityMethodsForCodeOptimizations.synthesizeThrowIncompatibleClassChangeErrorMethod(
+                appView, context, methodProcessingId);
+    throwMethod.optimize(methodProcessor);
+
+    InvokeStatic throwInvoke =
+        InvokeStatic.builder()
+            .setMethod(throwMethod.getMethod())
+            .setFreshOutValue(appView, code)
+            .setPosition(invoke)
+            .build();
+    instructions.previous();
+    instructions.add(throwInvoke);
+    instructions.next();
+    instructions.replaceCurrentInstructionWithThrow(
+        appView, code, blockIterator, throwInvoke.outValue(), blocksToRemove, affectedValues);
   }
 
   private void rewriteInvokeSuper(
@@ -470,7 +536,7 @@
       DexMethod amendedMethod = amendDefaultMethod(context.getHolder(), invokedMethod);
       instructions.replaceCurrentInstruction(
           new InvokeStatic(
-              defaultAsMethodOfCompanionClass(amendedMethod),
+              defaultAsMethodOfCompanionClass(amendedMethod, appView.dexItemFactory()),
               invoke.outValue(),
               invoke.arguments()));
     } else {
@@ -484,7 +550,7 @@
             if (holder.isLibraryClass() && holder.isInterface()) {
               instructions.replaceCurrentInstruction(
                   new InvokeStatic(
-                      defaultAsMethodOfCompanionClass(target.getReference(), factory),
+                      defaultAsMethodOfCompanionClass(target),
                       invoke.outValue(),
                       invoke.arguments()));
             }
@@ -504,9 +570,7 @@
           DexMethod retargetMethod =
               options.desugaredLibraryConfiguration.retargetMethod(superTarget, appView);
           if (retargetMethod == null) {
-            DexMethod originalCompanionMethod =
-                instanceAsMethodOfCompanionClass(
-                    superTarget.getReference(), DEFAULT_METHOD_PREFIX, factory);
+            DexMethod originalCompanionMethod = defaultAsMethodOfCompanionClass(superTarget);
             DexMethod companionMethod =
                 factory.createMethod(
                     getCompanionClassType(emulatedItf),
@@ -892,12 +956,11 @@
 
   // Represent a static interface method as a method of companion class.
   final DexMethod staticAsMethodOfCompanionClass(DexClassAndMethod method) {
-    return staticAsMethodOfCompanionClass(method.getReference());
-  }
-
-  final DexMethod staticAsMethodOfCompanionClass(DexMethod method) {
-    // No changes for static methods.
-    return factory.createMethod(getCompanionClassType(method.holder), method.proto, method.name);
+    DexItemFactory dexItemFactory = appView.dexItemFactory();
+    DexType companionClassType = getCompanionClassType(method.getHolderType(), dexItemFactory);
+    DexMethod rewritten = method.getReference().withHolder(companionClassType, dexItemFactory);
+    recordCompanionClassReference(appView, method, rewritten);
+    return rewritten;
   }
 
   private static DexMethod instanceAsMethodOfCompanionClass(
@@ -930,12 +993,11 @@
     return instanceAsMethodOfCompanionClass(method, DEFAULT_METHOD_PREFIX, factory);
   }
 
-  DexMethod defaultAsMethodOfCompanionClass(DexMethod method) {
-    return defaultAsMethodOfCompanionClass(method, factory);
-  }
-
-  DexMethod defaultAsMethodOfCompanionClass(DexClassAndMethod method) {
-    return defaultAsMethodOfCompanionClass(method.getReference(), factory);
+  final DexMethod defaultAsMethodOfCompanionClass(DexClassAndMethod method) {
+    DexItemFactory dexItemFactory = appView.dexItemFactory();
+    DexMethod rewritten = defaultAsMethodOfCompanionClass(method.getReference(), dexItemFactory);
+    recordCompanionClassReference(appView, method, rewritten);
+    return rewritten;
   }
 
   // Represent a private instance interface method as a method of companion class.
@@ -944,8 +1006,57 @@
     return instanceAsMethodOfCompanionClass(method, PRIVATE_METHOD_PREFIX, factory);
   }
 
-  DexMethod privateAsMethodOfCompanionClass(DexMethod method) {
-    return privateAsMethodOfCompanionClass(method, factory);
+  private DexMethod privateAsMethodOfCompanionClass(DexClassAndMethod method) {
+    return privateAsMethodOfCompanionClass(method.getReference(), factory);
+  }
+
+  private static void recordCompanionClassReference(
+      AppView<?> appView, DexClassAndMethod method, DexMethod rewritten) {
+    // If the interface class is a program class, we shouldn't need to synthesize the companion
+    // class on the classpath.
+    if (method.getHolder().isProgramClass()) {
+      return;
+    }
+
+    // If the companion class does not exist, then synthesize it on the classpath.
+    DexClass companionClass = appView.definitionFor(rewritten.getHolderType());
+    if (companionClass == null) {
+      companionClass =
+          synthesizeEmptyCompanionClass(appView, rewritten.getHolderType(), method.getHolder());
+    }
+
+    // If the companion class is a classpath class, then synthesize the companion class method if it
+    // does not exist.
+    if (companionClass.isClasspathClass()) {
+      synthetizeCompanionClassMethodIfNotPresent(companionClass.asClasspathClass(), rewritten);
+    }
+  }
+
+  private static DexClasspathClass synthesizeEmptyCompanionClass(
+      AppView<?> appView, DexType type, DexClass context) {
+    return appView
+        .getSyntheticItems()
+        .createClasspathClass(
+            SyntheticKind.COMPANION_CLASS, type, context, appView.dexItemFactory());
+  }
+
+  private static void synthetizeCompanionClassMethodIfNotPresent(
+      DexClasspathClass companionClass, DexMethod method) {
+    MethodCollection methodCollection = companionClass.getMethodCollection();
+    synchronized (methodCollection) {
+      if (methodCollection.getMethod(method) == null) {
+        boolean d8R8Synthesized = true;
+        methodCollection.addDirectMethod(
+            new DexEncodedMethod(
+                method,
+                MethodAccessFlags.createPublicStaticSynthetic(),
+                MethodTypeSignature.noSignature(),
+                DexAnnotationSet.empty(),
+                ParameterAnnotationsList.empty(),
+                DexEncodedMethod.buildEmptyThrowingCfCode(method),
+                d8R8Synthesized));
+      }
+    }
   }
 
   private void renameEmulatedInterfaces() {
@@ -1128,6 +1239,7 @@
       Builder<?> builder, Flavor flavour, Consumer<ProgramMethod> newSynthesizedMethodConsumer) {
     ClassProcessor processor = new ClassProcessor(appView, this, newSynthesizedMethodConsumer);
     // First we compute all desugaring *without* introducing forwarding methods.
+    assert appView.getSyntheticItems().verifyNonLegacySyntheticsAreCommitted();
     for (DexProgramClass clazz : builder.getProgramClasses()) {
       if (shouldProcess(clazz, flavour, false)) {
         if (appView.isAlreadyLibraryDesugared(clazz)) {
@@ -1165,11 +1277,8 @@
         || missing.isD8R8SynthesizedClassType()
         || isCompanionClassType(missing)
         || emulatedInterfaces.containsValue(missing)
-        || ObjectUtils.getBooleanOrElse(
-            options.getProguardConfiguration(),
-            configuration -> configuration.getDontWarnPatterns().matches(missing),
-            false)
-        || options.desugaredLibraryConfiguration.getCustomConversions().containsValue(missing);
+        || options.desugaredLibraryConfiguration.getCustomConversions().containsValue(missing)
+        || appView.getDontWarnConfiguration().matches(missing);
   }
 
   void warnMissingInterface(DexClass classToDesugar, DexClass implementing, DexType missing) {
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/InterfaceProcessor.java b/src/main/java/com/android/tools/r8/ir/desugar/InterfaceProcessor.java
index c2db31a..e40251c 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/InterfaceProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/InterfaceProcessor.java
@@ -220,23 +220,28 @@
       List<DexEncodedMethod> companionMethods,
       InterfaceProcessorNestedGraphLens.Builder graphLensBuilder) {
     List<DexEncodedMethod> newVirtualMethods = new ArrayList<>();
-    for (DexEncodedMethod virtual : iface.virtualMethods()) {
+    for (ProgramMethod method : iface.virtualProgramMethods()) {
+      DexEncodedMethod virtual = method.getDefinition();
       if (rewriter.isDefaultMethod(virtual)) {
         if (!canMoveToCompanionClass(virtual)) {
-          throw new CompilationError("One or more instruction is preventing default interface "
-              + "method from being desugared: " + virtual.method.toSourceString(), iface.origin);
+          throw new CompilationError(
+              "One or more instruction is preventing default interface "
+                  + "method from being desugared: "
+                  + method.toSourceString(),
+              iface.origin);
         }
 
         // Create a new method in a companion class to represent default method implementation.
-        DexMethod companionMethod = rewriter.defaultAsMethodOfCompanionClass(virtual.method);
+        DexMethod companionMethod = rewriter.defaultAsMethodOfCompanionClass(method);
 
         Code code = virtual.getCode();
         if (code == null) {
-          throw new CompilationError("Code is missing for default "
-              + "interface method: " + virtual.method.toSourceString(), iface.origin);
+          throw new CompilationError(
+              "Code is missing for default " + "interface method: " + method.toSourceString(),
+              iface.origin);
         }
 
-        MethodAccessFlags newFlags = virtual.accessFlags.copy();
+        MethodAccessFlags newFlags = method.getAccessFlags().copy();
         newFlags.promoteToStatic();
         DexEncodedMethod.setDebugInfoWithFakeThisParameter(
             code, companionMethod.getArity(), appView);
@@ -252,7 +257,7 @@
         implMethod.copyMetadata(virtual);
         virtual.setDefaultInterfaceMethodImplementation(implMethod);
         companionMethods.add(implMethod);
-        graphLensBuilder.recordCodeMovedToCompanionClass(virtual.method, implMethod.method);
+        graphLensBuilder.recordCodeMovedToCompanionClass(method.getReference(), implMethod.method);
       }
 
       // Remove bridge methods.
@@ -272,12 +277,13 @@
       List<DexEncodedMethod> companionMethods,
       InterfaceProcessorNestedGraphLens.Builder graphLensBuilder) {
     DexEncodedMethod clinit = null;
-    for (DexEncodedMethod method : iface.directMethods()) {
-      if (method.isClassInitializer()) {
-        clinit = method;
+    for (ProgramMethod method : iface.directProgramMethods()) {
+      DexEncodedMethod definition = method.getDefinition();
+      if (definition.isClassInitializer()) {
+        clinit = definition;
         continue;
       }
-      if (method.isInstanceInitializer()) {
+      if (definition.isInstanceInitializer()) {
         assert false
             : "Unexpected interface instance initializer: "
                 + method.getReference().toSourceString();
@@ -291,24 +297,24 @@
       }
 
       DexMethod oldMethod = method.getReference();
-      if (isStaticMethod(method)) {
+      if (isStaticMethod(definition)) {
         assert originalFlags.isPrivate() || originalFlags.isPublic()
             : "Static interface method "
                 + method.toSourceString()
                 + " is expected to "
                 + "either be public or private in "
                 + iface.origin;
-        DexMethod companionMethod = rewriter.staticAsMethodOfCompanionClass(oldMethod);
+        DexMethod companionMethod = rewriter.staticAsMethodOfCompanionClass(method);
         DexEncodedMethod implMethod =
             new DexEncodedMethod(
                 companionMethod,
                 newFlags,
-                method.getGenericSignature(),
-                method.annotations(),
-                method.parameterAnnotationsList,
-                method.getCode(),
+                definition.getGenericSignature(),
+                definition.annotations(),
+                definition.parameterAnnotationsList,
+                definition.getCode(),
                 true);
-        implMethod.copyMetadata(method);
+        implMethod.copyMetadata(definition);
         companionMethods.add(implMethod);
         graphLensBuilder.move(oldMethod, companionMethod);
         continue;
@@ -318,9 +324,10 @@
 
       newFlags.promoteToStatic();
 
-      DexMethod companionMethod = rewriter.privateAsMethodOfCompanionClass(oldMethod);
+      DexMethod companionMethod =
+          rewriter.privateAsMethodOfCompanionClass(oldMethod, appView.dexItemFactory());
 
-      Code code = method.getCode();
+      Code code = definition.getCode();
       if (code == null) {
         throw new CompilationError(
             "Code is missing for private instance "
@@ -333,12 +340,12 @@
           new DexEncodedMethod(
               companionMethod,
               newFlags,
-              method.getGenericSignature(),
-              method.annotations(),
-              method.parameterAnnotationsList,
+              definition.getGenericSignature(),
+              definition.annotations(),
+              definition.parameterAnnotationsList,
               code,
               true);
-      implMethod.copyMetadata(method);
+      implMethod.copyMetadata(definition);
       companionMethods.add(implMethod);
       graphLensBuilder.move(oldMethod, companionMethod);
     }
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 a50581e..477d8d8 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
@@ -8,7 +8,6 @@
 import com.android.tools.r8.errors.Unimplemented;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.ClassAccessFlags;
 import com.android.tools.r8.graph.DexAnnotationSet;
 import com.android.tools.r8.graph.DexEncodedField;
 import com.android.tools.r8.graph.DexEncodedMethod;
@@ -20,10 +19,8 @@
 import com.android.tools.r8.graph.DexProto;
 import com.android.tools.r8.graph.DexString;
 import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.graph.DexTypeList;
 import com.android.tools.r8.graph.DexValue.DexValueNull;
 import com.android.tools.r8.graph.FieldAccessFlags;
-import com.android.tools.r8.graph.GenericSignature.ClassSignature;
 import com.android.tools.r8.graph.GenericSignature.FieldTypeSignature;
 import com.android.tools.r8.graph.GenericSignature.MethodTypeSignature;
 import com.android.tools.r8.graph.MethodAccessFlags;
@@ -37,18 +34,11 @@
 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.origin.SynthesizedOrigin;
+import com.android.tools.r8.synthesis.SyntheticProgramClassBuilder;
 import com.android.tools.r8.utils.OptionalBool;
-import com.google.common.base.Suppliers;
-import com.google.common.primitives.Longs;
-import java.nio.ByteBuffer;
 import java.util.ArrayList;
-import java.util.Collection;
 import java.util.Collections;
 import java.util.List;
-import java.util.concurrent.atomic.AtomicBoolean;
-import java.util.function.Supplier;
-import java.util.zip.CRC32;
 
 /**
  * Represents lambda class generated for a lambda descriptor in context of lambda instantiation
@@ -77,31 +67,27 @@
   final DexMethod classConstructor;
   public final DexField lambdaField;
   public final Target target;
-  public final AtomicBoolean addToMainDexList = new AtomicBoolean(false);
-  private final Collection<DexProgramClass> synthesizedFrom = new ArrayList<>(1);
-  private final Supplier<DexProgramClass> lazyDexClass =
-      Suppliers.memoize(this::synthesizeLambdaClass); // NOTE: thread-safe.
+
+  // Considered final but is set after due to circularity in allocation.
+  private DexProgramClass clazz = null;
 
   LambdaClass(
+      SyntheticProgramClassBuilder builder,
       AppView<?> appView,
       LambdaRewriter rewriter,
       ProgramMethod accessedFrom,
-      DexType lambdaClassType,
       LambdaDescriptor descriptor) {
     assert rewriter != null;
-    assert lambdaClassType != null;
     assert descriptor != null;
-
+    this.type = builder.getType();
     this.appView = appView;
     this.rewriter = rewriter;
-    this.type = lambdaClassType;
     this.descriptor = descriptor;
 
-    DexItemFactory factory = appView.dexItemFactory();
+    DexItemFactory factory = builder.getFactory();
     DexProto constructorProto = factory.createProto(
         factory.voidType, descriptor.captures.values);
-    this.constructor =
-        factory.createMethod(lambdaClassType, constructorProto, factory.constructorMethodName);
+    this.constructor = factory.createMethod(type, constructorProto, factory.constructorMethodName);
 
     this.target = createTarget(accessedFrom);
 
@@ -109,98 +95,32 @@
     this.classConstructor =
         !stateless
             ? null
-            : factory.createMethod(
-                lambdaClassType, constructorProto, factory.classConstructorMethodName);
+            : factory.createMethod(type, constructorProto, factory.classConstructorMethodName);
     this.lambdaField =
-        !stateless
-            ? null
-            : factory.createField(lambdaClassType, lambdaClassType, rewriter.instanceFieldName);
+        !stateless ? null : factory.createField(type, type, rewriter.instanceFieldName);
+
+    // Synthesize the program class one all fields are set.
+    synthesizeLambdaClass(builder);
   }
 
-  // Generate unique lambda class type for lambda descriptor and instantiation point context.
-  public static DexType createLambdaClassType(
-      AppView<?> appView, ProgramMethod accessedFrom, LambdaDescriptor match) {
-    StringBuilder lambdaClassDescriptor = new StringBuilder("L");
-
-    // We always create lambda class in the same package where it is referenced.
-    String packageDescriptor = accessedFrom.getHolderType().getPackageDescriptor();
-    if (!packageDescriptor.isEmpty()) {
-      lambdaClassDescriptor.append(packageDescriptor).append('/');
-    }
-
-    // Lambda class name prefix
-    lambdaClassDescriptor.append(LambdaRewriter.LAMBDA_CLASS_NAME_PREFIX);
-
-    // If the lambda class should match 1:1 the class it is accessed from, we
-    // just add the name of this type to make lambda class name unique.
-    // It also helps link the class lambda originated from in some cases.
-    if (match.delegatesToLambdaImplMethod() || match.needsAccessor(accessedFrom)) {
-      lambdaClassDescriptor.append(accessedFrom.getHolderType().getName()).append('$');
-    }
-
-    // Add unique lambda descriptor id
-    lambdaClassDescriptor.append(match.uniqueId).append(';');
-    return appView.dexItemFactory().createType(lambdaClassDescriptor.toString());
-  }
-
-  public final DexProgramClass getOrCreateLambdaClass() {
-    return lazyDexClass.get();
-  }
-
-  private DexProgramClass synthesizeLambdaClass() {
-    DexMethod mainMethod =
-        appView.dexItemFactory().createMethod(type, descriptor.erasedProto, descriptor.name);
-
-    DexProgramClass clazz =
-        new DexProgramClass(
-            type,
-            null,
-            new SynthesizedOrigin("lambda desugaring", getClass()),
-            // Make the synthesized class public, as it might end up being accessed from a different
-            // classloader (package private access is not allowed across classloaders, b/72538146).
-            ClassAccessFlags.fromDexAccessFlags(
-                Constants.ACC_FINAL | Constants.ACC_SYNTHETIC | Constants.ACC_PUBLIC),
-            appView.dexItemFactory().objectType,
-            buildInterfaces(),
-            appView.dexItemFactory().createString("lambda"),
-            null,
-            Collections.emptyList(),
-            null,
-            Collections.emptyList(),
-            ClassSignature.noSignature(),
-            DexAnnotationSet.empty(),
-            synthesizeStaticFields(),
-            synthesizeInstanceFields(),
-            synthesizeDirectMethods(),
-            synthesizeVirtualMethods(mainMethod),
-            appView.dexItemFactory().getSkipNameValidationForTesting(),
-            LambdaClass::computeChecksumForSynthesizedClass);
-    appView.appInfo().addSynthesizedClass(clazz, false);
-
-    // The method addSynthesizedFrom() may be called concurrently. To avoid a Concurrent-
-    // ModificationException we must use synchronization.
-    synchronized (synthesizedFrom) {
-      synthesizedFrom.forEach(clazz::addSynthesizedFrom);
-    }
+  public final DexProgramClass getLambdaProgramClass() {
+    assert clazz != null;
     return clazz;
   }
 
-  private static long computeChecksumForSynthesizedClass(DexProgramClass clazz) {
-    // Checksum of synthesized classes are compute based off the depending input. This might
-    // create false positives (ie: unchanged lambda class detected as changed even thought only
-    // an unrelated part from a synthesizedFrom class is changed).
+  void setClass(DexProgramClass clazz) {
+    assert this.clazz == null;
+    assert clazz != null;
+    assert type == clazz.type;
+    this.clazz = clazz;
+  }
 
-    // Ideally, we should use some hashcode of the dex program class that is deterministic across
-    // compiles.
-    Collection<DexProgramClass> synthesizedFrom = clazz.getSynthesizedFrom();
-    ByteBuffer buffer = ByteBuffer.allocate(synthesizedFrom.size() * Longs.BYTES);
-    for (DexProgramClass from : synthesizedFrom) {
-      buffer.putLong(from.getChecksum());
-    }
-    CRC32 crc = new CRC32();
-    byte[] array = buffer.array();
-    crc.update(array, 0, array.length);
-    return crc.getValue();
+  private void synthesizeLambdaClass(SyntheticProgramClassBuilder builder) {
+    builder.setInterfaces(descriptor.interfaces);
+    synthesizeStaticFields(builder);
+    synthesizeInstanceFields(builder);
+    synthesizeDirectMethods(builder);
+    synthesizeVirtualMethods(builder);
   }
 
   final DexField getCaptureField(int index) {
@@ -216,24 +136,15 @@
     return descriptor.isStateless();
   }
 
-  void addSynthesizedFrom(DexProgramClass clazz) {
-    assert clazz != null;
-    synchronized (synthesizedFrom) {
-      if (synthesizedFrom.add(clazz)) {
-        // The lambda class may already have been synthesized, and we therefore need to update the
-        // synthesized lambda class as well.
-        getOrCreateLambdaClass().addSynthesizedFrom(clazz);
-      }
-    }
-  }
-
   // Synthesize virtual methods.
-  private DexEncodedMethod[] synthesizeVirtualMethods(DexMethod mainMethod) {
-    DexEncodedMethod[] methods = new DexEncodedMethod[1 + descriptor.bridges.size()];
-    int index = 0;
+  private void synthesizeVirtualMethods(SyntheticProgramClassBuilder builder) {
+    DexMethod mainMethod =
+        appView.dexItemFactory().createMethod(type, descriptor.erasedProto, descriptor.name);
+
+    List<DexEncodedMethod> methods = new ArrayList<>(1 + descriptor.bridges.size());
 
     // Synthesize main method.
-    methods[index++] =
+    methods.add(
         new DexEncodedMethod(
             mainMethod,
             MethodAccessFlags.fromSharedAccessFlags(
@@ -242,13 +153,13 @@
             DexAnnotationSet.empty(),
             ParameterAnnotationsList.empty(),
             LambdaMainMethodSourceCode.build(this, mainMethod),
-            true);
+            true));
 
     // Synthesize bridge methods.
     for (DexProto bridgeProto : descriptor.bridges) {
       DexMethod bridgeMethod =
           appView.dexItemFactory().createMethod(type, bridgeProto, descriptor.name);
-      methods[index++] =
+      methods.add(
           new DexEncodedMethod(
               bridgeMethod,
               MethodAccessFlags.fromSharedAccessFlags(
@@ -261,18 +172,18 @@
               DexAnnotationSet.empty(),
               ParameterAnnotationsList.empty(),
               LambdaBridgeMethodSourceCode.build(this, bridgeMethod, mainMethod),
-              true);
+              true));
     }
-    return methods;
+    builder.setVirtualMethods(methods);
   }
 
   // Synthesize direct methods.
-  private DexEncodedMethod[] synthesizeDirectMethods() {
+  private void synthesizeDirectMethods(SyntheticProgramClassBuilder builder) {
     boolean stateless = isStateless();
-    DexEncodedMethod[] methods = new DexEncodedMethod[stateless ? 2 : 1];
+    List<DexEncodedMethod> methods = new ArrayList<>(stateless ? 2 : 1);
 
     // Constructor.
-    methods[0] =
+    methods.add(
         new DexEncodedMethod(
             constructor,
             MethodAccessFlags.fromSharedAccessFlags(
@@ -283,11 +194,11 @@
             DexAnnotationSet.empty(),
             ParameterAnnotationsList.empty(),
             LambdaConstructorSourceCode.build(this),
-            true);
+            true));
 
     // Class constructor for stateless lambda classes.
     if (stateless) {
-      methods[1] =
+      methods.add(
           new DexEncodedMethod(
               classConstructor,
               MethodAccessFlags.fromSharedAccessFlags(
@@ -296,61 +207,50 @@
               DexAnnotationSet.empty(),
               ParameterAnnotationsList.empty(),
               LambdaClassConstructorSourceCode.build(this),
-              true);
-      feedback.classInitializerMayBePostponed(methods[1]);
+              true));
+      feedback.classInitializerMayBePostponed(methods.get(1));
     }
-    return methods;
+    builder.setDirectMethods(methods);
   }
 
   // Synthesize instance fields to represent captured values.
-  private DexEncodedField[] synthesizeInstanceFields() {
+  private void synthesizeInstanceFields(SyntheticProgramClassBuilder builder) {
     DexType[] fieldTypes = descriptor.captures.values;
     int fieldCount = fieldTypes.length;
-    DexEncodedField[] fields = new DexEncodedField[fieldCount];
+    List<DexEncodedField> fields = new ArrayList<>(fieldCount);
     for (int i = 0; i < fieldCount; i++) {
       FieldAccessFlags accessFlags =
           FieldAccessFlags.fromSharedAccessFlags(
               Constants.ACC_FINAL | Constants.ACC_SYNTHETIC | Constants.ACC_PUBLIC);
-      fields[i] =
+      fields.add(
           new DexEncodedField(
               getCaptureField(i),
               accessFlags,
               FieldTypeSignature.noSignature(),
               DexAnnotationSet.empty(),
-              null);
+              null));
     }
-    return fields;
+    builder.setInstanceFields(fields);
   }
 
   // Synthesize static fields to represent singleton instance.
-  private DexEncodedField[] synthesizeStaticFields() {
-    if (!isStateless()) {
-      return DexEncodedField.EMPTY_ARRAY;
+  private void synthesizeStaticFields(SyntheticProgramClassBuilder builder) {
+    if (isStateless()) {
+      // Create instance field for stateless lambda.
+      assert this.lambdaField != null;
+      builder.setStaticFields(
+          Collections.singletonList(
+              new DexEncodedField(
+                  this.lambdaField,
+                  FieldAccessFlags.fromSharedAccessFlags(
+                      Constants.ACC_PUBLIC
+                          | Constants.ACC_FINAL
+                          | Constants.ACC_SYNTHETIC
+                          | Constants.ACC_STATIC),
+                  FieldTypeSignature.noSignature(),
+                  DexAnnotationSet.empty(),
+                  DexValueNull.NULL)));
     }
-
-    // Create instance field for stateless lambda.
-    assert this.lambdaField != null;
-    DexEncodedField[] fields = new DexEncodedField[1];
-    fields[0] =
-        new DexEncodedField(
-            this.lambdaField,
-            FieldAccessFlags.fromSharedAccessFlags(
-                Constants.ACC_PUBLIC
-                    | Constants.ACC_FINAL
-                    | Constants.ACC_SYNTHETIC
-                    | Constants.ACC_STATIC),
-            FieldTypeSignature.noSignature(),
-            DexAnnotationSet.empty(),
-            DexValueNull.NULL);
-    return fields;
-  }
-
-  // Build a list of implemented interfaces.
-  private DexTypeList buildInterfaces() {
-    List<DexType> interfaces = descriptor.interfaces;
-    return interfaces.isEmpty()
-        ? DexTypeList.empty()
-        : new DexTypeList(interfaces.toArray(DexType.EMPTY_ARRAY));
   }
 
   // Creates a delegation target for this particular lambda class. Note that we
@@ -643,11 +543,15 @@
                         newMethod.getCode(), callTarget.getArity(), appView);
                     return newMethod;
                   });
-
-      assert replacement != null
-          : "Unexpected failure to find direct lambda target for: " + implMethod.qualifiedName();
-
-      return new ProgramMethod(implMethodHolder, replacement);
+      if (replacement != null) {
+        return new ProgramMethod(implMethodHolder, replacement);
+      }
+      // The method might already have been moved by another invoke-dynamic targeting it.
+      // If so, it must be defined on the holder.
+      ProgramMethod modified = implMethodHolder.lookupProgramMethod(callTarget);
+      assert modified != null;
+      assert modified.getDefinition().isNonPrivateVirtualMethod();
+      return modified;
     }
   }
 
@@ -713,11 +617,27 @@
                     rewriter.forcefullyMoveMethod(encodedMethod.method, callTarget);
                     return newMethod;
                   });
-      return new ProgramMethod(implMethodHolder, replacement);
+      if (replacement != null) {
+        return new ProgramMethod(implMethodHolder, replacement);
+      }
+      // The method might already have been moved by another invoke-dynamic targeting it.
+      // If so, it must be defined on the holder.
+      ProgramMethod modified = implMethodHolder.lookupProgramMethod(callTarget);
+      assert modified != null;
+      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);
@@ -762,6 +682,15 @@
       DexProgramClass accessorClass = appView.definitionForProgramType(callTarget.holder);
       assert accessorClass != null;
 
+      // The accessor might already have been created by another invoke-dynamic targeting it.
+      ProgramMethod existing = accessorClass.lookupProgramMethod(callTarget);
+      if (existing != null) {
+        assert existing.getAccessFlags().isSynthetic();
+        assert existing.getAccessFlags().isPublic();
+        assert existing.getAccessFlags().isStatic();
+        return existing;
+      }
+
       // 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 =
@@ -779,11 +708,7 @@
               AccessorMethodSourceCode.build(LambdaClass.this, callTarget),
               true);
 
-      // We may arrive here concurrently so we need must update the methods of the class atomically.
-      synchronized (accessorClass) {
-        accessorClass.addDirectMethod(accessorEncodedMethod);
-      }
-
+      accessorClass.addDirectMethod(accessorEncodedMethod);
       return new ProgramMethod(accessorClass, accessorEncodedMethod);
     }
   }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/LambdaRewriter.java b/src/main/java/com/android/tools/r8/ir/desugar/LambdaRewriter.java
index f464fdd..165cb52 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/LambdaRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/LambdaRewriter.java
@@ -16,7 +16,6 @@
 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.DexApplication.Builder;
 import com.android.tools.r8.graph.DexCallSite;
 import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.graph.DexItemFactory;
@@ -28,32 +27,25 @@
 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.analysis.type.Nullability;
-import com.android.tools.r8.ir.analysis.type.TypeElement;
-import com.android.tools.r8.ir.code.BasicBlock;
-import com.android.tools.r8.ir.code.IRCode;
-import com.android.tools.r8.ir.code.InstructionListIterator;
-import com.android.tools.r8.ir.code.InvokeCustom;
-import com.android.tools.r8.ir.code.InvokeDirect;
-import com.android.tools.r8.ir.code.NewInstance;
-import com.android.tools.r8.ir.code.StaticGet;
-import com.android.tools.r8.ir.code.Value;
 import com.android.tools.r8.ir.code.ValueType;
 import com.android.tools.r8.ir.conversion.IRConverter;
-import com.android.tools.r8.utils.DescriptorUtils;
+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 com.google.common.hash.Hasher;
+import com.google.common.hash.Hashing;
 import java.util.ArrayList;
 import java.util.Collection;
-import java.util.IdentityHashMap;
+import java.util.Collections;
 import java.util.List;
-import java.util.ListIterator;
 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;
@@ -69,7 +61,6 @@
 public class LambdaRewriter {
 
   // Public for testing.
-  public static final String LAMBDA_CLASS_NAME_PREFIX = "-$$Lambda$";
   public static final String LAMBDA_GROUP_CLASS_NAME_PREFIX = "-$$LambdaGroup$";
   static final String EXPECTED_LAMBDA_METHOD_PREFIX = "lambda$";
   public static final String LAMBDA_INSTANCE_FIELD_NAME = "INSTANCE";
@@ -81,16 +72,11 @@
   private final LambdaRewriterLens.Builder lensBuilder = LambdaRewriterLens.builder();
   private final Set<DexMethod> forcefullyMovedMethods = Sets.newIdentityHashSet();
 
-  // Maps call sites seen so far to inferred lambda descriptor. It is intended
-  // to help avoid re-matching call sites we already seen. Note that same call
-  // site may match one or several lambda classes.
-  //
-  // NOTE: synchronize concurrent access on `knownCallSites`.
-  private final Map<DexCallSite, LambdaDescriptor> knownCallSites = new IdentityHashMap<>();
-  // Maps lambda class type into lambda class representation. Since lambda class
-  // type uniquely defines lambda class, effectively canonicalizes lambda classes.
+  // Maps lambda class type into lambda class representation.
   // NOTE: synchronize concurrent access on `knownLambdaClasses`.
-  private final Map<DexType, LambdaClass> knownLambdaClasses = new IdentityHashMap<>();
+  private final List<LambdaClass> knownLambdaClasses = new ArrayList<>();
+
+  private final Map<DexMethod, Integer> methodIds = new ConcurrentHashMap<>();
 
   public LambdaRewriter(AppView<?> appView) {
     this.appView = appView;
@@ -138,7 +124,7 @@
           if (descriptor == null) {
             return null;
           }
-          return getOrCreateLambdaClass(descriptor, method);
+          return createLambdaClass(descriptor, method);
         });
   }
 
@@ -209,16 +195,10 @@
   }
 
   /** Generates lambda classes and adds them to the builder. */
-  public void finalizeLambdaDesugaringForD8(
-      Builder<?> builder, IRConverter converter, ExecutorService executorService)
+  public void finalizeLambdaDesugaringForD8(IRConverter converter, ExecutorService executorService)
       throws ExecutionException {
     synthesizeAccessibilityBridgesForLambdaClassesD8(
-        knownLambdaClasses.values(), converter, executorService);
-    for (LambdaClass lambdaClass : knownLambdaClasses.values()) {
-      DexProgramClass synthesizedClass = lambdaClass.getOrCreateLambdaClass();
-      appView.appInfo().addSynthesizedClass(synthesizedClass, lambdaClass.addToMainDexList.get());
-      builder.addSynthesizedClass(synthesizedClass);
-    }
+        knownLambdaClasses, converter, executorService);
     fixup();
     optimizeSynthesizedClasses(converter, executorService);
   }
@@ -226,183 +206,44 @@
   private void optimizeSynthesizedClasses(IRConverter converter, ExecutorService executorService)
       throws ExecutionException {
     converter.optimizeSynthesizedClasses(
-        knownLambdaClasses.values().stream()
-            .map(LambdaClass::getOrCreateLambdaClass)
+        knownLambdaClasses.stream()
+            .map(LambdaClass::getLambdaProgramClass)
             .collect(ImmutableSet.toImmutableSet()),
         executorService);
   }
 
-  // Matches invoke-custom instruction operands to infer lambda descriptor
-  // corresponding to this lambda invocation point.
-  //
-  // Returns the lambda descriptor or `MATCH_FAILED`.
-  private LambdaDescriptor inferLambdaDescriptor(DexCallSite callSite, ProgramMethod context) {
-    // We check the map before and after inferring lambda descriptor to minimize time
-    // spent in synchronized block. As a result we may throw away calculated descriptor
-    // in rare case when another thread has same call site processed concurrently,
-    // but this is a low price to pay comparing to making whole method synchronous.
-    LambdaDescriptor descriptor = getKnown(knownCallSites, callSite);
-    return descriptor != null
-        ? descriptor
-        : putIfAbsent(
-            knownCallSites,
-            callSite,
-            LambdaDescriptor.infer(callSite, appView.appInfoForDesugaring(), context));
-  }
-
-  // Returns a lambda class corresponding to the lambda descriptor and context,
-  // creates the class if it does not yet exist.
-  public LambdaClass getOrCreateLambdaClass(
-      LambdaDescriptor descriptor, ProgramMethod accessedFrom) {
-    DexType lambdaClassType = LambdaClass.createLambdaClassType(appView, accessedFrom, descriptor);
-    // We check the map twice to to minimize time spent in synchronized block.
-    LambdaClass lambdaClass = getKnown(knownLambdaClasses, lambdaClassType);
-    if (lambdaClass == null) {
-      lambdaClass =
-          putIfAbsent(
-              knownLambdaClasses,
-              lambdaClassType,
-              new LambdaClass(appView, this, accessedFrom, lambdaClassType, descriptor));
-      if (appView.options().isDesugaredLibraryCompilation()) {
-        DexType rewrittenType =
-            appView.rewritePrefix.rewrittenType(accessedFrom.getHolderType(), appView);
-        if (rewrittenType == null) {
-          rewrittenType =
-              appView
-                  .options()
-                  .desugaredLibraryConfiguration
-                  .getEmulateLibraryInterface()
-                  .get(accessedFrom.getHolderType());
-        }
-        if (rewrittenType != null) {
-          addRewritingPrefix(accessedFrom, rewrittenType, lambdaClassType);
-        }
-      }
-    }
-    lambdaClass.addSynthesizedFrom(accessedFrom.getHolder());
-    if (appView.appInfo().getMainDexClasses().contains(accessedFrom.getHolder())) {
-      lambdaClass.addToMainDexList.set(true);
+  // Creates a lambda class corresponding to the lambda descriptor and context.
+  public LambdaClass createLambdaClass(LambdaDescriptor descriptor, ProgramMethod accessedFrom) {
+    int nextId =
+        methodIds.compute(
+            accessedFrom.getReference(), (method, value) -> value == null ? 0 : value + 1);
+    Box<LambdaClass> box = new Box<>();
+    DexProgramClass clazz =
+        appView
+            .getSyntheticItems()
+            .createClass(
+                SyntheticNaming.SyntheticKind.LAMBDA,
+                accessedFrom.getHolder(),
+                appView.dexItemFactory(),
+                // TODO(b/172194101): Make this part of a unique context construction.
+                () -> {
+                  Hasher hasher = Hashing.sha256().newHasher();
+                  accessedFrom.getReference().hash(hasher);
+                  return "$" + hasher.hash().toString() + "$" + nextId;
+                },
+                builder ->
+                    box.set(new LambdaClass(builder, appView, this, accessedFrom, descriptor)));
+    // Immediately set the actual program class on the lambda.
+    LambdaClass lambdaClass = box.get();
+    lambdaClass.setClass(clazz);
+    synchronized (knownLambdaClasses) {
+      knownLambdaClasses.add(lambdaClass);
     }
     return lambdaClass;
   }
 
-  private LambdaClass getKnownLambdaClass(LambdaDescriptor descriptor, ProgramMethod accessedFrom) {
-    DexType lambdaClassType = LambdaClass.createLambdaClassType(appView, accessedFrom, descriptor);
-    return getKnown(knownLambdaClasses, lambdaClassType);
-  }
-
-  private void addRewritingPrefix(
-      ProgramMethod context, DexType rewritten, DexType lambdaClassType) {
-    String javaName = lambdaClassType.toString();
-    String typeString = context.getHolderType().toString();
-    String actualPrefix = typeString.substring(0, typeString.lastIndexOf('.'));
-    String rewrittenString = rewritten.toString();
-    String actualRewrittenPrefix = rewrittenString.substring(0, rewrittenString.lastIndexOf('.'));
-    assert javaName.startsWith(actualPrefix);
-    appView.rewritePrefix.rewriteType(
-        lambdaClassType,
-        appView
-            .dexItemFactory()
-            .createType(
-                DescriptorUtils.javaTypeToDescriptor(
-                    actualRewrittenPrefix + javaName.substring(actualPrefix.length()))));
-  }
-
-  private static <K, V> V getKnown(Map<K, V> map, K key) {
-    synchronized (map) {
-      return map.get(key);
-    }
-  }
-
-  private static <K, V> V putIfAbsent(Map<K, V> map, K key, V value) {
-    synchronized (map) {
-      V known = map.get(key);
-      if (known != null) {
-        return known;
-      }
-      map.put(key, value);
-      return value;
-    }
-  }
-
-  // Patches invoke-custom instruction to create or get an instance
-  // of the generated lambda class.
-  private void patchInstruction(
-      InvokeCustom invoke,
-      LambdaClass lambdaClass,
-      IRCode code,
-      ListIterator<BasicBlock> blocks,
-      InstructionListIterator instructions,
-      Set<Value> affectedValues) {
-    assert lambdaClass != null;
-    assert instructions != null;
-
-    // The value representing new lambda instance: we reuse the
-    // value from the original invoke-custom instruction, and thus
-    // all its usages.
-    Value lambdaInstanceValue = invoke.outValue();
-    if (lambdaInstanceValue == null) {
-      // The out value might be empty in case it was optimized out.
-      lambdaInstanceValue =
-          code.createValue(
-              TypeElement.fromDexType(lambdaClass.type, Nullability.maybeNull(), appView));
-    } else {
-      affectedValues.add(lambdaInstanceValue);
-    }
-
-    // For stateless lambdas we replace InvokeCustom instruction with StaticGet
-    // reading the value of INSTANCE field created for singleton lambda class.
-    if (lambdaClass.isStateless()) {
-      instructions.replaceCurrentInstruction(
-          new StaticGet(lambdaInstanceValue, lambdaClass.lambdaField));
-      // Note that since we replace one throwing operation with another we don't need
-      // to have any special handling for catch handlers.
-      return;
-    }
-
-    // For stateful lambdas we always create a new instance since we need to pass
-    // captured values to the constructor.
-    //
-    // We replace InvokeCustom instruction with a new NewInstance instruction
-    // instantiating lambda followed by InvokeDirect instruction calling a
-    // constructor on it.
-    //
-    //    original:
-    //      Invoke-Custom rResult <- { rArg0, rArg1, ... }; call site: ...
-    //
-    //    result:
-    //      NewInstance   rResult <-  LambdaClass
-    //      Invoke-Direct { rResult, rArg0, rArg1, ... }; method: void LambdaClass.<init>(...)
-    lambdaInstanceValue.setType(
-        lambdaInstanceValue.getType().asReferenceType().asDefinitelyNotNull());
-    NewInstance newInstance = new NewInstance(lambdaClass.type, lambdaInstanceValue);
-    instructions.replaceCurrentInstruction(newInstance);
-
-    List<Value> arguments = new ArrayList<>();
-    arguments.add(lambdaInstanceValue);
-    arguments.addAll(invoke.arguments()); // Optional captures.
-    InvokeDirect constructorCall =
-        new InvokeDirect(lambdaClass.constructor, null /* no return value */, arguments);
-    instructions.add(constructorCall);
-    constructorCall.setPosition(newInstance.getPosition());
-
-    // If we don't have catch handlers we are done.
-    if (!constructorCall.getBlock().hasCatchHandlers()) {
-      return;
-    }
-
-    // Move the iterator back to position it between the two instructions, split
-    // the block between the two instructions, and copy the catch handlers.
-    instructions.previous();
-    assert instructions.peekNext().isInvokeDirect();
-    BasicBlock currentBlock = newInstance.getBlock();
-    BasicBlock nextBlock = instructions.split(code, blocks);
-    assert !instructions.hasNext();
-    nextBlock.copyCatchHandlers(code, blocks, currentBlock, appView.options());
-  }
-
-  public Map<DexType, LambdaClass> getKnownLambdaClasses() {
-    return knownLambdaClasses;
+  public Collection<LambdaClass> getKnownLambdaClasses() {
+    return Collections.unmodifiableList(knownLambdaClasses);
   }
 
   public NestedGraphLens fixup() {
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/NestBasedAccessDesugaring.java b/src/main/java/com/android/tools/r8/ir/desugar/NestBasedAccessDesugaring.java
deleted file mode 100644
index eb42231..0000000
--- a/src/main/java/com/android/tools/r8/ir/desugar/NestBasedAccessDesugaring.java
+++ /dev/null
@@ -1,529 +0,0 @@
-// Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
-
-package com.android.tools.r8.ir.desugar;
-
-
-import com.android.tools.r8.dex.Constants;
-import com.android.tools.r8.errors.Unreachable;
-import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.ClassAccessFlags;
-import com.android.tools.r8.graph.ClasspathMethod;
-import com.android.tools.r8.graph.DexAnnotationSet;
-import com.android.tools.r8.graph.DexApplication;
-import com.android.tools.r8.graph.DexClass;
-import com.android.tools.r8.graph.DexClassAndMethod;
-import com.android.tools.r8.graph.DexEncodedField;
-import com.android.tools.r8.graph.DexEncodedMethod;
-import com.android.tools.r8.graph.DexField;
-import com.android.tools.r8.graph.DexMethod;
-import com.android.tools.r8.graph.DexProgramClass;
-import com.android.tools.r8.graph.DexProto;
-import com.android.tools.r8.graph.DexString;
-import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.graph.DexTypeList;
-import com.android.tools.r8.graph.GenericSignature.ClassSignature;
-import com.android.tools.r8.graph.NestMemberClassAttribute;
-import com.android.tools.r8.graph.ProgramMethod;
-import com.android.tools.r8.graph.UseRegistry;
-import com.android.tools.r8.ir.code.Invoke;
-import com.android.tools.r8.origin.SynthesizedOrigin;
-import com.android.tools.r8.utils.BooleanUtils;
-import com.android.tools.r8.utils.Pair;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-import java.util.Map;
-import java.util.Objects;
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Future;
-
-// NestBasedAccessDesugaring contains common code between the two subclasses
-// which are specialized for d8 and r8
-public abstract class NestBasedAccessDesugaring {
-
-  // Short names to avoid creating long strings
-  public static final String NEST_ACCESS_NAME_PREFIX = "-$$Nest$";
-  private static final String NEST_ACCESS_METHOD_NAME_PREFIX = NEST_ACCESS_NAME_PREFIX + "m";
-  private static final String NEST_ACCESS_STATIC_METHOD_NAME_PREFIX =
-      NEST_ACCESS_NAME_PREFIX + "sm";
-  private static final String NEST_ACCESS_FIELD_GET_NAME_PREFIX = NEST_ACCESS_NAME_PREFIX + "fget";
-  private static final String NEST_ACCESS_STATIC_GET_FIELD_NAME_PREFIX =
-      NEST_ACCESS_NAME_PREFIX + "sfget";
-  private static final String NEST_ACCESS_FIELD_PUT_NAME_PREFIX = NEST_ACCESS_NAME_PREFIX + "fput";
-  private static final String NEST_ACCESS_STATIC_PUT_FIELD_NAME_PREFIX =
-      NEST_ACCESS_NAME_PREFIX + "sfput";
-  public static final String NEST_CONSTRUCTOR_NAME = NEST_ACCESS_NAME_PREFIX + "Constructor";
-
-  protected final AppView<?> appView;
-  // Following maps are there to avoid creating the bridges multiple times
-  // and remember the bridges to add once the nests are processed.
-  final Map<DexMethod, ProgramMethod> bridges = new ConcurrentHashMap<>();
-  final Map<DexField, ProgramMethod> getFieldBridges = new ConcurrentHashMap<>();
-  final Map<DexField, ProgramMethod> putFieldBridges = new ConcurrentHashMap<>();
-  // Common single empty class for nest based private constructors
-  private final DexProgramClass nestConstructor;
-  private boolean nestConstructorUsed = false;
-
-  NestBasedAccessDesugaring(AppView<?> appView) {
-    this.appView = appView;
-    this.nestConstructor = createNestAccessConstructor();
-  }
-
-  DexType getNestConstructorType() {
-    assert nestConstructor != null;
-    return nestConstructor.type;
-  }
-
-  abstract void reportMissingNestHost(DexClass clazz);
-
-  abstract void reportIncompleteNest(List<DexType> nest);
-
-  DexClass definitionFor(DexType type) {
-    return appView.definitionFor(appView.graphLens().lookupType(type));
-  }
-
-  private DexEncodedMethod lookupOnHolder(
-      DexMethod method, DexClassAndMethod context, Invoke.Type invokeType) {
-    DexMethod rewritten =
-        appView.graphLens().lookupMethod(method, context.getReference(), invokeType).getReference();
-    return rewritten.lookupOnClass(appView.definitionForHolder(rewritten));
-  }
-
-  private DexEncodedField lookupOnHolder(DexField field) {
-    DexField rewritten = appView.graphLens().lookupField(field);
-    return rewritten.lookupOnClass(appView.definitionForHolder(rewritten));
-  }
-
-  // Extract the list of types in the programClass' nest, of host hostClass
-  private Pair<DexClass, List<DexType>> extractNest(DexClass clazz) {
-    assert clazz != null;
-    DexClass hostClass = clazz.isNestHost() ? clazz : definitionFor(clazz.getNestHost());
-    if (hostClass == null) {
-      reportMissingNestHost(clazz);
-      // Missing nest host means the class is considered as not being part of a nest.
-      clazz.clearNestHost();
-      return null;
-    }
-    List<DexType> classesInNest =
-        new ArrayList<>(hostClass.getNestMembersClassAttributes().size() + 1);
-    for (NestMemberClassAttribute nestmate : hostClass.getNestMembersClassAttributes()) {
-      classesInNest.add(nestmate.getNestMember());
-    }
-    classesInNest.add(hostClass.type);
-    return new Pair<>(hostClass, classesInNest);
-  }
-
-  Future<?> asyncProcessNest(DexProgramClass clazz, ExecutorService executorService) {
-    return executorService.submit(
-        () -> {
-          Pair<DexClass, List<DexType>> nest = extractNest(clazz);
-          // Nest is null when nest host is missing, we do nothing in this case.
-          if (nest != null) {
-            processNest(nest.getFirst(), nest.getSecond());
-          }
-          return null; // we want a Callable not a Runnable to be able to throw
-        });
-  }
-
-  private void processNest(DexClass host, List<DexType> nest) {
-    boolean reported = false;
-    for (DexType type : nest) {
-      DexClass clazz = definitionFor(type);
-      if (clazz == null) {
-        if (!reported) {
-          reportIncompleteNest(nest);
-          reported = true;
-        }
-      } else {
-        reportDesugarDependencies(host, clazz);
-        if (shouldProcessClassInNest(clazz, nest)) {
-          for (DexEncodedMethod definition : clazz.methods()) {
-            if (clazz.isProgramClass()) {
-              ProgramMethod method = new ProgramMethod(clazz.asProgramClass(), definition);
-              method.registerCodeReferences(new NestBasedAccessDesugaringUseRegistry(method));
-            } else if (clazz.isClasspathClass()) {
-              ClasspathMethod method = new ClasspathMethod(clazz.asClasspathClass(), definition);
-              method.registerCodeReferencesForDesugaring(
-                  new NestBasedAccessDesugaringUseRegistry(method));
-            } else {
-              assert false;
-            }
-          }
-        }
-      }
-    }
-  }
-
-  private void reportDesugarDependencies(DexClass host, DexClass clazz) {
-    if (host == clazz) {
-      return;
-    }
-    if (host.isProgramClass()) {
-      InterfaceMethodRewriter.reportDependencyEdge(host.asProgramClass(), clazz, appView.options());
-    }
-    if (clazz.isProgramClass()) {
-      InterfaceMethodRewriter.reportDependencyEdge(clazz.asProgramClass(), host, appView.options());
-    }
-  }
-
-  protected abstract boolean shouldProcessClassInNest(DexClass clazz, List<DexType> nest);
-
-  private DexProgramClass createNestAccessConstructor() {
-    // TODO(b/176900254): The class generated should be in a package that has lower change of
-    //   collisions (and perhaps be unique by some hash from the nest).
-    return new DexProgramClass(
-        appView.dexItemFactory().nestConstructorType,
-        null,
-        new SynthesizedOrigin("Nest based access desugaring", getClass()),
-        // Make the synthesized class public since shared in the whole program.
-        ClassAccessFlags.fromDexAccessFlags(
-            Constants.ACC_FINAL | Constants.ACC_SYNTHETIC | Constants.ACC_PUBLIC),
-        appView.dexItemFactory().objectType,
-        DexTypeList.empty(),
-        appView.dexItemFactory().createString("nest"),
-        null,
-        Collections.emptyList(),
-        null,
-        Collections.emptyList(),
-        ClassSignature.noSignature(),
-        DexAnnotationSet.empty(),
-        DexEncodedField.EMPTY_ARRAY,
-        DexEncodedField.EMPTY_ARRAY,
-        DexEncodedMethod.EMPTY_ARRAY,
-        DexEncodedMethod.EMPTY_ARRAY,
-        appView.dexItemFactory().getSkipNameValidationForTesting(),
-        DexProgramClass::checksumFromType);
-  }
-
-  void synthesizeNestConstructor(DexApplication.Builder<?> builder) {
-    if (nestConstructorUsed) {
-      appView.appInfo().addSynthesizedClass(nestConstructor, true);
-      builder.addSynthesizedClass(nestConstructor);
-    }
-  }
-
-  private DexString computeMethodBridgeName(DexEncodedMethod method) {
-    String methodName = method.method.name.toString();
-    String fullName;
-    if (method.isStatic()) {
-      fullName = NEST_ACCESS_STATIC_METHOD_NAME_PREFIX + methodName;
-    } else {
-      fullName = NEST_ACCESS_METHOD_NAME_PREFIX + methodName;
-    }
-    return appView.dexItemFactory().createString(fullName);
-  }
-
-  private DexString computeFieldBridgeName(DexEncodedField field, boolean isGet) {
-    String fieldName = field.field.name.toString();
-    String fullName;
-    if (isGet && !field.isStatic()) {
-      fullName = NEST_ACCESS_FIELD_GET_NAME_PREFIX + fieldName;
-    } else if (isGet) {
-      fullName = NEST_ACCESS_STATIC_GET_FIELD_NAME_PREFIX + fieldName;
-    } else if (!field.isStatic()) {
-      fullName = NEST_ACCESS_FIELD_PUT_NAME_PREFIX + fieldName;
-    } else {
-      fullName = NEST_ACCESS_STATIC_PUT_FIELD_NAME_PREFIX + fieldName;
-    }
-    return appView.dexItemFactory().createString(fullName);
-  }
-
-  private DexMethod computeMethodBridge(DexEncodedMethod encodedMethod) {
-    DexMethod method = encodedMethod.method;
-    DexProto proto =
-        encodedMethod.accessFlags.isStatic()
-            ? method.proto
-            : appView.dexItemFactory().prependHolderToProto(method);
-    return appView
-        .dexItemFactory()
-        .createMethod(method.holder, proto, computeMethodBridgeName(encodedMethod));
-  }
-
-  private DexMethod computeInitializerBridge(DexMethod method) {
-    DexProto newProto =
-        appView.dexItemFactory().appendTypeToProto(method.proto, nestConstructor.type);
-    return appView.dexItemFactory().createMethod(method.holder, newProto, method.name);
-  }
-
-  private DexMethod computeFieldBridge(DexEncodedField field, boolean isGet) {
-    DexType holderType = field.getHolderType();
-    DexType fieldType = field.field.type;
-    int bridgeParameterCount =
-        BooleanUtils.intValue(!field.isStatic()) + BooleanUtils.intValue(!isGet);
-    DexType[] parameters = new DexType[bridgeParameterCount];
-    if (!isGet) {
-      parameters[parameters.length - 1] = fieldType;
-    }
-    if (!field.isStatic()) {
-      parameters[0] = holderType;
-    }
-    DexType returnType = isGet ? fieldType : appView.dexItemFactory().voidType;
-    DexProto proto = appView.dexItemFactory().createProto(returnType, parameters);
-    return appView
-        .dexItemFactory()
-        .createMethod(holderType, proto, computeFieldBridgeName(field, isGet));
-  }
-
-  boolean invokeRequiresRewriting(DexEncodedMethod method, DexClassAndMethod context) {
-    assert method != null;
-    // Rewrite only when targeting other nest members private fields.
-    if (!method.accessFlags.isPrivate() || method.getHolderType() == context.getHolderType()) {
-      return false;
-    }
-    DexClass methodHolder = definitionFor(method.getHolderType());
-    assert methodHolder != null; // from encodedMethod
-    return methodHolder.getNestHost() == context.getHolder().getNestHost();
-  }
-
-  boolean fieldAccessRequiresRewriting(DexEncodedField field, DexClassAndMethod context) {
-    assert field != null;
-    // Rewrite only when targeting other nest members private fields.
-    if (!field.accessFlags.isPrivate() || field.getHolderType() == context.getHolderType()) {
-      return false;
-    }
-    DexClass fieldHolder = definitionFor(field.getHolderType());
-    assert fieldHolder != null; // from encodedField
-    return fieldHolder.getNestHost() == context.getHolder().getNestHost();
-  }
-
-  private boolean holderRequiresBridge(DexClass holder) {
-    // Bridges are added on program classes only.
-    // Bridges on class paths are added in different compilation units.
-    if (holder.isProgramClass()) {
-      return false;
-    } else if (holder.isClasspathClass()) {
-      return true;
-    }
-    assert holder.isLibraryClass();
-    Pair<DexClass, List<DexType>> nest = extractNest(holder);
-    assert nest != null : "Should be a compilation error if missing nest host on library class.";
-    reportIncompleteNest(nest.getSecond());
-    throw new Unreachable(
-        "Incomplete nest due to missing library class should raise a compilation error.");
-  }
-
-  DexMethod ensureFieldAccessBridge(DexEncodedField field, boolean isGet) {
-    DexClass holder = definitionFor(field.getHolderType());
-    assert holder != null;
-    DexMethod bridgeMethod = computeFieldBridge(field, isGet);
-    if (holderRequiresBridge(holder)) {
-      return bridgeMethod;
-    }
-    assert holder.isProgramClass();
-    // The map is used to avoid creating multiple times the bridge
-    // and remembers the bridges to add.
-    Map<DexField, ProgramMethod> fieldMap = isGet ? getFieldBridges : putFieldBridges;
-    assert holder.isProgramClass();
-    fieldMap.computeIfAbsent(
-        field.field,
-        k ->
-            DexEncodedMethod.createFieldAccessorBridge(
-                new DexFieldWithAccess(field, isGet), holder.asProgramClass(), bridgeMethod));
-    return bridgeMethod;
-  }
-
-  DexMethod ensureInvokeBridge(DexEncodedMethod method) {
-    // We add bridges only when targeting other nest members.
-    DexClass holder = definitionFor(method.getHolderType());
-    assert holder != null;
-    DexMethod bridgeMethod;
-    if (method.isInstanceInitializer()) {
-      nestConstructorUsed = true;
-      bridgeMethod = computeInitializerBridge(method.method);
-    } else {
-      bridgeMethod = computeMethodBridge(method);
-    }
-    if (holderRequiresBridge(holder)) {
-      return bridgeMethod;
-    }
-    // The map is used to avoid creating multiple times the bridge
-    // and remembers the bridges to add.
-    assert holder.isProgramClass();
-    bridges.computeIfAbsent(
-        method.method,
-        k ->
-            method.isInstanceInitializer()
-                ? method.toInitializerForwardingBridge(holder.asProgramClass(), bridgeMethod)
-                : method.toStaticForwardingBridge(
-                    holder.asProgramClass(), computeMethodBridge(method)));
-    return bridgeMethod;
-  }
-
-  protected class NestBasedAccessDesugaringUseRegistry extends UseRegistry {
-
-    private final DexClassAndMethod context;
-
-    NestBasedAccessDesugaringUseRegistry(DexClassAndMethod context) {
-      super(appView.options().itemFactory);
-      this.context = context;
-    }
-
-    private void registerInvoke(DexMethod method, Invoke.Type invokeType) {
-      // Calls to non class type are not done through nest based access control.
-      // Work-around for calls to enum.clone().
-      if (!method.holder.isClassType()) {
-        return;
-      }
-      DexEncodedMethod encodedMethod = lookupOnHolder(method, context, invokeType);
-      if (encodedMethod != null && invokeRequiresRewriting(encodedMethod, context)) {
-        ensureInvokeBridge(encodedMethod);
-      }
-    }
-
-    private void registerFieldAccess(DexField field, boolean isGet) {
-      // 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.
-      DexEncodedField encodedField = lookupOnHolder(field);
-      if (encodedField != null && fieldAccessRequiresRewriting(encodedField, context)) {
-        ensureFieldAccessBridge(encodedField, isGet);
-      }
-    }
-
-    @Override
-    public void registerInitClass(DexType clazz) {
-      // Nothing to do since we always use a public field for initializing the class.
-    }
-
-    @Override
-    public void registerInvokeVirtual(DexMethod method) {
-      // Calls to class nest mate private methods are targeted by invokeVirtual in jdk11.
-      // The spec recommends to do so, but do not enforce it, hence invokeDirect is also registered.
-      registerInvoke(method, Invoke.Type.VIRTUAL);
-    }
-
-    @Override
-    public void registerInvokeDirect(DexMethod method) {
-      registerInvoke(method, Invoke.Type.DIRECT);
-    }
-
-    @Override
-    public void registerInvokeStatic(DexMethod method) {
-      registerInvoke(method, Invoke.Type.STATIC);
-    }
-
-    @Override
-    public void registerInvokeInterface(DexMethod method) {
-      // Calls to interface nest mate private methods are targeted by invokeInterface in jdk11.
-      // The spec recommends to do so, but do not enforce it, hence invokeDirect is also registered.
-      registerInvoke(method, Invoke.Type.INTERFACE);
-    }
-
-    @Override
-    public void registerInvokeSuper(DexMethod method) {
-      registerInvoke(method, Invoke.Type.SUPER);
-    }
-
-    @Override
-    public void registerInstanceFieldWrite(DexField field) {
-      registerFieldAccess(field, false);
-    }
-
-    @Override
-    public void registerInstanceFieldRead(DexField field) {
-      registerFieldAccess(field, true);
-    }
-
-    @Override
-    public void registerNewInstance(DexType type) {
-      // Unrelated to access based control.
-      // The <init> method has to be rewritten instead
-      // and <init> is called through registerInvoke.
-    }
-
-    @Override
-    public void registerStaticFieldRead(DexField field) {
-      registerFieldAccess(field, true);
-    }
-
-    @Override
-    public void registerStaticFieldWrite(DexField field) {
-      registerFieldAccess(field, false);
-    }
-
-    @Override
-    public void registerTypeReference(DexType type) {
-      // Unrelated to access based control.
-    }
-
-    @Override
-    public void registerInstanceOf(DexType type) {
-      // Unrelated to access based control.
-    }
-  }
-
-  public static final class DexFieldWithAccess {
-
-    private final DexEncodedField field;
-    private final boolean isGet;
-
-    DexFieldWithAccess(DexEncodedField field, boolean isGet) {
-      this.field = field;
-      this.isGet = isGet;
-    }
-
-    @Override
-    public int hashCode() {
-      return Objects.hash(field, isGet);
-    }
-
-    @Override
-    public boolean equals(Object o) {
-      if (o == null) {
-        return false;
-      }
-      if (getClass() != o.getClass()) {
-        return false;
-      }
-      DexFieldWithAccess other = (DexFieldWithAccess) o;
-      return isGet == other.isGet && field == other.field;
-    }
-
-    public boolean isGet() {
-      return isGet;
-    }
-
-    public boolean isStatic() {
-      return field.accessFlags.isStatic();
-    }
-
-    public boolean isPut() {
-      return !isGet();
-    }
-
-    public boolean isInstance() {
-      return !isStatic();
-    }
-
-    public boolean isStaticGet() {
-      return isStatic() && isGet();
-    }
-
-    public boolean isStaticPut() {
-      return isStatic() && isPut();
-    }
-
-    public boolean isInstanceGet() {
-      return isInstance() && isGet();
-    }
-
-    public boolean isInstancePut() {
-      return isInstance() && isPut();
-    }
-
-    public DexType getType() {
-      return field.field.type;
-    }
-
-    public DexType getHolder() {
-      return field.getHolderType();
-    }
-
-    public DexField getField() {
-      return field.field;
-    }
-  }
-}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/NestedPrivateMethodLens.java b/src/main/java/com/android/tools/r8/ir/desugar/NestedPrivateMethodLens.java
deleted file mode 100644
index 12418b3..0000000
--- a/src/main/java/com/android/tools/r8/ir/desugar/NestedPrivateMethodLens.java
+++ /dev/null
@@ -1,161 +0,0 @@
-// Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
-
-package com.android.tools.r8.ir.desugar;
-
-import com.android.tools.r8.graph.AppView;
-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.GraphLens;
-import com.android.tools.r8.graph.GraphLens.NestedGraphLens;
-import com.android.tools.r8.graph.RewrittenPrototypeDescription;
-import com.android.tools.r8.ir.code.Invoke.Type;
-import com.android.tools.r8.utils.collections.EmptyBidirectionalOneToOneMap;
-import com.google.common.collect.ImmutableMap;
-import java.util.IdentityHashMap;
-import java.util.Map;
-
-public class NestedPrivateMethodLens extends NestedGraphLens {
-
-  // Map from nestHost to nest members including nest hosts
-  private final DexType nestConstructorType;
-  private final Map<DexField, DexMethod> getFieldMap;
-  private final Map<DexField, DexMethod> putFieldMap;
-
-  NestedPrivateMethodLens(
-      AppView<?> appView,
-      DexType nestConstructorType,
-      Map<DexMethod, DexMethod> methodMap,
-      Map<DexField, DexMethod> getFieldMap,
-      Map<DexField, DexMethod> putFieldMap,
-      GraphLens previousLens) {
-    super(
-        ImmutableMap.of(),
-        methodMap,
-        new EmptyBidirectionalOneToOneMap<>(),
-        new EmptyBidirectionalOneToOneMap<>(),
-        previousLens,
-        appView.dexItemFactory());
-    // No concurrent maps here, we do not want synchronization overhead.
-    assert methodMap instanceof IdentityHashMap;
-    assert getFieldMap instanceof IdentityHashMap;
-    assert putFieldMap instanceof IdentityHashMap;
-    this.nestConstructorType = nestConstructorType;
-    this.getFieldMap = getFieldMap;
-    this.putFieldMap = putFieldMap;
-  }
-
-  private DexMethod lookupFieldForMethod(
-      DexField field, DexMethod context, Map<DexField, DexMethod> map) {
-    assert context != null;
-    DexMethod bridge = map.get(field);
-    if (bridge != null && bridge.holder != context.holder) {
-      return bridge;
-    }
-    return null;
-  }
-
-  @Override
-  public DexMethod lookupGetFieldForMethod(DexField field, DexMethod context) {
-    assert getPrevious().lookupGetFieldForMethod(field, context) == null;
-    return lookupFieldForMethod(field, context, getFieldMap);
-  }
-
-  @Override
-  public DexMethod lookupPutFieldForMethod(DexField field, DexMethod context) {
-    assert getPrevious().lookupPutFieldForMethod(field, context) == null;
-    return lookupFieldForMethod(field, context, putFieldMap);
-  }
-
-  @Override
-  public boolean isContextFreeForMethods() {
-    return methodMap.isEmpty();
-  }
-
-  @Override
-  public boolean verifyIsContextFreeForMethod(DexMethod method) {
-    assert !methodMap.containsKey(getPrevious().lookupMethod(method));
-    return true;
-  }
-
-  // This is true because mappings specific to this class can be filled.
-  @Override
-  protected boolean isLegitimateToHaveEmptyMappings() {
-    return true;
-  }
-
-  private boolean isConstructorBridge(DexMethod method) {
-    DexType[] parameters = method.proto.parameters.values;
-    if (parameters.length == 0) {
-      return false;
-    }
-    DexType lastParameterType = parameters[parameters.length - 1];
-    return lastParameterType == nestConstructorType;
-  }
-
-  @Override
-  protected RewrittenPrototypeDescription internalDescribePrototypeChanges(
-      RewrittenPrototypeDescription prototypeChanges, DexMethod method) {
-    if (isConstructorBridge(method)) {
-      // TODO (b/132767654): Try to write a test which breaks that assertion.
-      assert prototypeChanges.isEmpty();
-      // TODO(b/164901008): Fix when the number of arguments overflows.
-      return RewrittenPrototypeDescription.none().withExtraUnusedNullParameter();
-    }
-    return prototypeChanges;
-  }
-
-  @Override
-  public MethodLookupResult internalDescribeLookupMethod(
-      MethodLookupResult previous, DexMethod context) {
-    DexMethod bridge = methodMap.get(previous.getReference());
-    if (bridge == null) {
-      return previous;
-    }
-    assert context != null : "Guaranteed by isContextFreeForMethod";
-    if (bridge.holder == context.holder) {
-      return previous;
-    }
-    MethodLookupResult.Builder resultBuilder =
-        MethodLookupResult.builder(this).setReference(bridge);
-    if (isConstructorBridge(bridge)) {
-      resultBuilder
-          .setPrototypeChanges(
-              internalDescribePrototypeChanges(previous.getPrototypeChanges(), bridge))
-          .setType(Type.DIRECT);
-    } else {
-      resultBuilder.setPrototypeChanges(previous.getPrototypeChanges()).setType(Type.STATIC);
-    }
-    return resultBuilder.build();
-  }
-
-  public static Builder builder() {
-    return new Builder();
-  }
-
-  public static class Builder extends NestedGraphLens.Builder {
-
-    private Map<DexField, DexMethod> getFieldMap = new IdentityHashMap<>();
-    private Map<DexField, DexMethod> putFieldMap = new IdentityHashMap<>();
-
-    public void mapGetField(DexField from, DexMethod to) {
-      getFieldMap.put(from, to);
-    }
-
-    public void mapPutField(DexField from, DexMethod to) {
-      putFieldMap.put(from, to);
-    }
-
-    public NestedPrivateMethodLens build(AppView<?> appView, DexType nestConstructorType) {
-      assert typeMap.isEmpty();
-      assert fieldMap.isEmpty();
-      if (getFieldMap.isEmpty() && methodMap.isEmpty() && putFieldMap.isEmpty()) {
-        return null;
-      }
-      return new NestedPrivateMethodLens(
-          appView, nestConstructorType, methodMap, getFieldMap, putFieldMap, appView.graphLens());
-    }
-  }
-}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/R8NestBasedAccessDesugaring.java b/src/main/java/com/android/tools/r8/ir/desugar/R8NestBasedAccessDesugaring.java
deleted file mode 100644
index e9ac116..0000000
--- a/src/main/java/com/android/tools/r8/ir/desugar/R8NestBasedAccessDesugaring.java
+++ /dev/null
@@ -1,119 +0,0 @@
-// Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
-
-package com.android.tools.r8.ir.desugar;
-
-import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.DexApplication;
-import com.android.tools.r8.graph.DexClass;
-import com.android.tools.r8.graph.DexMethod;
-import com.android.tools.r8.graph.DexProgramClass;
-import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.graph.ProgramMethod;
-import com.android.tools.r8.utils.ThreadUtils;
-import com.google.common.collect.Sets;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-import java.util.concurrent.ExecutionException;
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Future;
-import java.util.function.BiConsumer;
-
-// Summary:
-// - Computes all the live nests reachable from Program Classes (Sequential), each time a
-//  nest is computed, its processing starts concurrently.
-// - Add bridges to be processed by further passes and create the maps
-// for the lens (Sequential)
-public class R8NestBasedAccessDesugaring extends NestBasedAccessDesugaring {
-
-  public R8NestBasedAccessDesugaring(AppView<?> appView) {
-    super(appView);
-  }
-
-  public NestedPrivateMethodLens run(
-      ExecutorService executorService, DexApplication.Builder<?> appBuilder)
-      throws ExecutionException {
-    assert !appView.options().canUseNestBasedAccess()
-        || appView.options().testing.enableForceNestBasedAccessDesugaringForTest;
-    computeAndProcessNestsConcurrently(executorService);
-    NestedPrivateMethodLens.Builder lensBuilder = NestedPrivateMethodLens.builder();
-    addDeferredBridgesAndMapMethods(lensBuilder);
-    clearNestAttributes();
-    synthesizeNestConstructor(appBuilder);
-    return lensBuilder.build(appView, getNestConstructorType());
-  }
-
-  private void addDeferredBridgesAndMapMethods(NestedPrivateMethodLens.Builder lensBuilder) {
-    // Here we add the bridges and we fill the lens map.
-    addDeferredBridgesAndMapMethods(bridges, lensBuilder::map);
-    addDeferredBridgesAndMapMethods(getFieldBridges, lensBuilder::mapGetField);
-    addDeferredBridgesAndMapMethods(putFieldBridges, lensBuilder::mapPutField);
-  }
-
-  private <E> void addDeferredBridgesAndMapMethods(
-      Map<E, ProgramMethod> bridges, BiConsumer<E, DexMethod> lensInserter) {
-    for (Map.Entry<E, ProgramMethod> entry : bridges.entrySet()) {
-      ProgramMethod method = entry.getValue();
-      method.getHolder().addMethod(method.getDefinition());
-      lensInserter.accept(entry.getKey(), method.getReference());
-    }
-    bridges.clear();
-  }
-
-  private void clearNestAttributes() {
-    // Clearing nest attributes is required to allow class merging to be performed across nests
-    // and to forbid other optimizations to introduce nest based accesses.
-    for (DexClass clazz : appView.appInfo().classes()) {
-      clazz.clearNestHost();
-      clazz.getNestMembersClassAttributes().clear();
-    }
-  }
-
-  private void computeAndProcessNestsConcurrently(ExecutorService executorService)
-      throws ExecutionException {
-    Set<DexType> nestHosts = Sets.newIdentityHashSet();
-    ;
-    List<Future<?>> futures = new ArrayList<>();
-    // It is possible that a nest member is on the program path but its nest host
-    // is only in the class path (or missing, raising an error).
-    // Nests are therefore computed the first time a nest member is met, host or not.
-    // The computedNestHosts list is there to avoid processing multiple times the same nest.
-    for (DexProgramClass clazz : appView.appInfo().classes()) {
-      if (clazz.isInANest()) {
-        DexType hostType = clazz.getNestHost();
-        if (!nestHosts.contains(hostType)) {
-          nestHosts.add(hostType);
-          futures.add(asyncProcessNest(clazz, executorService));
-        }
-      }
-    }
-    ThreadUtils.awaitFutures(futures);
-  }
-
-  // In R8, all classes are processed ahead of time.
-  @Override
-  protected boolean shouldProcessClassInNest(DexClass clazz, List<DexType> nest) {
-    return true;
-  }
-
-  @Override
-  void reportMissingNestHost(DexClass clazz) {
-    if (appView.options().ignoreMissingClasses) {
-      appView.options().nestDesugaringWarningMissingNestHost(clazz);
-    } else {
-      appView.options().errorMissingClassMissingNestHost(clazz);
-    }
-  }
-
-  @Override
-  void reportIncompleteNest(List<DexType> nest) {
-    if (appView.options().ignoreMissingClasses) {
-      appView.options().nestDesugaringWarningIncompleteNest(nest, appView);
-    } else {
-      appView.options().errorMissingClassIncompleteNest(nest, appView);
-    }
-  }
-}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/TwrCloseResourceRewriter.java b/src/main/java/com/android/tools/r8/ir/desugar/TwrCloseResourceRewriter.java
index f7be74d..98acb2d 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/TwrCloseResourceRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/TwrCloseResourceRewriter.java
@@ -4,178 +4,137 @@
 
 package com.android.tools.r8.ir.desugar;
 
-import com.android.tools.r8.dex.Constants;
-import com.android.tools.r8.graph.AppInfo;
+import com.android.tools.r8.cf.code.CfInstruction;
+import com.android.tools.r8.cf.code.CfInvoke;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.CfCode;
-import com.android.tools.r8.graph.ClassAccessFlags;
-import com.android.tools.r8.graph.DexAnnotationSet;
-import com.android.tools.r8.graph.DexApplication.Builder;
-import com.android.tools.r8.graph.DexEncodedField;
-import com.android.tools.r8.graph.DexEncodedMethod;
 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.DexProto;
-import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.graph.DexTypeList;
-import com.android.tools.r8.graph.GenericSignature.ClassSignature;
-import com.android.tools.r8.graph.GenericSignature.MethodTypeSignature;
 import com.android.tools.r8.graph.MethodAccessFlags;
-import com.android.tools.r8.graph.ParameterAnnotationsList;
+import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.code.IRCode;
-import com.android.tools.r8.ir.code.Instruction;
 import com.android.tools.r8.ir.code.InstructionListIterator;
 import com.android.tools.r8.ir.code.InvokeStatic;
 import com.android.tools.r8.ir.conversion.IRConverter;
 import com.android.tools.r8.ir.desugar.backports.BackportedMethods;
-import com.android.tools.r8.origin.SynthesizedOrigin;
-import com.android.tools.r8.shaking.MainDexClasses;
+import com.android.tools.r8.synthesis.SyntheticNaming.SyntheticKind;
 import com.android.tools.r8.utils.InternalOptions;
-import com.google.common.collect.Sets;
-import java.util.Collections;
-import java.util.Set;
-import java.util.concurrent.ExecutionException;
-import java.util.concurrent.ExecutorService;
+import com.android.tools.r8.utils.InternalOptions.DesugarState;
+import com.google.common.base.Suppliers;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.function.Consumer;
+import java.util.function.Supplier;
+import org.objectweb.asm.Opcodes;
 
-// Try with resources outlining processor. Handles $closeResource methods
-// synthesized by java 9 compiler.
+// Try with resources close-resource desugaring.
 //
-// Works in two phases:
-//   a. during method code processing finds all references to $closeResource(...) synthesized
-//      by java compiler, and replaces them with references to a special utility class method.
-//   b. after all methods are processed and if there was at least one method referencing
-//      $closeResource(...), it synthesizes utility class with appropriate methods.
+// Rewrites $closeResource methods synthesized by java compilers to work on older DEX runtimes.
+// All invocations to $closeResource methods are rewritten to target a compiler generated version
+// that is correct on older DEX runtimes where not all types implement AutoClosable as expected.
 //
 // Note that we don't remove $closeResource(...) synthesized by java compiler, relying on
 // tree shaking to remove them since now they should not be referenced.
-//
+// TODO(b/177401708): D8 does not tree shake so we should remove the now unused method.
 public final class TwrCloseResourceRewriter {
 
-  public static final String UTILITY_CLASS_NAME = "$r8$twr$utility";
-  public static final String UTILITY_CLASS_DESCRIPTOR = "L$r8$twr$utility;";
-
   private final AppView<?> appView;
-  private final IRConverter converter;
+  private final DexProto twrCloseResourceProto;
+  private final List<ProgramMethod> synthesizedMethods = new ArrayList<>();
 
-  private final DexMethod twrCloseResourceMethod;
-
-  private final Set<DexProgramClass> referencingClasses = Sets.newConcurrentHashSet();
-
-  public TwrCloseResourceRewriter(AppView<?> appView, IRConverter converter) {
-    this.appView = appView;
-    this.converter = converter;
-
-    DexItemFactory dexItemFactory = appView.dexItemFactory();
-    DexType twrUtilityClass = dexItemFactory.createType(UTILITY_CLASS_DESCRIPTOR);
-    DexProto twrCloseResourceProto =
-        dexItemFactory.createProto(
-            dexItemFactory.voidType, dexItemFactory.throwableType, dexItemFactory.objectType);
-    this.twrCloseResourceMethod =
-        dexItemFactory.createMethod(
-            twrUtilityClass, twrCloseResourceProto, dexItemFactory.twrCloseResourceMethodName);
+  public static boolean enableTwrCloseResourceDesugaring(InternalOptions options) {
+    return options.desugarState == DesugarState.ON
+        && options.enableTryWithResourcesDesugaring()
+        && !options.canUseTwrCloseResourceMethod();
   }
 
-  public static boolean isUtilityClassDescriptor(DexType type) {
-    return type.descriptor.toString().equals(UTILITY_CLASS_DESCRIPTOR);
+  public TwrCloseResourceRewriter(AppView<?> appView) {
+    this.appView = appView;
+    DexItemFactory dexItemFactory = appView.dexItemFactory();
+    twrCloseResourceProto =
+        dexItemFactory.createProto(
+            dexItemFactory.voidType, dexItemFactory.throwableType, dexItemFactory.objectType);
+  }
+
+  public int rewriteCf(ProgramMethod method, Consumer<ProgramMethod> newMethodCallback) {
+    CfCode code = method.getDefinition().getCode().asCfCode();
+    List<CfInstruction> instructions = code.getInstructions();
+    Supplier<List<CfInstruction>> lazyNewInstructions =
+        Suppliers.memoize(() -> new ArrayList<>(instructions));
+    int replaced = 0;
+    int newInstructionDelta = 0;
+    for (int i = 0; i < instructions.size(); i++) {
+      CfInvoke invoke = instructions.get(i).asInvoke();
+      if (invoke == null
+          || invoke.getOpcode() != Opcodes.INVOKESTATIC
+          || !isTwrCloseResourceMethod(invoke.getMethod(), appView.dexItemFactory())) {
+        continue;
+      }
+      // Synthesize a new method.
+      ProgramMethod closeMethod = createSyntheticCloseResourceMethod(method);
+      newMethodCallback.accept(closeMethod);
+      // Rewrite the invoke to the new synthetic.
+      int newInstructionIndex = i + newInstructionDelta;
+      lazyNewInstructions
+          .get()
+          .set(
+              newInstructionIndex,
+              new CfInvoke(Opcodes.INVOKESTATIC, closeMethod.getReference(), false));
+      ++replaced;
+    }
+    if (replaced > 0) {
+      code.setInstructions(lazyNewInstructions.get());
+    }
+    return replaced;
   }
 
   // Rewrites calls to $closeResource() method. Can be invoked concurrently.
-  public void rewriteMethodCode(IRCode code) {
+  public void rewriteIR(IRCode code) {
     InstructionListIterator iterator = code.instructionListIterator();
-    AppInfo appInfo = appView.appInfo();
     while (iterator.hasNext()) {
-      Instruction instruction = iterator.next();
-      if (!instruction.isInvokeStatic()) {
-        continue;
-      }
-      InvokeStatic invoke = instruction.asInvokeStatic();
-      if (!isSynthesizedCloseResourceMethod(invoke.getInvokedMethod(), appView)) {
+      InvokeStatic invoke = iterator.next().asInvokeStatic();
+      if (invoke == null
+          || !isTwrCloseResourceMethod(invoke.getInvokedMethod(), appView.dexItemFactory())) {
         continue;
       }
 
-      // Replace with a call to a synthetic utility with the only
-      // implementation of the method $closeResource.
+      // Replace with a call to a synthetic utility.
       assert invoke.outValue() == null;
       assert invoke.inValues().size() == 2;
-      iterator.replaceCurrentInstruction(
-          new InvokeStatic(twrCloseResourceMethod, null, invoke.inValues()));
-
-      // Mark as a class referencing utility class.
-      referencingClasses.add(appInfo.definitionFor(code.method().getHolderType()).asProgramClass());
+      ProgramMethod closeResourceMethod = createSyntheticCloseResourceMethod(code.context());
+      InvokeStatic newInvoke =
+          new InvokeStatic(closeResourceMethod.getReference(), null, invoke.inValues());
+      iterator.replaceCurrentInstruction(newInvoke);
+      synchronized (synthesizedMethods) {
+        synthesizedMethods.add(closeResourceMethod);
+      }
     }
   }
 
-  public static boolean isSynthesizedCloseResourceMethod(DexMethod method, AppView<?> appView) {
-    DexMethod original = appView.graphLens().getOriginalMethodSignature(method);
-    assert original != null;
-    // We consider all methods of *any* class with expected name and signature
-    // to be synthesized by java 9 compiler for try-with-resources, reasoning:
-    //
-    //  * we need to look to all potential classes because the calls might be
-    //    moved by inlining.
-    //  * theoretically we could check appropriate encoded method for having
-    //    right attributes, but it still does not guarantee much since we also
-    //    need to look into code and doing this seems an overkill
-    //
-    DexItemFactory dexItemFactory = appView.dexItemFactory();
-    return original.name == dexItemFactory.twrCloseResourceMethodName
-        && original.proto == dexItemFactory.twrCloseResourceMethodProto;
+  public static boolean isTwrCloseResourceMethod(DexMethod method, DexItemFactory factory) {
+    return method.name == factory.twrCloseResourceMethodName
+        && method.proto == factory.twrCloseResourceMethodProto;
   }
 
-  public void synthesizeUtilityClass(
-      Builder<?> builder, ExecutorService executorService, InternalOptions options)
-      throws ExecutionException {
-    if (referencingClasses.isEmpty()) {
-      return;
-    }
+  private ProgramMethod createSyntheticCloseResourceMethod(ProgramMethod method) {
+    return appView
+        .getSyntheticItems()
+        .createMethod(
+            SyntheticKind.TWR_CLOSE_RESOURCE,
+            method,
+            appView.dexItemFactory(),
+            methodBuilder ->
+                methodBuilder
+                    .setAccessFlags(MethodAccessFlags.createPublicStaticSynthetic())
+                    .setProto(twrCloseResourceProto)
+                    .setCode(
+                        m ->
+                            BackportedMethods.CloseResourceMethod_closeResourceImpl(
+                                appView.options(), m)));
+  }
 
-    // The only encoded method.
-    CfCode code =
-        BackportedMethods.CloseResourceMethod_closeResourceImpl(options, twrCloseResourceMethod);
-    MethodAccessFlags flags =
-        MethodAccessFlags.fromSharedAccessFlags(
-            Constants.ACC_PUBLIC | Constants.ACC_STATIC | Constants.ACC_SYNTHETIC, false);
-    DexEncodedMethod method =
-        new DexEncodedMethod(
-            twrCloseResourceMethod,
-            flags,
-            MethodTypeSignature.noSignature(),
-            DexAnnotationSet.empty(),
-            ParameterAnnotationsList.empty(),
-            code,
-            true);
-
-    // Create utility class.
-    DexProgramClass utilityClass =
-        new DexProgramClass(
-            twrCloseResourceMethod.holder,
-            null,
-            new SynthesizedOrigin("twr utility class", getClass()),
-            ClassAccessFlags.fromSharedAccessFlags(Constants.ACC_PUBLIC | Constants.ACC_SYNTHETIC),
-            appView.dexItemFactory().objectType,
-            DexTypeList.empty(),
-            null,
-            null,
-            Collections.emptyList(),
-            null,
-            Collections.emptyList(),
-            ClassSignature.noSignature(),
-            DexAnnotationSet.empty(),
-            DexEncodedField.EMPTY_ARRAY,
-            DexEncodedField.EMPTY_ARRAY,
-            new DexEncodedMethod[] {method},
-            DexEncodedMethod.EMPTY_ARRAY,
-            appView.dexItemFactory().getSkipNameValidationForTesting(),
-            DexProgramClass::checksumFromType,
-            referencingClasses);
-
-    // Process created class and method.
-    AppInfo appInfo = appView.appInfo();
-    MainDexClasses mainDexClasses = appInfo.getMainDexClasses();
-    boolean addToMainDexList = mainDexClasses.containsAnyOf(referencingClasses);
-    appInfo.addSynthesizedClass(utilityClass, addToMainDexList);
-    converter.optimizeSynthesizedClass(utilityClass, executorService);
-    builder.addSynthesizedClass(utilityClass);
+  public void processSynthesizedMethods(IRConverter converter) {
+    synthesizedMethods.forEach(converter::optimizeSynthesizedMethod);
   }
 }
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
new file mode 100644
index 0000000..9fcf485
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/desugar/nest/D8NestBasedAccessDesugaring.java
@@ -0,0 +1,178 @@
+// 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 static com.android.tools.r8.ir.desugar.InterfaceMethodRewriter.reportDependencyEdge;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.ClasspathMethod;
+import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexClassAndField;
+import com.android.tools.r8.graph.DexClassAndMethod;
+import com.android.tools.r8.graph.DexClasspathClass;
+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.UseRegistry;
+import com.android.tools.r8.ir.conversion.MethodProcessor;
+import com.android.tools.r8.utils.ThreadUtils;
+import com.google.common.collect.Iterables;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
+
+/**
+ * Responsible for reporting desugar dependencies and for synthesizing bridges in the program for
+ * accesses from the classpath into the program.
+ */
+public class D8NestBasedAccessDesugaring extends NestBasedAccessDesugaring {
+
+  public D8NestBasedAccessDesugaring(AppView<?> appView) {
+    super(appView);
+  }
+
+  public void reportDesugarDependencies() {
+    forEachNest(
+        nest -> {
+          if (nest.hasMissingMembers()) {
+            throw appView.options().errorMissingNestMember(nest);
+          }
+          DexClass hostClass = nest.getHostClass();
+          for (DexClass memberClass : nest.getMembers()) {
+            if (hostClass.isProgramClass() || memberClass.isProgramClass()) {
+              reportDependencyEdge(hostClass, memberClass, appView.options());
+              reportDependencyEdge(memberClass, hostClass, appView.options());
+            }
+          }
+        },
+        classWithoutHost -> {
+          throw appView.options().errorMissingNestHost(classWithoutHost);
+        });
+  }
+
+  public void synthesizeBridgesForNestBasedAccessesOnClasspath(
+      MethodProcessor methodProcessor, ExecutorService executorService) throws ExecutionException {
+    List<DexClasspathClass> classpathClassesInNests = new ArrayList<>();
+    forEachNest(
+        nest -> {
+          if (nest.getHostClass().isClasspathClass()) {
+            classpathClassesInNests.add(nest.getHostClass().asClasspathClass());
+          }
+          Iterables.addAll(classpathClassesInNests, nest.getClasspathMembers());
+        });
+
+    NestBridgeConsumer bridgeConsumer = NestBridgeConsumer.createForD8(methodProcessor);
+    ThreadUtils.processItems(
+        classpathClassesInNests,
+        clazz -> synthesizeBridgesForNestBasedAccessesOnClasspath(clazz, bridgeConsumer),
+        executorService);
+  }
+
+  public void synthesizeBridgesForNestBasedAccessesOnClasspath(
+      DexClasspathClass clazz, NestBridgeConsumer bridgeConsumer) {
+    clazz.forEachClasspathMethod(
+        method ->
+            method.registerCodeReferencesForDesugaring(
+                new NestBasedAccessDesugaringUseRegistry(method, bridgeConsumer)));
+  }
+
+  private class NestBasedAccessDesugaringUseRegistry extends UseRegistry {
+
+    private final NestBridgeConsumer bridgeConsumer;
+    private final ClasspathMethod context;
+
+    NestBasedAccessDesugaringUseRegistry(
+        ClasspathMethod context, NestBridgeConsumer bridgeConsumer) {
+      super(appView.dexItemFactory());
+      this.bridgeConsumer = bridgeConsumer;
+      this.context = context;
+    }
+
+    private void registerFieldAccess(DexField reference, boolean isGet) {
+      DexClassAndField field =
+          reference.lookupMemberOnClass(appView.definitionForHolder(reference));
+      if (field != null && needsDesugaring(field, context)) {
+        ensureFieldAccessBridge(field, isGet, bridgeConsumer);
+      }
+    }
+
+    private void registerInvoke(DexMethod reference) {
+      if (!reference.getHolderType().isClassType()) {
+        return;
+      }
+      DexClassAndMethod method =
+          reference.lookupMemberOnClass(appView.definitionForHolder(reference));
+      if (method != null && needsDesugaring(method, context)) {
+        ensureMethodBridge(method, bridgeConsumer);
+      }
+    }
+
+    @Override
+    public void registerInvokeDirect(DexMethod method) {
+      registerInvoke(method);
+    }
+
+    @Override
+    public void registerInvokeInterface(DexMethod method) {
+      registerInvoke(method);
+    }
+
+    @Override
+    public void registerInvokeStatic(DexMethod method) {
+      registerInvoke(method);
+    }
+
+    @Override
+    public void registerInvokeSuper(DexMethod method) {
+      registerInvoke(method);
+    }
+
+    @Override
+    public void registerInvokeVirtual(DexMethod method) {
+      registerInvoke(method);
+    }
+
+    @Override
+    public void registerInstanceFieldWrite(DexField field) {
+      registerFieldAccess(field, false);
+    }
+
+    @Override
+    public void registerInstanceFieldRead(DexField field) {
+      registerFieldAccess(field, true);
+    }
+
+    @Override
+    public void registerStaticFieldRead(DexField field) {
+      registerFieldAccess(field, true);
+    }
+
+    @Override
+    public void registerStaticFieldWrite(DexField field) {
+      registerFieldAccess(field, false);
+    }
+
+    @Override
+    public void registerInitClass(DexType clazz) {
+      // Intentionally empty.
+    }
+
+    @Override
+    public void registerInstanceOf(DexType type) {
+      // Intentionally empty.
+    }
+
+    @Override
+    public void registerNewInstance(DexType type) {
+      // Intentionally empty.
+    }
+
+    @Override
+    public void registerTypeReference(DexType type) {
+      // Intentionally empty.
+    }
+  }
+}
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
new file mode 100644
index 0000000..7468cba
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/desugar/nest/D8NestBridgeConsumer.java
@@ -0,0 +1,33 @@
+// 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.scheduleMethodForProcessingAfterCurrentWave(bridge);
+  }
+
+  @Override
+  public void acceptFieldPutBridge(ProgramField target, ProgramMethod bridge) {
+    methodProcessor.scheduleMethodForProcessingAfterCurrentWave(bridge);
+  }
+
+  @Override
+  public void acceptMethodBridge(ProgramMethod target, ProgramMethod bridge) {
+    methodProcessor.scheduleMethodForProcessingAfterCurrentWave(bridge);
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/nest/Nest.java b/src/main/java/com/android/tools/r8/ir/desugar/nest/Nest.java
new file mode 100644
index 0000000..979eb9c
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/desugar/nest/Nest.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.ir.desugar.nest;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexClasspathClass;
+import com.android.tools.r8.graph.DexType;
+import com.google.common.collect.Iterables;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.function.Consumer;
+
+public class Nest {
+
+  private final DexClass hostClass;
+  private final List<DexClass> members;
+  private final List<DexType> missingMembers;
+
+  private Nest(DexClass hostClass, List<DexClass> members, List<DexType> missingMembers) {
+    this.hostClass = hostClass;
+    this.members = members;
+    this.missingMembers = missingMembers;
+  }
+
+  public static Nest create(AppView<?> appView, DexClass clazz) {
+    return create(appView, clazz, null);
+  }
+
+  public static Nest create(
+      AppView<?> appView, DexClass clazz, Consumer<DexClass> missingHostConsumer) {
+    assert clazz.isInANest();
+
+    DexClass hostClass = clazz.isNestHost() ? clazz : appView.definitionFor(clazz.getNestHost());
+    if (hostClass == null) {
+      // Missing nest host means the class is considered as not being part of a nest.
+      if (missingHostConsumer != null) {
+        missingHostConsumer.accept(clazz);
+      }
+      return null;
+    }
+
+    List<DexClass> members = new ArrayList<>(hostClass.getNestMembersClassAttributes().size());
+    List<DexType> missingMembers = new ArrayList<>();
+    hostClass.forEachNestMember(
+        memberType -> {
+          DexClass memberClass = appView.definitionFor(memberType);
+          if (memberClass != null) {
+            members.add(memberClass);
+          } else {
+            missingMembers.add(memberType);
+          }
+        });
+    return new Nest(hostClass, members, missingMembers);
+  }
+
+  public Iterable<DexClasspathClass> getClasspathMembers() {
+    return Iterables.transform(
+        Iterables.filter(members, DexClass::isClasspathClass), DexClass::asClasspathClass);
+  }
+
+  public DexClass getHostClass() {
+    return hostClass;
+  }
+
+  public List<DexClass> getMembers() {
+    return members;
+  }
+
+  public List<DexType> getMissingMembers() {
+    return missingMembers;
+  }
+
+  public boolean hasLibraryMember() {
+    return Iterables.any(members, DexClass::isLibraryClass);
+  }
+
+  public boolean hasMissingMembers() {
+    return !missingMembers.isEmpty();
+  }
+}
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
new file mode 100644
index 0000000..cbaa05a
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/desugar/nest/NestBasedAccessDesugaring.java
@@ -0,0 +1,382 @@
+// 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 static com.android.tools.r8.utils.ConsumerUtils.emptyConsumer;
+
+import com.android.tools.r8.cf.code.CfConstNull;
+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.dex.Constants;
+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.ClassAccessFlags;
+import com.android.tools.r8.graph.Code;
+import com.android.tools.r8.graph.DexAnnotationSet;
+import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexClassAndField;
+import com.android.tools.r8.graph.DexClassAndMember;
+import com.android.tools.r8.graph.DexClassAndMethod;
+import com.android.tools.r8.graph.DexEncodedField;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexMember;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.DexProto;
+import com.android.tools.r8.graph.DexString;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.DexTypeList;
+import com.android.tools.r8.graph.GenericSignature.ClassSignature;
+import com.android.tools.r8.graph.LibraryMember;
+import com.android.tools.r8.graph.ProgramField;
+import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.origin.SynthesizedOrigin;
+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.Collections;
+import java.util.List;
+import java.util.Set;
+import java.util.function.Consumer;
+import org.objectweb.asm.Opcodes;
+
+// NestBasedAccessDesugaring contains common code between the two subclasses
+// which are specialized for d8 and r8
+public class NestBasedAccessDesugaring {
+
+  // Short names to avoid creating long strings
+  public static final String NEST_ACCESS_NAME_PREFIX = "-$$Nest$";
+  private static final String NEST_ACCESS_METHOD_NAME_PREFIX = NEST_ACCESS_NAME_PREFIX + "m";
+  private static final String NEST_ACCESS_STATIC_METHOD_NAME_PREFIX =
+      NEST_ACCESS_NAME_PREFIX + "sm";
+  private static final String NEST_ACCESS_FIELD_GET_NAME_PREFIX = NEST_ACCESS_NAME_PREFIX + "fget";
+  private static final String NEST_ACCESS_STATIC_GET_FIELD_NAME_PREFIX =
+      NEST_ACCESS_NAME_PREFIX + "sfget";
+  private static final String NEST_ACCESS_FIELD_PUT_NAME_PREFIX = NEST_ACCESS_NAME_PREFIX + "fput";
+  private static final String NEST_ACCESS_STATIC_PUT_FIELD_NAME_PREFIX =
+      NEST_ACCESS_NAME_PREFIX + "sfput";
+  public static final String NEST_CONSTRUCTOR_NAME = NEST_ACCESS_NAME_PREFIX + "Constructor";
+
+  protected final AppView<?> appView;
+  private final DexItemFactory dexItemFactory;
+
+  // Common single empty class for nest based private constructors
+  private DexProgramClass nestConstructor;
+  private boolean nestConstructorUsed;
+
+  public NestBasedAccessDesugaring(AppView<?> appView) {
+    this.appView = appView;
+    this.dexItemFactory = appView.dexItemFactory();
+  }
+
+  void forEachNest(Consumer<Nest> consumer) {
+    forEachNest(consumer, emptyConsumer());
+  }
+
+  void forEachNest(Consumer<Nest> consumer, Consumer<DexClass> missingHostConsumer) {
+    Set<DexType> seenNestHosts = Sets.newIdentityHashSet();
+    for (DexProgramClass clazz : appView.appInfo().classes()) {
+      if (!clazz.isInANest() || !seenNestHosts.add(clazz.getNestHost())) {
+        continue;
+      }
+
+      Nest nest = Nest.create(appView, clazz, missingHostConsumer);
+      if (nest != null) {
+        consumer.accept(nest);
+      }
+    }
+  }
+
+  public boolean needsDesugaring(ProgramMethod method) {
+    if (!method.getHolder().isInANest() || !method.getDefinition().hasCode()) {
+      return false;
+    }
+
+    Code code = method.getDefinition().getCode();
+    if (code.isDexCode()) {
+      assert appView.testing().allowDexInputForTesting;
+      return false;
+    }
+
+    if (!code.isCfCode()) {
+      throw new Unreachable("Unexpected attempt to determine if non-CF code needs desugaring");
+    }
+
+    return Iterables.any(
+        code.asCfCode().getInstructions(), instruction -> needsDesugaring(instruction, method));
+  }
+
+  private boolean needsDesugaring(CfInstruction instruction, ProgramMethod context) {
+    if (instruction.isFieldInstruction()) {
+      return needsDesugaring(instruction.asFieldInstruction().getField(), context);
+    }
+    if (instruction.isInvoke()) {
+      return needsDesugaring(instruction.asInvoke().getMethod(), context);
+    }
+    return false;
+  }
+
+  public boolean needsDesugaring(DexMember<?, ?> memberReference, ProgramMethod context) {
+    if (!context.getHolder().isInANest() || !memberReference.getHolderType().isClassType()) {
+      return false;
+    }
+    DexClass holder = appView.definitionForHolder(memberReference, context);
+    DexClassAndMember<?, ?> member = memberReference.lookupMemberOnClass(holder);
+    return member != null && needsDesugaring(member, context);
+  }
+
+  public boolean needsDesugaring(DexClassAndMember<?, ?> member, DexClassAndMethod context) {
+    return member.getAccessFlags().isPrivate()
+        && member.getHolderType() != context.getHolderType()
+        && member.getHolder().isInANest()
+        && 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;
+  }
+
+  public List<CfInstruction> desugarInstruction(
+      CfInstruction instruction, ProgramMethod context, NestBridgeConsumer bridgeConsumer) {
+    if (instruction.isFieldInstruction()) {
+      return desugarFieldInstruction(instruction.asFieldInstruction(), context, bridgeConsumer);
+    }
+    if (instruction.isInvoke()) {
+      return desugarInvokeInstruction(instruction.asInvoke(), context, bridgeConsumer);
+    }
+    return null;
+  }
+
+  private List<CfInstruction> desugarFieldInstruction(
+      CfFieldInstruction instruction, ProgramMethod context, NestBridgeConsumer bridgeConsumer) {
+    // 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.
+    DexClass holder = appView.definitionForHolder(instruction.getField(), context);
+    DexClassAndField field = instruction.getField().lookupMemberOnClass(holder);
+    if (field == null || !needsDesugaring(field, context)) {
+      return null;
+    }
+
+    DexMethod bridge = ensureFieldAccessBridge(field, instruction.isFieldGet(), bridgeConsumer);
+    return ImmutableList.of(
+        new CfInvoke(Opcodes.INVOKESTATIC, bridge, field.getHolder().isInterface()));
+  }
+
+  private List<CfInstruction> desugarInvokeInstruction(
+      CfInvoke invoke, ProgramMethod context, NestBridgeConsumer bridgeConsumer) {
+    DexMethod invokedMethod = invoke.getMethod();
+    if (!invokedMethod.getHolderType().isClassType()) {
+      return null;
+    }
+
+    // Since we only need to desugar accesses to private methods, and all accesses to private
+    // methods must be accessing the private method directly on its holder, we can lookup the
+    // method on the holder instead of resolving the method.
+    DexClass holder = appView.definitionForHolder(invokedMethod, context);
+    DexClassAndMethod target = invokedMethod.lookupMemberOnClass(holder);
+    if (target == null || !needsDesugaring(target, context)) {
+      return null;
+    }
+
+    DexMethod bridge = ensureMethodBridge(target, bridgeConsumer);
+    if (target.getDefinition().isInstanceInitializer()) {
+      assert !invoke.isInterface();
+      return ImmutableList.of(
+          new CfConstNull(), new CfInvoke(Opcodes.INVOKESPECIAL, bridge, false));
+    }
+
+    return ImmutableList.of(new CfInvoke(Opcodes.INVOKESTATIC, bridge, invoke.isInterface()));
+  }
+
+  private RuntimeException reportIncompleteNest(LibraryMember<?, ?> member) {
+    Nest nest = Nest.create(appView, member.getHolder());
+    assert nest != null : "Should be a compilation error if missing nest host on library class.";
+    throw appView.options().errorMissingNestMember(nest);
+  }
+
+  private DexProgramClass createNestAccessConstructor() {
+    // TODO(b/176900254): ensure hygienic synthetic class.
+    return new DexProgramClass(
+        dexItemFactory.nestConstructorType,
+        null,
+        new SynthesizedOrigin("Nest based access desugaring", getClass()),
+        // Make the synthesized class public since shared in the whole program.
+        ClassAccessFlags.fromDexAccessFlags(
+            Constants.ACC_FINAL | Constants.ACC_SYNTHETIC | Constants.ACC_PUBLIC),
+        dexItemFactory.objectType,
+        DexTypeList.empty(),
+        dexItemFactory.createString("nest"),
+        null,
+        Collections.emptyList(),
+        null,
+        Collections.emptyList(),
+        ClassSignature.noSignature(),
+        DexAnnotationSet.empty(),
+        DexEncodedField.EMPTY_ARRAY,
+        DexEncodedField.EMPTY_ARRAY,
+        DexEncodedMethod.EMPTY_ARRAY,
+        DexEncodedMethod.EMPTY_ARRAY,
+        dexItemFactory.getSkipNameValidationForTesting(),
+        DexProgramClass::checksumFromType);
+  }
+
+  public DexProgramClass synthesizeNestConstructor() {
+    if (nestConstructorUsed && nestConstructor == null) {
+      nestConstructor = createNestAccessConstructor();
+      return nestConstructor;
+    }
+    return null;
+  }
+
+  DexMethod ensureFieldAccessBridge(
+      DexClassAndField field, boolean isGet, NestBridgeConsumer bridgeConsumer) {
+    if (field.isProgramField()) {
+      return ensureFieldAccessBridge(field.asProgramField(), isGet, bridgeConsumer);
+    }
+    if (field.isClasspathField()) {
+      return getFieldAccessBridgeReference(field, isGet);
+    }
+    assert field.isLibraryField();
+    throw reportIncompleteNest(field.asLibraryField());
+  }
+
+  private DexMethod ensureFieldAccessBridge(
+      ProgramField field, boolean isGet, NestBridgeConsumer bridgeConsumer) {
+    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);
+        }
+      }
+      return bridge.getReference();
+    }
+  }
+
+  private DexMethod getFieldAccessBridgeReference(DexClassAndField field, boolean isGet) {
+    int bridgeParameterCount =
+        BooleanUtils.intValue(!field.getAccessFlags().isStatic()) + BooleanUtils.intValue(!isGet);
+    DexType[] parameters = new DexType[bridgeParameterCount];
+    if (!isGet) {
+      parameters[parameters.length - 1] = field.getType();
+    }
+    if (!field.getAccessFlags().isStatic()) {
+      parameters[0] = field.getHolderType();
+    }
+    DexType returnType = isGet ? field.getType() : dexItemFactory.voidType;
+    DexProto proto = dexItemFactory.createProto(returnType, parameters);
+    return dexItemFactory.createMethod(
+        field.getHolderType(), proto, getFieldAccessBridgeName(field, isGet));
+  }
+
+  private DexString getFieldAccessBridgeName(DexClassAndField field, boolean isGet) {
+    String prefix;
+    if (isGet && !field.getAccessFlags().isStatic()) {
+      prefix = NEST_ACCESS_FIELD_GET_NAME_PREFIX;
+    } else if (isGet) {
+      prefix = NEST_ACCESS_STATIC_GET_FIELD_NAME_PREFIX;
+    } else if (!field.getAccessFlags().isStatic()) {
+      prefix = NEST_ACCESS_FIELD_PUT_NAME_PREFIX;
+    } else {
+      prefix = NEST_ACCESS_STATIC_PUT_FIELD_NAME_PREFIX;
+    }
+    return dexItemFactory.createString(prefix + field.getName().toString());
+  }
+
+  DexMethod ensureMethodBridge(DexClassAndMethod method, NestBridgeConsumer bridgeConsumer) {
+    if (method.isProgramMethod()) {
+      return ensureMethodBridge(method.asProgramMethod(), bridgeConsumer);
+    }
+    if (method.isClasspathMethod()) {
+      return getMethodBridgeReference(method);
+    }
+    assert method.isLibraryMethod();
+    throw reportIncompleteNest(method.asLibraryMethod());
+  }
+
+  private DexMethod ensureMethodBridge(ProgramMethod method, NestBridgeConsumer bridgeConsumer) {
+    DexMethod bridgeReference = getMethodBridgeReference(method);
+    synchronized (method.getHolder().getMethodCollection()) {
+      ProgramMethod bridge = method.getHolder().lookupProgramMethod(bridgeReference);
+      if (bridge == null) {
+        DexEncodedMethod definition = method.getDefinition();
+        bridge =
+            definition.isInstanceInitializer()
+                ? definition.toInitializerForwardingBridge(
+                    method.getHolder(), bridgeReference, dexItemFactory)
+                : definition.toStaticForwardingBridge(
+                    method.getHolder(), bridgeReference, dexItemFactory);
+        bridge.getHolder().addDirectMethod(bridge.getDefinition());
+        if (bridgeConsumer != null) {
+          bridgeConsumer.acceptMethodBridge(method, bridge);
+        }
+      }
+    }
+    return bridgeReference;
+  }
+
+  private DexMethod getMethodBridgeReference(DexClassAndMethod method) {
+    if (method.getDefinition().isInstanceInitializer()) {
+      DexProto newProto =
+          dexItemFactory.appendTypeToProto(method.getProto(), dexItemFactory.nestConstructorType);
+      nestConstructorUsed = true;
+      return method.getReference().withProto(newProto, dexItemFactory);
+    }
+    DexProto proto =
+        method.getAccessFlags().isStatic()
+            ? method.getProto()
+            : dexItemFactory.prependHolderToProto(method.getReference());
+    return dexItemFactory.createMethod(method.getHolderType(), proto, getMethodBridgeName(method));
+  }
+
+  private DexString getMethodBridgeName(DexClassAndMethod method) {
+    String prefix =
+        method.getAccessFlags().isStatic()
+            ? NEST_ACCESS_STATIC_METHOD_NAME_PREFIX
+            : NEST_ACCESS_METHOD_NAME_PREFIX;
+    return dexItemFactory.createString(prefix + method.getName().toString());
+  }
+}
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
new file mode 100644
index 0000000..e6a8a03
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/desugar/nest/NestBridgeConsumer.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.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/AssumeInserter.java b/src/main/java/com/android/tools/r8/ir/optimize/AssumeInserter.java
index d6eae9a..ddb7ff5 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/AssumeInserter.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/AssumeInserter.java
@@ -6,11 +6,12 @@
 import static com.android.tools.r8.ir.analysis.type.Nullability.definitelyNotNull;
 import static com.google.common.base.Predicates.alwaysTrue;
 
-import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
 import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexClassAndField;
 import com.android.tools.r8.graph.DexClassAndMethod;
-import com.android.tools.r8.graph.DexEncodedField;
 import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.FieldResolutionResult.SuccessfulFieldResolutionResult;
+import com.android.tools.r8.graph.ResolutionResult.SingleResolutionResult;
 import com.android.tools.r8.ir.analysis.type.ClassTypeElement;
 import com.android.tools.r8.ir.analysis.type.TypeAnalysis;
 import com.android.tools.r8.ir.analysis.type.TypeElement;
@@ -33,7 +34,9 @@
 import com.android.tools.r8.ir.code.Value;
 import com.android.tools.r8.ir.optimize.info.FieldOptimizationInfo;
 import com.android.tools.r8.ir.optimize.info.MethodOptimizationInfo;
-import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.ir.optimize.membervaluepropagation.assume.AssumeInfo;
+import com.android.tools.r8.ir.optimize.membervaluepropagation.assume.AssumeInfoLookup;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.utils.Timing;
 import com.android.tools.r8.utils.TriConsumer;
 import com.android.tools.r8.utils.TriFunction;
@@ -56,12 +59,10 @@
 
 public class AssumeInserter {
 
-  private final AppView<? extends AppInfoWithClassHierarchy> appView;
-  private final InternalOptions options;
+  private final AppView<AppInfoWithLiveness> appView;
 
-  public AssumeInserter(AppView<? extends AppInfoWithClassHierarchy> appView) {
+  public AssumeInserter(AppView<AppInfoWithLiveness> appView) {
     this.appView = appView;
-    this.options = appView.options();
   }
 
   public void insertAssumeInstructions(IRCode code, Timing timing) {
@@ -225,7 +226,26 @@
 
   private boolean computeAssumedValuesFromSingleTarget(
       IRCode code, InvokeMethod invoke, AssumedValues.Builder assumedValuesBuilder) {
+    SingleResolutionResult resolutionResult =
+        appView
+            .appInfo()
+            .unsafeResolveMethodDueToDexFormat(invoke.getInvokedMethod())
+            .asSingleResolution();
+    if (resolutionResult == null) {
+      return false;
+    }
+
     DexClassAndMethod singleTarget = invoke.lookupSingleTarget(appView, code.context());
+    if (invoke.hasUsedOutValue() && invoke.getOutType().isReferenceType()) {
+      AssumeInfo assumeInfo =
+          AssumeInfoLookup.lookupAssumeInfo(appView, resolutionResult, singleTarget);
+      if (assumeInfo != null
+          && assumeInfo.hasReturnInfo()
+          && assumeInfo.getReturnInfo().isNonNull()) {
+        assumedValuesBuilder.addNonNullValueKnownToDominateAllUsers(invoke, invoke.outValue());
+      }
+    }
+
     if (singleTarget == null) {
       return false;
     }
@@ -234,12 +254,11 @@
     MethodOptimizationInfo optimizationInfo = singleTarget.getDefinition().getOptimizationInfo();
 
     // Case (2), invocations that are guaranteed to return a non-null value.
-    Value outValue = invoke.outValue();
-    if (outValue != null && outValue.hasNonDebugUsers()) {
+    if (invoke.hasUsedOutValue()) {
       needsAssumeInstruction =
           computeAssumedValuesForOutValue(
               invoke,
-              optimizationInfo.getDynamicUpperBoundTypeOrElse(outValue.getType()),
+              optimizationInfo.getDynamicUpperBoundTypeOrElse(invoke.getOutType()),
               optimizationInfo.getDynamicLowerBoundType(),
               assumedValuesBuilder);
     }
@@ -264,20 +283,31 @@
 
   private boolean computeAssumedValuesForFieldGet(
       FieldInstruction fieldGet, AssumedValues.Builder assumedValuesBuilder) {
-    Value outValue = fieldGet.outValue();
-    if (!outValue.hasNonDebugUsers()) {
+    if (fieldGet.hasUnusedOutValue()) {
       return false;
     }
 
-    DexEncodedField field = appView.appInfo().resolveField(fieldGet.getField()).getResolvedField();
-    if (field == null) {
+    SuccessfulFieldResolutionResult resolutionResult =
+        appView.appInfo().resolveField(fieldGet.getField()).asSuccessfulResolution();
+    if (resolutionResult == null) {
       return false;
     }
 
-    FieldOptimizationInfo optimizationInfo = field.getOptimizationInfo();
+    DexClassAndField field = resolutionResult.getResolutionPair();
+
+    if (field.getType().isReferenceType()) {
+      AssumeInfo assumeInfo = AssumeInfoLookup.lookupAssumeInfo(appView, field);
+      if (assumeInfo != null
+          && assumeInfo.hasReturnInfo()
+          && assumeInfo.getReturnInfo().isNonNull()) {
+        assumedValuesBuilder.addNonNullValueKnownToDominateAllUsers(fieldGet, fieldGet.outValue());
+      }
+    }
+
+    FieldOptimizationInfo optimizationInfo = field.getDefinition().getOptimizationInfo();
     return computeAssumedValuesForOutValue(
         fieldGet,
-        optimizationInfo.getDynamicUpperBoundTypeOrElse(outValue.getType()),
+        optimizationInfo.getDynamicUpperBoundTypeOrElse(fieldGet.getOutType()),
         optimizationInfo.getDynamicLowerBoundType(),
         assumedValuesBuilder);
   }
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 89d9eb7..5112116 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
@@ -954,7 +954,10 @@
     return rewriteSwitch(code, SwitchCaseAnalyzer.getInstance());
   }
 
-  public boolean rewriteSwitch(IRCode code, SwitchCaseAnalyzer switchCaseAnalyzer) {
+  private boolean rewriteSwitch(IRCode code, SwitchCaseAnalyzer switchCaseAnalyzer) {
+    if (!options.isSwitchRewritingEnabled()) {
+      return false;
+    }
     if (!code.metadata().mayHaveSwitch()) {
       return false;
     }
@@ -1473,19 +1476,16 @@
       UtilityMethodForCodeOptimizations throwClassCastExceptionIfNotNullMethod =
           UtilityMethodsForCodeOptimizations.synthesizeThrowClassCastExceptionIfNotNullMethod(
               appView, context, methodProcessingId);
-      // TODO(b/172194277): Allow synthetics when generating CF.
-      if (throwClassCastExceptionIfNotNullMethod != null) {
-        throwClassCastExceptionIfNotNullMethod.optimize(methodProcessor);
-        InvokeStatic replacement =
-            InvokeStatic.builder()
-                .setMethod(throwClassCastExceptionIfNotNullMethod.getMethod())
-                .setSingleArgument(checkCast.object())
-                .setPosition(checkCast)
-                .build();
-        it.replaceCurrentInstruction(replacement);
-        assert replacement.lookupSingleTarget(appView, context) != null;
-        return RemoveCheckCastInstructionIfTrivialResult.REMOVED_CAST_DO_NARROW;
-      }
+      throwClassCastExceptionIfNotNullMethod.optimize(methodProcessor);
+      InvokeStatic replacement =
+          InvokeStatic.builder()
+              .setMethod(throwClassCastExceptionIfNotNullMethod.getMethod())
+              .setSingleArgument(checkCast.object())
+              .setPosition(checkCast)
+              .build();
+      it.replaceCurrentInstruction(replacement);
+      assert replacement.lookupSingleTarget(appView, context) != null;
+      return RemoveCheckCastInstructionIfTrivialResult.REMOVED_CAST_DO_NARROW;
     }
 
     // Otherwise, keep the checkcast to preserve verification errors. E.g., down-cast:
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/Devirtualizer.java b/src/main/java/com/android/tools/r8/ir/optimize/Devirtualizer.java
index 674488f..7179327 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/Devirtualizer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/Devirtualizer.java
@@ -25,6 +25,7 @@
 import com.android.tools.r8.ir.code.InvokeVirtual;
 import com.android.tools.r8.ir.code.Value;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.shaking.MainDexTracingResult;
 import com.android.tools.r8.utils.InternalOptions;
 import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.ImmutableSet;
@@ -47,10 +48,13 @@
 public class Devirtualizer {
 
   private final AppView<AppInfoWithLiveness> appView;
+  private final MainDexTracingResult mainDexTracingResult;
   private final InternalOptions options;
 
-  public Devirtualizer(AppView<AppInfoWithLiveness> appView) {
+  public Devirtualizer(
+      AppView<AppInfoWithLiveness> appView, MainDexTracingResult mainDexTracingResult) {
     this.appView = appView;
+    this.mainDexTracingResult = mainDexTracingResult;
     this.options = appView.options();
   }
 
@@ -148,7 +152,9 @@
           // Rebind the invoke to the most specific target.
           DexMethod invokedMethod = invoke.getInvokedMethod();
           DexClassAndMethod reboundTarget = rebindSuperInvokeToMostSpecific(invokedMethod, context);
-          if (reboundTarget != null && reboundTarget.getReference() != invokedMethod) {
+          if (reboundTarget != null
+              && reboundTarget.getReference() != invokedMethod
+              && !isRebindingNewClassIntoMainDex(invokedMethod, reboundTarget.getReference())) {
             it.replaceCurrentInstruction(
                 new InvokeSuper(
                     reboundTarget.getReference(),
@@ -190,6 +196,11 @@
           continue;
         }
 
+        // Ensure that we are not adding a new main dex root
+        if (isRebindingNewClassIntoMainDex(invoke.getInvokedMethod(), target.getReference())) {
+          continue;
+        }
+
         InvokeVirtual devirtualizedInvoke =
             new InvokeVirtual(target.getReference(), invoke.outValue(), invoke.inValues());
         it.replaceCurrentInstruction(devirtualizedInvoke);
@@ -382,4 +393,14 @@
     // Change the invoke-virtual instruction to target the refined resolution result instead.
     return newResolutionResult.getResolvedMethod().method;
   }
+
+  private boolean isRebindingNewClassIntoMainDex(
+      DexMethod originalMethod, DexMethod reboundMethod) {
+    if (!mainDexTracingResult.isRoot(originalMethod.holder)
+        && !appView.appInfo().getMainDexClasses().contains(originalMethod.holder)) {
+      return false;
+    }
+    return !mainDexTracingResult.isRoot(reboundMethod.holder)
+        && !appView.appInfo().getMainDexClasses().contains(reboundMethod.holder);
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java b/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java
index 1cce76e..38c4e6f 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java
@@ -49,7 +49,6 @@
 import com.android.tools.r8.ir.conversion.LensCodeRewriter;
 import com.android.tools.r8.ir.conversion.MethodProcessor;
 import com.android.tools.r8.ir.conversion.PostOptimization;
-import com.android.tools.r8.ir.desugar.TwrCloseResourceRewriter;
 import com.android.tools.r8.ir.optimize.info.OptimizationFeedback;
 import com.android.tools.r8.ir.optimize.info.OptimizationFeedbackIgnore;
 import com.android.tools.r8.ir.optimize.inliner.DefaultInliningReasonStrategy;
@@ -130,9 +129,7 @@
     }
 
     if (extraNeverInlineMethods.contains(
-            appView.graphLens().getOriginalMethodSignature(singleTargetReference))
-        || TwrCloseResourceRewriter.isSynthesizedCloseResourceMethod(
-            singleTargetReference, appView)) {
+        appView.graphLens().getOriginalMethodSignature(singleTargetReference))) {
       whyAreYouNotInliningReporter.reportExtraNeverInline();
       return true;
     }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/MemberValuePropagation.java b/src/main/java/com/android/tools/r8/ir/optimize/MemberValuePropagation.java
index 2950d4c..d92d375 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/MemberValuePropagation.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/MemberValuePropagation.java
@@ -7,19 +7,20 @@
 import static com.google.common.base.Predicates.alwaysTrue;
 
 import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexClassAndField;
 import com.android.tools.r8.graph.DexClassAndMethod;
-import com.android.tools.r8.graph.DexDefinition;
 import com.android.tools.r8.graph.DexEncodedField;
-import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.graph.DexMethod;
-import com.android.tools.r8.graph.DexReference;
 import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.FieldResolutionResult.SuccessfulFieldResolutionResult;
 import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.graph.ResolutionResult.SingleResolutionResult;
 import com.android.tools.r8.ir.analysis.type.TypeAnalysis;
 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.SingleValue;
+import com.android.tools.r8.ir.analysis.value.UnknownValue;
 import com.android.tools.r8.ir.code.BasicBlock;
 import com.android.tools.r8.ir.code.FieldInstruction;
 import com.android.tools.r8.ir.code.IRCode;
@@ -34,8 +35,9 @@
 import com.android.tools.r8.ir.code.Value;
 import com.android.tools.r8.ir.optimize.info.OptimizationFeedback;
 import com.android.tools.r8.ir.optimize.info.OptimizationFeedbackSimple;
+import com.android.tools.r8.ir.optimize.membervaluepropagation.assume.AssumeInfo;
+import com.android.tools.r8.ir.optimize.membervaluepropagation.assume.AssumeInfoLookup;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
-import com.android.tools.r8.shaking.ProguardMemberRule;
 import com.android.tools.r8.shaking.ProguardMemberRuleReturnValue;
 import com.android.tools.r8.utils.Reporter;
 import com.android.tools.r8.utils.StringDiagnostic;
@@ -54,48 +56,17 @@
   // Fields for which we have reported warnings to due Proguard configuration rules.
   private final Set<DexField> warnedFields = Sets.newIdentityHashSet();
 
-  private enum RuleType {
-    NONE,
-    ASSUME_NO_SIDE_EFFECTS,
-    ASSUME_VALUES
-  }
-
-  private static class ProguardMemberRuleLookup {
-
-    final RuleType type;
-    final ProguardMemberRule rule;
-
-    ProguardMemberRuleLookup(RuleType type, ProguardMemberRule rule) {
-      this.type = type;
-      this.rule = rule;
-    }
-
-    @Override
-    public boolean equals(Object other) {
-      if (!(other instanceof ProguardMemberRuleLookup)) {
-        return false;
-      }
-      ProguardMemberRuleLookup otherLookup = (ProguardMemberRuleLookup) other;
-      return type == otherLookup.type && rule == otherLookup.rule;
-    }
-
-    @Override
-    public int hashCode() {
-      return type.ordinal() * 31 + rule.hashCode();
-    }
-  }
-
   public MemberValuePropagation(AppView<AppInfoWithLiveness> appView) {
     this.appView = appView;
     this.reporter = appView.options().reporter;
   }
 
-  private boolean mayPropagateValueFor(DexEncodedField field) {
-    if (field.isProgramField(appView)) {
-      return appView.appInfo().mayPropagateValueFor(field.field);
+  private boolean mayPropagateValueFor(DexClassAndField field) {
+    if (field.isProgramField()) {
+      return appView.appInfo().mayPropagateValueFor(field.getReference());
     }
-    return appView.appInfo().assumedValues.containsKey(field.field)
-        || appView.appInfo().noSideEffects.containsKey(field.field);
+    return appView.appInfo().assumedValues.containsKey(field.getReference())
+        || appView.appInfo().noSideEffects.containsKey(field.getReference());
   }
 
   private boolean mayPropagateValueFor(DexClassAndMethod method) {
@@ -106,36 +77,25 @@
         || appView.appInfo().noSideEffects.containsKey(method.getReference());
   }
 
-  private ProguardMemberRuleLookup lookupMemberRule(DexClassAndMethod method) {
-    return method != null ? lookupMemberRule(method.getDefinition()) : null;
-  }
-
-  private ProguardMemberRuleLookup lookupMemberRule(DexDefinition definition) {
-    if (definition == null) {
-      return null;
-    }
-    DexReference reference = definition.getReference();
-    ProguardMemberRule rule = appView.appInfo().noSideEffects.get(reference);
-    if (rule != null) {
-      return new ProguardMemberRuleLookup(RuleType.ASSUME_NO_SIDE_EFFECTS, rule);
-    }
-    rule = appView.appInfo().assumedValues.get(reference);
-    if (rule != null) {
-      return new ProguardMemberRuleLookup(RuleType.ASSUME_VALUES, rule);
-    }
-    return null;
-  }
-
-  private Instruction constantReplacementFromProguardRule(
-      ProguardMemberRule rule, IRCode code, Instruction instruction) {
-    if (rule == null || !rule.hasReturnValue()) {
+  private Instruction createReplacementFromAssumeInfo(
+      AssumeInfo assumeInfo, IRCode code, Instruction instruction) {
+    if (!assumeInfo.hasReturnInfo()) {
       return null;
     }
 
-    ProguardMemberRuleReturnValue returnValueRule = rule.getReturnValue();
+    ProguardMemberRuleReturnValue returnValueRule = assumeInfo.getReturnInfo();
 
     // Check if this value can be assumed constant.
     if (returnValueRule.isSingleValue()) {
+      if (instruction.getOutType().isReferenceType()) {
+        if (returnValueRule.getSingleValue() == 0) {
+          return appView
+              .abstractValueFactory()
+              .createNullValue()
+              .createMaterializingInstruction(appView, code, instruction);
+        }
+        return null;
+      }
       return appView.abstractValueFactory()
           .createSingleNumberValue(returnValueRule.getSingleValue())
           .createMaterializingInstruction(appView, code, instruction);
@@ -177,31 +137,33 @@
     return null;
   }
 
-  private void setValueRangeFromProguardRule(ProguardMemberRule rule, Value value) {
-    if (rule.hasReturnValue() && rule.getReturnValue().isValueRange()) {
-      assert !rule.getReturnValue().isSingleValue();
-      value.setValueRange(rule.getReturnValue().getValueRange());
+  private void setValueRangeFromAssumeInfo(AssumeInfo assumeInfo, Value value) {
+    if (assumeInfo.hasReturnInfo() && assumeInfo.getReturnInfo().isValueRange()) {
+      assert !assumeInfo.getReturnInfo().isSingleValue();
+      value.setValueRange(assumeInfo.getReturnInfo().getValueRange());
     }
   }
 
-  private boolean tryConstantReplacementFromProguard(
+  private boolean applyAssumeInfoIfPossible(
       IRCode code,
       Set<Value> affectedValues,
       ListIterator<BasicBlock> blocks,
       InstructionListIterator iterator,
       Instruction current,
-      ProguardMemberRuleLookup lookup) {
-    Instruction replacement = constantReplacementFromProguardRule(lookup.rule, code, current);
+      AssumeInfo assumeInfo) {
+    Instruction replacement = createReplacementFromAssumeInfo(assumeInfo, code, current);
     if (replacement == null) {
       // Check to see if a value range can be assumed.
-      setValueRangeFromProguardRule(lookup.rule, current.outValue());
+      if (current.getOutType().isPrimitiveType()) {
+        setValueRangeFromAssumeInfo(assumeInfo, current.outValue());
+      }
       return false;
     }
     affectedValues.addAll(current.outValue().affectedValues());
-    if (lookup.type == RuleType.ASSUME_NO_SIDE_EFFECTS) {
+    if (assumeInfo.isAssumeNoSideEffects()) {
       iterator.replaceCurrentInstruction(replacement);
     } else {
-      assert lookup.type == RuleType.ASSUME_VALUES;
+      assert assumeInfo.isAssumeValues();
       BasicBlock block = current.getBlock();
       Position position = current.getPosition();
       if (current.hasOutValue()) {
@@ -240,43 +202,33 @@
       return;
     }
 
-    DexClassAndMethod singleTarget = invoke.lookupSingleTarget(appView, context);
-    ProguardMemberRuleLookup lookup = lookupMemberRule(singleTarget);
-    if (lookup == null) {
-      // -assumenosideeffects rules are applied to upward visible and overriding methods, but only
-      // references that have actual definitions are marked by the root set builder. So, here, we
-      // try again with a resolved target, not the direct definition, which may not exist.
-      DexEncodedMethod resolutionTarget =
-          appView.appInfo().unsafeResolveMethodDueToDexFormat(invokedMethod).getSingleTarget();
-      lookup = lookupMemberRule(resolutionTarget);
-    }
-
-    if (lookup != null) {
-      // Check to see if a constant value can be assumed.
-      // But, if the current matched rule is -assumenosideeffects without the return value, it won't
-      // be transformed into a replacement instruction. Check if there is -assumevalues rule bound
-      // to the target.
-      if (singleTarget != null
-          && lookup.type == RuleType.ASSUME_NO_SIDE_EFFECTS
-          && !lookup.rule.hasReturnValue()) {
-        ProguardMemberRule rule = appView.appInfo().assumedValues.get(singleTarget.getReference());
-        if (rule != null) {
-          lookup = new ProguardMemberRuleLookup(RuleType.ASSUME_VALUES, rule);
-        }
-      }
-      if (tryConstantReplacementFromProguard(
-          code, affectedValues, blocks, iterator, invoke, lookup)) {
-        return;
-      }
-    }
-
-    // No Proguard rule could replace the instruction check for knowledge about the return value.
-    if (singleTarget == null || !mayPropagateValueFor(singleTarget)) {
+    SingleResolutionResult resolutionResult =
+        appView.appInfo().unsafeResolveMethodDueToDexFormat(invokedMethod).asSingleResolution();
+    if (resolutionResult == null) {
       return;
     }
 
-    AbstractValue abstractReturnValue =
-        singleTarget.getDefinition().getOptimizationInfo().getAbstractReturnValue();
+    DexClassAndMethod singleTarget = invoke.lookupSingleTarget(appView, context);
+    AssumeInfo lookup = AssumeInfoLookup.lookupAssumeInfo(appView, resolutionResult, singleTarget);
+    if (lookup != null
+        && applyAssumeInfoIfPossible(code, affectedValues, blocks, iterator, invoke, lookup)) {
+      return;
+    }
+
+    // No Proguard rule could replace the instruction check for knowledge about the return value.
+    if (singleTarget != null && !mayPropagateValueFor(singleTarget)) {
+      return;
+    }
+
+    AbstractValue abstractReturnValue;
+    if (invokedMethod.getReturnType().isAlwaysNull(appView)) {
+      abstractReturnValue = appView.abstractValueFactory().createNullValue();
+    } else if (singleTarget != null) {
+      abstractReturnValue =
+          singleTarget.getDefinition().getOptimizationInfo().getAbstractReturnValue();
+    } else {
+      abstractReturnValue = UnknownValue.getInstance();
+    }
 
     if (abstractReturnValue.isSingleValue()) {
       SingleValue singleReturnValue = abstractReturnValue.asSingleValue();
@@ -293,7 +245,7 @@
 
         if (invoke.isInvokeMethodWithReceiver()) {
           iterator.replaceCurrentInstructionByNullCheckIfPossible(appView, context);
-        } else if (invoke.isInvokeStatic()) {
+        } else if (invoke.isInvokeStatic() && singleTarget != null) {
           iterator.replaceCurrentInstructionByInitClassIfPossible(
               appView, code, singleTarget.getHolderType());
         }
@@ -308,7 +260,10 @@
         } else {
           iterator.add(replacement);
         }
-        singleTarget.getDefinition().getMutableOptimizationInfo().markAsPropagated();
+
+        if (singleTarget != null) {
+          singleTarget.getDefinition().getMutableOptimizationInfo().markAsPropagated();
+        }
       }
     }
   }
@@ -322,8 +277,9 @@
     DexField field = current.getField();
 
     // TODO(b/123857022): Should be able to use definitionFor().
-    DexEncodedField target = appView.appInfo().resolveField(field).getResolvedField();
-    if (target == null) {
+    SuccessfulFieldResolutionResult resolutionResult =
+        appView.appInfo().resolveField(field).asSuccessfulResolution();
+    if (resolutionResult == null) {
       boolean replaceCurrentInstructionWithConstNull =
           appView.withGeneratedExtensionRegistryShrinker(
               shrinker -> shrinker.wasRemoved(field), false);
@@ -333,7 +289,9 @@
       return;
     }
 
-    if (target.isStatic() != current.isStaticGet()) {
+    DexClassAndField target = resolutionResult.getResolutionPair();
+    DexEncodedField definition = target.getDefinition();
+    if (definition.isStatic() != current.isStaticGet()) {
       return;
     }
 
@@ -342,33 +300,35 @@
     }
 
     // Check if there is a Proguard configuration rule that specifies the value of the field.
-    ProguardMemberRuleLookup lookup = lookupMemberRule(target);
+    AssumeInfo lookup = AssumeInfoLookup.lookupAssumeInfo(appView, target);
     if (lookup != null
-        && tryConstantReplacementFromProguard(
-            code, affectedValues, blocks, iterator, current, lookup)) {
+        && applyAssumeInfoIfPossible(code, affectedValues, blocks, iterator, current, lookup)) {
       return;
     }
 
     AbstractValue abstractValue;
     if (field.getType().isAlwaysNull(appView)) {
       abstractValue = appView.abstractValueFactory().createSingleNumberValue(0);
-    } else if (appView.appInfo().isFieldWrittenByFieldPutInstruction(target)) {
-      abstractValue = target.getOptimizationInfo().getAbstractValue();
-      if (abstractValue.isUnknown() && !target.isStatic()) {
+    } else if (appView.appInfo().isFieldWrittenByFieldPutInstruction(definition)) {
+      abstractValue = definition.getOptimizationInfo().getAbstractValue();
+      if (abstractValue.isUnknown() && !definition.isStatic()) {
         AbstractValue abstractReceiverValue =
             current.asInstanceGet().object().getAbstractValue(appView, code.context());
         if (abstractReceiverValue.isSingleFieldValue()) {
           abstractValue =
-              abstractReceiverValue.asSingleFieldValue().getState().getAbstractFieldValue(target);
+              abstractReceiverValue
+                  .asSingleFieldValue()
+                  .getState()
+                  .getAbstractFieldValue(definition);
         }
       }
-    } else if (target.isStatic()) {
+    } else if (definition.isStatic()) {
       // This is guaranteed to read the static value of the field.
-      abstractValue = target.getStaticValue().toAbstractValue(appView.abstractValueFactory());
+      abstractValue = definition.getStaticValue().toAbstractValue(appView.abstractValueFactory());
       // Verify that the optimization info is consistent with the static value.
-      assert target.getOptimizationInfo().getAbstractValue().isUnknown()
-          || !target.hasExplicitStaticValue()
-          || abstractValue == target.getOptimizationInfo().getAbstractValue();
+      assert definition.getOptimizationInfo().getAbstractValue().isUnknown()
+          || !definition.hasExplicitStaticValue()
+          || abstractValue == definition.getOptimizationInfo().getAbstractValue();
     } else {
       // This is guaranteed to read the default value of the field.
       abstractValue = appView.abstractValueFactory().createSingleNumberValue(0);
@@ -411,7 +371,8 @@
         } else {
           iterator.add(replacement);
         }
-        feedback.markFieldAsPropagated(target);
+
+        feedback.markFieldAsPropagated(definition);
       }
     }
   }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/NestReducer.java b/src/main/java/com/android/tools/r8/ir/optimize/NestReducer.java
index 6fa08c8..e20e28c 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/NestReducer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/NestReducer.java
@@ -4,131 +4,102 @@
 
 package com.android.tools.r8.ir.optimize;
 
+import static com.android.tools.r8.graph.DexProgramClass.asProgramClassOrNull;
+
 import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.DexClass;
-import com.android.tools.r8.graph.DexEncodedField;
-import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexEncodedMember;
 import com.android.tools.r8.graph.DexProgramClass;
-import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.graph.NestMemberClassAttribute;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.utils.BooleanBox;
+import com.android.tools.r8.utils.IterableUtils;
 import com.android.tools.r8.utils.ThreadUtils;
+import com.android.tools.r8.utils.Timing;
 import com.google.common.collect.Sets;
-import java.util.ArrayList;
-import java.util.Iterator;
-import java.util.List;
 import java.util.Set;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Future;
 
-// This pass
-// - cleans the nests: it removes missing nest host/members from the input and
-// report them (warning or error).
-// - clears nests which do not use nest based access control to allow other
-// optimizations such as class merging to perform better.
+/**
+ * This pass:
+ *
+ * <ul>
+ *   <li>cleans the nests: it removes missing nest host/members from the input,
+ *   <li>clears nests which do not use nest based access control to allow other optimizations such
+ *       as class merging to perform better.
+ * </ul>
+ */
 public class NestReducer {
 
-  private AppView<?> appView;
+  private AppView<AppInfoWithLiveness> appView;
 
-  public NestReducer(AppView<?> appView) {
+  public NestReducer(AppView<AppInfoWithLiveness> appView) {
     this.appView = appView;
   }
 
-  private DexClass definitionFor(DexType type) {
-    assert appView.graphLens().lookupType(type) == type;
-    return appView.definitionFor(appView.graphLens().lookupType(type));
+  public void run(ExecutorService executorService, Timing timing) throws ExecutionException {
+    timing.begin("NestReduction");
+    if (appView.options().shouldDesugarNests()) {
+      removeNests();
+    } else {
+      reduceNests(executorService);
+    }
+    timing.end();
   }
 
-  public void run(ExecutorService executorService) throws ExecutionException {
-    Set<DexType> nestHosts = Sets.newIdentityHashSet();
-    List<Future<?>> futures = new ArrayList<>();
-    // It is possible that a nest member is on the program path but its nest host
-    // is only in the class path.
-    // Nests are therefore computed the first time a nest member is met, host or not.
-    // The computedNestHosts list is there to avoid processing multiple times the same nest.
+  private void removeNests() {
     for (DexProgramClass clazz : appView.appInfo().classes()) {
-      DexType hostType = clazz.getNestHost();
-      if (hostType != null && !nestHosts.contains(hostType)) {
-        nestHosts.add(hostType);
-        futures.add(
-            executorService.submit(
-                () -> {
-                  processNestFrom(clazz);
-                  return null; // we want a Callable not a Runnable to be able to throw
-                }));
-      }
-    }
-    ThreadUtils.awaitFutures(futures);
-  }
-
-  private void processNestFrom(DexClass clazz) {
-    DexClass nestHost = definitionFor(clazz.getNestHost());
-    if (nestHost == null) {
-      reportMissingNestHost(clazz);
-      clazz.clearNestHost();
-      return;
-    }
-    boolean hasPrivateMembers = hasPrivateMembers(nestHost);
-    Iterator<NestMemberClassAttribute> iterator =
-        nestHost.getNestMembersClassAttributes().iterator();
-    boolean reported = false;
-    while (iterator.hasNext()) {
-      DexClass member = definitionFor(iterator.next().getNestMember());
-      if (member == null) {
-        if (!reported) {
-          reported = true;
-          reportIncompleteNest(nestHost);
+      if (clazz.isInANest()) {
+        if (clazz.isNestHost()) {
+          clazz.clearNestMembers();
+        } else {
+          clazz.clearNestHost();
         }
-        iterator.remove();
-      } else {
-        hasPrivateMembers = hasPrivateMembers || hasPrivateMembers(member);
       }
     }
-    if (!hasPrivateMembers && appView.options().enableNestReduction) {
-      clearNestAttributes(nestHost);
-    }
   }
 
-  private void reportMissingNestHost(DexClass clazz) {
-    if (appView.options().ignoreMissingClasses) {
-      appView.options().warningMissingClassMissingNestHost(clazz);
-    } else {
-      appView.options().errorMissingClassMissingNestHost(clazz);
-    }
-  }
-
-  private void reportIncompleteNest(DexClass nestHost) {
-    List<DexType> nest = new ArrayList<>(nestHost.getNestMembersClassAttributes().size() + 1);
-    for (NestMemberClassAttribute attr : nestHost.getNestMembersClassAttributes()) {
-      nest.add(attr.getNestMember());
-    }
-    nest.add(nestHost.type);
-    if (appView.options().ignoreMissingClasses) {
-      appView.options().warningMissingClassIncompleteNest(nest, appView);
-    } else {
-      appView.options().errorMissingClassIncompleteNest(nest, appView);
-    }
-  }
-
-  private void clearNestAttributes(DexClass nestHost) {
-    nestHost.getNestMembersClassAttributes().clear();
-    for (NestMemberClassAttribute attr : nestHost.getNestMembersClassAttributes()) {
-      DexClass member = appView.definitionFor(appView.graphLens().lookupType(attr.getNestMember()));
-      member.clearNestHost();
-    }
-  }
-
-  private boolean hasPrivateMembers(DexClass clazz) {
-    for (DexEncodedMethod method : clazz.methods()) {
-      if (method.accessFlags.isPrivate()) {
-        return true;
+  private void reduceNests(ExecutorService executorService) throws ExecutionException {
+    Set<DexProgramClass> nestHosts = Sets.newIdentityHashSet();
+    Set<DexProgramClass> nestMembers = Sets.newIdentityHashSet();
+    for (DexProgramClass clazz : appView.appInfo().classes()) {
+      if (clazz.isInANest()) {
+        if (clazz.isNestHost()) {
+          nestHosts.add(clazz);
+        } else {
+          nestMembers.add(clazz);
+        }
       }
     }
-    for (DexEncodedField field : clazz.fields()) {
-      if (field.accessFlags.isPrivate()) {
-        return true;
-      }
+    ThreadUtils.processItems(nestHosts, this::processNestHost, executorService);
+    ThreadUtils.processItems(nestMembers, this::processNestMember, executorService);
+  }
+
+  private void processNestHost(DexProgramClass clazz) {
+    BooleanBox nestHasPrivateMembers =
+        new BooleanBox(IterableUtils.hasNext(clazz.members(DexEncodedMember::isPrivate)));
+    clazz
+        .getNestMembersClassAttributes()
+        .removeIf(
+            attribute -> {
+              DexProgramClass member =
+                  asProgramClassOrNull(appView.definitionFor(attribute.getNestMember(), clazz));
+              if (member == null) {
+                return true;
+              }
+              nestHasPrivateMembers.computeIfNotSet(
+                  () -> IterableUtils.hasNext(member.members(DexEncodedMember::isPrivate)));
+              return false;
+            });
+    if (nestHasPrivateMembers.isFalse() && appView.options().enableNestReduction) {
+      clazz.getNestMembersClassAttributes().clear();
     }
-    return false;
+  }
+
+  private void processNestMember(DexProgramClass clazz) {
+    DexProgramClass hostClass =
+        asProgramClassOrNull(appView.definitionFor(clazz.getNestHost(), clazz));
+    if (hostClass == null || !hostClass.isNestHost()) {
+      clazz.clearNestHost();
+    }
   }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/Outliner.java b/src/main/java/com/android/tools/r8/ir/optimize/Outliner.java
index f673395..11b4ddf 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/Outliner.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/Outliner.java
@@ -955,7 +955,7 @@
         return true;
       }
       if (arrayBaseType.isClassType()) {
-        return arrayBaseType.asClassType().getInterfaces().size() == 0;
+        return arrayBaseType.asClassType().getInterfaces().isEmpty();
       }
       return false;
     }
@@ -972,8 +972,8 @@
         // have a common super interface nor are they implemented by a common superclass so the
         // argument type of the outline will be java.lang.Object.
         if (valueClassType.getClassType() == objectType
-            && valueClassType.getInterfaces().size() == 1) {
-          return valueClassType.getInterfaces().iterator().next();
+            && valueClassType.getInterfaces().hasSingleKnownInterface()) {
+          return valueClassType.getInterfaces().getSingleKnownInterface();
         } else {
           return valueClassType.getClassType();
         }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/ServiceLoaderRewriter.java b/src/main/java/com/android/tools/r8/ir/optimize/ServiceLoaderRewriter.java
index 1694a7b..efc440b 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/ServiceLoaderRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/ServiceLoaderRewriter.java
@@ -4,25 +4,15 @@
 
 package com.android.tools.r8.ir.optimize;
 
-import com.android.tools.r8.dex.Constants;
 import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.ClassAccessFlags;
-import com.android.tools.r8.graph.DexAnnotationSet;
 import com.android.tools.r8.graph.DexClass;
-import com.android.tools.r8.graph.DexEncodedField;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexMethod;
-import com.android.tools.r8.graph.DexProgramClass;
-import com.android.tools.r8.graph.DexProgramClass.ChecksumSupplier;
 import com.android.tools.r8.graph.DexProto;
 import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.graph.DexTypeList;
-import com.android.tools.r8.graph.GenericSignature.ClassSignature;
-import com.android.tools.r8.graph.GenericSignature.MethodTypeSignature;
 import com.android.tools.r8.graph.MethodAccessFlags;
-import com.android.tools.r8.graph.MethodCollection;
-import com.android.tools.r8.graph.ParameterAnnotationsList;
+import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.code.ConstClass;
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.Instruction;
@@ -32,15 +22,13 @@
 import com.android.tools.r8.ir.code.Value;
 import com.android.tools.r8.ir.conversion.MethodProcessingId;
 import com.android.tools.r8.ir.desugar.ServiceLoaderSourceCode;
-import com.android.tools.r8.origin.SynthesizedOrigin;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.synthesis.SyntheticNaming.SyntheticKind;
 import com.google.common.collect.ImmutableList;
 import java.util.ArrayList;
-import java.util.Collections;
 import java.util.IdentityHashMap;
 import java.util.List;
 import java.util.Map;
-import java.util.concurrent.atomic.AtomicReference;
 
 /**
  * ServiceLoaderRewriter will attempt to rewrite calls on the form of: ServiceLoader.load(X.class,
@@ -71,19 +59,15 @@
  */
 public class ServiceLoaderRewriter {
 
-  public static final String SERVICE_LOADER_CLASS_NAME = "$$ServiceLoaderMethods";
-  private static final String SERVICE_LOADER_METHOD_PREFIX_NAME = "$load";
-
-  private AtomicReference<DexProgramClass> synthesizedClass = new AtomicReference<>();
-
   private final AppView<AppInfoWithLiveness> appView;
+  private final List<ProgramMethod> serviceLoadMethods = new ArrayList<>();
 
   public ServiceLoaderRewriter(AppView<AppInfoWithLiveness> appView) {
     this.appView = appView;
   }
 
-  public DexProgramClass getSynthesizedClass() {
-    return synthesizedClass.get();
+  public List<ProgramMethod> getServiceLoadMethods() {
+    return serviceLoadMethods;
   }
 
   public void rewrite(IRCode code, MethodProcessingId methodProcessingId) {
@@ -186,7 +170,7 @@
               constClass.getValue(),
               service -> {
                 DexEncodedMethod addedMethod =
-                    createSynthesizedMethod(service, classes, methodProcessingId);
+                    createSynthesizedMethod(service, classes, methodProcessingId, code.context());
                 if (appView.options().isGeneratingClassFiles()) {
                   addedMethod.upgradeClassFileVersion(code.method().getClassFileVersion());
                 }
@@ -199,75 +183,31 @@
   }
 
   private DexEncodedMethod createSynthesizedMethod(
-      DexType serviceType, List<DexClass> classes, MethodProcessingId methodProcessingId) {
-    MethodCollection methodCollection = getOrSetSynthesizedClass().getMethodCollection();
-    String methodNamePrefix = SERVICE_LOADER_METHOD_PREFIX_NAME + "$";
+      DexType serviceType,
+      List<DexClass> classes,
+      MethodProcessingId methodProcessingId,
+      ProgramMethod context) {
     DexProto proto = appView.dexItemFactory().createProto(appView.dexItemFactory().iteratorType);
-    synchronized (methodCollection) {
-      DexMethod methodReference;
-      do {
-        methodReference =
-            appView
-                .dexItemFactory()
-                .createMethod(
-                    appView.dexItemFactory().serviceLoaderRewrittenClassType,
-                    proto,
-                    methodNamePrefix + methodProcessingId.getAndIncrementId());
-      } while (methodCollection.getMethod(methodReference) != null);
-      DexEncodedMethod method =
-          new DexEncodedMethod(
-              methodReference,
-              MethodAccessFlags.fromSharedAccessFlags(
-                  Constants.ACC_PUBLIC | Constants.ACC_STATIC, false),
-              MethodTypeSignature.noSignature(),
-              DexAnnotationSet.empty(),
-              ParameterAnnotationsList.empty(),
-              ServiceLoaderSourceCode.generate(serviceType, classes, appView.dexItemFactory()),
-              true);
-      methodCollection.addDirectMethod(method);
-      return method;
+    ProgramMethod method =
+        appView
+            .getSyntheticItems()
+            .createMethod(
+                SyntheticKind.SERVICE_LOADER,
+                context,
+                appView.dexItemFactory(),
+                builder ->
+                    builder
+                        .setAccessFlags(MethodAccessFlags.createPublicStaticSynthetic())
+                        .setProto(proto)
+                        .setCode(
+                            m ->
+                                ServiceLoaderSourceCode.generate(
+                                    serviceType, classes, appView.dexItemFactory())),
+                methodProcessingId);
+    synchronized (serviceLoadMethods) {
+      serviceLoadMethods.add(method);
     }
-  }
-
-  private DexProgramClass getOrSetSynthesizedClass() {
-    if (synthesizedClass.get() != null) {
-      return synthesizedClass.get();
-    }
-    assert !appView.options().encodeChecksums;
-    ChecksumSupplier checksumSupplier = DexProgramClass::invalidChecksumRequest;
-    DexProgramClass clazz =
-        synthesizedClass.updateAndGet(
-            existingClass -> {
-              if (existingClass != null) {
-                return existingClass;
-              }
-              DexProgramClass newClass =
-                  new DexProgramClass(
-                      appView.dexItemFactory().serviceLoaderRewrittenClassType,
-                      null,
-                      new SynthesizedOrigin("Service Loader desugaring", getClass()),
-                      ClassAccessFlags.fromDexAccessFlags(
-                          Constants.ACC_FINAL | Constants.ACC_SYNTHETIC | Constants.ACC_PUBLIC),
-                      appView.dexItemFactory().objectType,
-                      DexTypeList.empty(),
-                      appView.dexItemFactory().createString("ServiceLoader"),
-                      null,
-                      Collections.emptyList(),
-                      null,
-                      Collections.emptyList(),
-                      ClassSignature.noSignature(),
-                      DexAnnotationSet.empty(),
-                      DexEncodedField.EMPTY_ARRAY, // Static fields.
-                      DexEncodedField.EMPTY_ARRAY, // Instance fields.
-                      DexEncodedMethod.EMPTY_ARRAY,
-                      DexEncodedMethod.EMPTY_ARRAY, // Virtual methods.
-                      appView.dexItemFactory().getSkipNameValidationForTesting(),
-                      checksumSupplier);
-              newClass.getMethodCollection().useSortedBacking();
-              return newClass;
-            });
-    assert clazz != null;
-    return clazz;
+    return method.getDefinition();
   }
 
   /**
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 1b55f13..9e360b4 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
@@ -4,6 +4,7 @@
 
 package com.android.tools.r8.ir.optimize;
 
+import com.android.tools.r8.cf.CfVersion;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.CfCode;
 import com.android.tools.r8.graph.DexItemFactory;
@@ -15,6 +16,7 @@
 import com.android.tools.r8.ir.conversion.MethodProcessor;
 import com.android.tools.r8.ir.optimize.templates.CfUtilityMethodsForCodeOptimizations;
 import com.android.tools.r8.synthesis.SyntheticItems;
+import com.android.tools.r8.synthesis.SyntheticNaming;
 import com.android.tools.r8.utils.InternalOptions;
 
 public class UtilityMethodsForCodeOptimizations {
@@ -22,22 +24,20 @@
   public static UtilityMethodForCodeOptimizations synthesizeToStringIfNotNullMethod(
       AppView<?> appView, ProgramMethod context, MethodProcessingId methodProcessingId) {
     InternalOptions options = appView.options();
-    if (options.isGeneratingClassFiles()) {
-      // TODO(b/172194277): Allow synthetics when generating CF.
-      return null;
-    }
     DexItemFactory dexItemFactory = appView.dexItemFactory();
     DexProto proto = dexItemFactory.createProto(dexItemFactory.voidType, dexItemFactory.objectType);
     SyntheticItems syntheticItems = appView.getSyntheticItems();
     ProgramMethod syntheticMethod =
         syntheticItems.createMethod(
+            SyntheticNaming.SyntheticKind.TO_STRING_IF_NOT_NULL,
             context,
             dexItemFactory,
             builder ->
                 builder
-                    .setProto(proto)
                     .setAccessFlags(MethodAccessFlags.createPublicStaticSynthetic())
-                    .setCode(method -> getToStringIfNotNullCodeTemplate(method, options)),
+                    .setClassFileVersion(CfVersion.V1_8)
+                    .setCode(method -> getToStringIfNotNullCodeTemplate(method, options))
+                    .setProto(proto),
             methodProcessingId);
     return new UtilityMethodForCodeOptimizations(syntheticMethod);
   }
@@ -51,23 +51,21 @@
   public static UtilityMethodForCodeOptimizations synthesizeThrowClassCastExceptionIfNotNullMethod(
       AppView<?> appView, ProgramMethod context, MethodProcessingId methodProcessingId) {
     InternalOptions options = appView.options();
-    if (options.isGeneratingClassFiles()) {
-      // TODO(b/172194277): Allow synthetics when generating CF.
-      return null;
-    }
     DexItemFactory dexItemFactory = appView.dexItemFactory();
     DexProto proto = dexItemFactory.createProto(dexItemFactory.voidType, dexItemFactory.objectType);
     SyntheticItems syntheticItems = appView.getSyntheticItems();
     ProgramMethod syntheticMethod =
         syntheticItems.createMethod(
+            SyntheticNaming.SyntheticKind.THROW_CCE_IF_NOT_NULL,
             context,
             dexItemFactory,
             builder ->
                 builder
-                    .setProto(proto)
                     .setAccessFlags(MethodAccessFlags.createPublicStaticSynthetic())
+                    .setClassFileVersion(CfVersion.V1_8)
                     .setCode(
-                        method -> getThrowClassCastExceptionIfNotNullCodeTemplate(method, options)),
+                        method -> getThrowClassCastExceptionIfNotNullCodeTemplate(method, options))
+                    .setProto(proto),
             methodProcessingId);
     return new UtilityMethodForCodeOptimizations(syntheticMethod);
   }
@@ -79,6 +77,62 @@
             options, method);
   }
 
+  public static UtilityMethodForCodeOptimizations synthesizeThrowIncompatibleClassChangeErrorMethod(
+      AppView<?> appView, ProgramMethod context, MethodProcessingId methodProcessingId) {
+    InternalOptions options = appView.options();
+    DexItemFactory dexItemFactory = appView.dexItemFactory();
+    DexProto proto = dexItemFactory.createProto(dexItemFactory.icceType);
+    SyntheticItems syntheticItems = appView.getSyntheticItems();
+    ProgramMethod syntheticMethod =
+        syntheticItems.createMethod(
+            SyntheticNaming.SyntheticKind.THROW_ICCE,
+            context,
+            dexItemFactory,
+            builder ->
+                builder
+                    .setAccessFlags(MethodAccessFlags.createPublicStaticSynthetic())
+                    .setClassFileVersion(CfVersion.V1_8)
+                    .setCode(
+                        method -> getThrowIncompatibleClassChangeErrorCodeTemplate(method, options))
+                    .setProto(proto),
+            methodProcessingId);
+    return new UtilityMethodForCodeOptimizations(syntheticMethod);
+  }
+
+  private static CfCode getThrowIncompatibleClassChangeErrorCodeTemplate(
+      DexMethod method, InternalOptions options) {
+    return CfUtilityMethodsForCodeOptimizations
+        .CfUtilityMethodsForCodeOptimizationsTemplates_throwIncompatibleClassChangeError(
+            options, method);
+  }
+
+  public static UtilityMethodForCodeOptimizations synthesizeThrowNoSuchMethodErrorMethod(
+      AppView<?> appView, ProgramMethod context, MethodProcessingId methodProcessingId) {
+    InternalOptions options = appView.options();
+    DexItemFactory dexItemFactory = appView.dexItemFactory();
+    DexProto proto = dexItemFactory.createProto(dexItemFactory.noSuchMethodErrorType);
+    SyntheticItems syntheticItems = appView.getSyntheticItems();
+    ProgramMethod syntheticMethod =
+        syntheticItems.createMethod(
+            SyntheticNaming.SyntheticKind.THROW_NSME,
+            context,
+            dexItemFactory,
+            builder ->
+                builder
+                    .setAccessFlags(MethodAccessFlags.createPublicStaticSynthetic())
+                    .setClassFileVersion(CfVersion.V1_8)
+                    .setCode(method -> getThrowNoSuchMethodErrorCodeTemplate(method, options))
+                    .setProto(proto),
+            methodProcessingId);
+    return new UtilityMethodForCodeOptimizations(syntheticMethod);
+  }
+
+  private static CfCode getThrowNoSuchMethodErrorCodeTemplate(
+      DexMethod method, InternalOptions options) {
+    return CfUtilityMethodsForCodeOptimizations
+        .CfUtilityMethodsForCodeOptimizationsTemplates_throwNoSuchMethodError(options, method);
+  }
+
   public static class UtilityMethodForCodeOptimizations {
 
     private final ProgramMethod method;
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInliner.java b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInliner.java
index 5d79d0e..13865a0 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInliner.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInliner.java
@@ -223,7 +223,7 @@
           // methods (which is bad for memory), or we would need to analyze the called methods
           // before inlining them. The latter could be good solution, since we are going to build IR
           // for the methods that need to be inlined anyway.
-          assert appView.options().testing.allowClassInlinerGracefulExit;
+          assert false;
           anyInlinedMethods = true;
         }
 
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxer.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxer.java
index 166022e..cf0ecb5 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxer.java
@@ -491,34 +491,36 @@
     // Step 1: We iterate over the field to find direct enum instance information and the values
     // fields.
     for (DexEncodedField staticField : enumClass.staticFields()) {
-      if (EnumUnboxingCandidateAnalysis.isEnumField(staticField, enumClass.type)) {
+      if (factory.enumMembers.isEnumField(staticField, enumClass.type)) {
         ObjectState enumState =
             enumStaticFieldValues.getObjectStateForPossiblyPinnedField(staticField.field);
-        if (enumState != null) {
-          OptionalInt optionalOrdinal = getOrdinal(enumState);
-          if (!optionalOrdinal.isPresent()) {
-            return null;
+        if (enumState == null) {
+          if (!isFinalFieldInitialized(staticField, enumClass)) {
+            continue;
           }
-          int ordinal = optionalOrdinal.getAsInt();
-          unboxedValues.put(staticField.field, ordinalToUnboxedInt(ordinal));
-          ordinalToObjectState.put(ordinal, enumState);
-        }
-      } else if (EnumUnboxingCandidateAnalysis.matchesValuesField(
-          staticField, enumClass.type, factory)) {
-        AbstractValue valuesValue =
-            enumStaticFieldValues.getValuesAbstractValueForPossiblyPinnedField(staticField.field);
-        if (valuesValue == null || valuesValue.isZero()) {
-          // Unused field
-          continue;
-        }
-        if (valuesValue.isUnknown()) {
+          // Tracking the content of the field yielded either an empty object state, or something
+          // incoherent. We bail out.
           return null;
         }
-        assert valuesValue.isSingleFieldValue();
-        ObjectState valuesState = valuesValue.asSingleFieldValue().getState();
-        if (!valuesState.isEnumValuesObjectState()) {
+        OptionalInt optionalOrdinal = getOrdinal(enumState);
+        if (!optionalOrdinal.isPresent()) {
           return null;
         }
+        int ordinal = optionalOrdinal.getAsInt();
+        unboxedValues.put(staticField.field, ordinalToUnboxedInt(ordinal));
+        ordinalToObjectState.put(ordinal, enumState);
+      } else if (factory.enumMembers.isValuesFieldCandidate(staticField, enumClass.type)) {
+        ObjectState valuesState =
+            enumStaticFieldValues.getObjectStateForPossiblyPinnedField(staticField.field);
+        if (valuesState == null) {
+          if (!isFinalFieldInitialized(staticField, enumClass)) {
+            continue;
+          }
+          // We could not track the content of that field, and the field could be a values field.
+          // We conservatively bail out.
+          return null;
+        }
+        assert valuesState.isEnumValuesObjectState();
         assert valuesContents == null
             || valuesContents.equals(valuesState.asEnumValuesObjectState());
         valuesContents = valuesState.asEnumValuesObjectState();
@@ -564,6 +566,13 @@
         valuesContents == null ? EnumData.INVALID_VALUES_SIZE : valuesContents.getEnumValuesSize());
   }
 
+  private boolean isFinalFieldInitialized(DexEncodedField staticField, DexProgramClass enumClass) {
+    assert staticField.isFinal();
+    return appView
+        .appInfo()
+        .isFieldOnlyWrittenInMethodIgnoringPinning(staticField, enumClass.getClassInitializer());
+  }
+
   private EnumInstanceFieldData computeEnumFieldData(
       DexField instanceField,
       DexProgramClass enumClass,
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingRewriter.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingRewriter.java
index 6c20c4f..4c7581e 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingRewriter.java
@@ -434,7 +434,7 @@
     return factory.createField(
         relocator.getNewMemberLocationFor(enumType),
         factory.intArrayType,
-        factory.enumValuesFieldName + "$field$" + compatibleName(enumType));
+        "$$values$$field$" + compatibleName(enumType));
   }
 
   private DexEncodedField computeValuesEncodedField(DexField field) {
@@ -451,7 +451,7 @@
     return factory.createMethod(
         relocator.getNewMemberLocationFor(enumType),
         factory.createProto(factory.intArrayType),
-        factory.enumValuesFieldName + "$method$" + compatibleName(enumType));
+        "$$values$$method$" + compatibleName(enumType));
   }
 
   private DexEncodedMethod computeValuesEncodedMethod(
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingTreeFixer.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingTreeFixer.java
index f2f70ca..10a50ca 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingTreeFixer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingTreeFixer.java
@@ -120,9 +120,9 @@
   }
 
   private DexEncodedMethod fixupEncodedMethod(DexEncodedMethod method) {
-    DexProto oldProto = method.proto();
+    DexProto oldProto = method.getProto();
     DexProto newProto = fixupProto(oldProto);
-    if (newProto == method.proto()) {
+    if (newProto == method.getProto()) {
       return method;
     }
     assert !method.isClassInitializer();
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfoCollector.java b/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfoCollector.java
index b5dfee6..9868a64 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfoCollector.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfoCollector.java
@@ -1090,10 +1090,18 @@
       } else if (classInitializerSideEffect.canBePostponed()) {
         feedback.classInitializerMayBePostponed(method);
       } else {
-        assert !context.getHolderType().isD8R8SynthesizedLambdaClassType()
-                || options.debug
-                || appView.appInfo().hasPinnedInstanceInitializer(context.getHolderType())
-                || appView.options().horizontalClassMergerOptions().isJavaLambdaMergingEnabled()
+        assert options.debug
+                || appView
+                    .getSyntheticItems()
+                    .verifySyntheticLambdaProperty(
+                        context.getHolder(),
+                        lambdaClass ->
+                            appView.appInfo().hasPinnedInstanceInitializer(lambdaClass.getType())
+                                || appView
+                                    .options()
+                                    .horizontalClassMergerOptions()
+                                    .isJavaLambdaMergingEnabled(),
+                        nonLambdaClass -> true)
             : "Unexpected observable side effects from lambda `" + context.toSourceString() + "`";
       }
       return;
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/lambda/LambdaMerger.java b/src/main/java/com/android/tools/r8/ir/optimize/lambda/LambdaMerger.java
index 89ffafd..e9ff9e8 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/lambda/LambdaMerger.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/lambda/LambdaMerger.java
@@ -50,6 +50,7 @@
 import com.android.tools.r8.ir.optimize.lambda.kotlin.KotlinLambdaGroupIdFactory;
 import com.android.tools.r8.kotlin.Kotlin;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.shaking.ProguardConfiguration;
 import com.android.tools.r8.utils.SetUtils;
 import com.android.tools.r8.utils.StringDiagnostic;
 import com.android.tools.r8.utils.ThreadUtils;
@@ -397,20 +398,30 @@
     for (LambdaGroup group : groups.values()) {
       ThrowingConsumer<DexClass, LambdaStructureError> validator =
           group.lambdaClassValidator(kotlin, appView.appInfo());
-      group.forEachLambda(info ->
-          futures.add(service.submit(() -> {
-            try {
-              validator.accept(info.clazz);
-            } catch (LambdaStructureError error) {
-              if (error.reportable) {
-                reporter.info(
-                    new StringDiagnostic("Unexpected Kotlin lambda structure [" +
-                        info.clazz.type.toSourceString() + "]: " + error.getMessage())
-                );
-              }
-              invalidateLambda(info.clazz.type);
-            }
-          })));
+      group.forEachLambda(
+          info ->
+              futures.add(
+                  service.submit(
+                      () -> {
+                        try {
+                          validator.accept(info.clazz);
+                        } catch (LambdaStructureError error) {
+                          ProguardConfiguration proguardConfiguration =
+                              appView.options().getProguardConfiguration();
+                          if (error.reportable
+                              && !proguardConfiguration
+                                  .getDontNotePatterns()
+                                  .matches(info.clazz.getType())) {
+                            reporter.info(
+                                new StringDiagnostic(
+                                    "Unexpected Kotlin lambda structure ["
+                                        + info.clazz.type.toSourceString()
+                                        + "]: "
+                                        + error.getMessage()));
+                          }
+                          invalidateLambda(info.clazz.type);
+                        }
+                      })));
     }
     ThreadUtils.awaitFutures(futures);
   }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/library/StringBuilderMethodOptimizer.java b/src/main/java/com/android/tools/r8/ir/optimize/library/StringBuilderMethodOptimizer.java
index 99b1bda..d75e060 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/library/StringBuilderMethodOptimizer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/library/StringBuilderMethodOptimizer.java
@@ -122,16 +122,13 @@
         UtilityMethodForCodeOptimizations toStringIfNotNullMethod =
             UtilityMethodsForCodeOptimizations.synthesizeToStringIfNotNullMethod(
                 appView, code.context(), state.methodProcessingId);
-        // TODO(b/172194277): Allow synthetics when generating CF.
-        if (toStringIfNotNullMethod != null) {
-          toStringIfNotNullMethod.optimize(state.methodProcessor);
-          InvokeStatic replacement =
-              InvokeStatic.builder()
-                  .setMethod(toStringIfNotNullMethod.getMethod())
-                  .setSingleArgument(object)
-                  .build();
-          instructionIterator.replaceCurrentInstruction(replacement);
-        }
+        toStringIfNotNullMethod.optimize(state.methodProcessor);
+        InvokeStatic replacement =
+            InvokeStatic.builder()
+                .setMethod(toStringIfNotNullMethod.getMethod())
+                .setSingleArgument(object)
+                .build();
+        instructionIterator.replaceCurrentInstruction(replacement);
       }
     }
   }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/membervaluepropagation/assume/AssumeInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/membervaluepropagation/assume/AssumeInfo.java
new file mode 100644
index 0000000..a704abb
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/optimize/membervaluepropagation/assume/AssumeInfo.java
@@ -0,0 +1,67 @@
+// 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.optimize.membervaluepropagation.assume;
+
+import com.android.tools.r8.shaking.ProguardMemberRule;
+import com.android.tools.r8.shaking.ProguardMemberRuleReturnValue;
+
+public class AssumeInfo {
+
+  public enum AssumeType {
+    ASSUME_NO_SIDE_EFFECTS,
+    ASSUME_VALUES;
+
+    AssumeType meet(AssumeType type) {
+      return this == ASSUME_NO_SIDE_EFFECTS || type == ASSUME_NO_SIDE_EFFECTS
+          ? ASSUME_NO_SIDE_EFFECTS
+          : ASSUME_VALUES;
+    }
+  }
+
+  private final AssumeType type;
+  private final ProguardMemberRule rule;
+
+  public AssumeInfo(AssumeType type, ProguardMemberRule rule) {
+    this.type = type;
+    this.rule = rule;
+  }
+
+  public boolean hasReturnInfo() {
+    return rule.hasReturnValue();
+  }
+
+  public ProguardMemberRuleReturnValue getReturnInfo() {
+    return rule.getReturnValue();
+  }
+
+  public boolean isAssumeNoSideEffects() {
+    return type == AssumeType.ASSUME_NO_SIDE_EFFECTS;
+  }
+
+  public boolean isAssumeValues() {
+    return type == AssumeType.ASSUME_VALUES;
+  }
+
+  public AssumeInfo meet(AssumeInfo lookup) {
+    return new AssumeInfo(type.meet(lookup.type), rule.hasReturnValue() ? rule : lookup.rule);
+  }
+
+  @Override
+  public boolean equals(Object other) {
+    if (other == null) {
+      return false;
+    }
+    if (!(other instanceof AssumeInfo)) {
+      return false;
+    }
+    AssumeInfo assumeInfo = (AssumeInfo) other;
+    return type == assumeInfo.type && rule == assumeInfo.rule;
+  }
+
+  @Override
+  public int hashCode() {
+    return type.ordinal() * 31 + rule.hashCode();
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/membervaluepropagation/assume/AssumeInfoLookup.java b/src/main/java/com/android/tools/r8/ir/optimize/membervaluepropagation/assume/AssumeInfoLookup.java
new file mode 100644
index 0000000..5ddba94
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/optimize/membervaluepropagation/assume/AssumeInfoLookup.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.optimize.membervaluepropagation.assume;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexClassAndMember;
+import com.android.tools.r8.graph.DexClassAndMethod;
+import com.android.tools.r8.graph.DexMember;
+import com.android.tools.r8.graph.ResolutionResult.SingleResolutionResult;
+import com.android.tools.r8.ir.optimize.membervaluepropagation.assume.AssumeInfo.AssumeType;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.shaking.ProguardMemberRule;
+
+public class AssumeInfoLookup {
+
+  public static AssumeInfo lookupAssumeInfo(
+      AppView<AppInfoWithLiveness> appView,
+      SingleResolutionResult resolutionResult,
+      DexClassAndMethod singleTarget) {
+    AssumeInfo resolutionLookup = lookupAssumeInfo(appView, resolutionResult.getResolutionPair());
+    if (resolutionLookup == null) {
+      return singleTarget != null ? lookupAssumeInfo(appView, singleTarget) : null;
+    }
+    AssumeInfo singleTargetLookup =
+        singleTarget != null ? lookupAssumeInfo(appView, singleTarget) : null;
+    return singleTargetLookup != null
+        ? resolutionLookup.meet(singleTargetLookup)
+        : resolutionLookup;
+  }
+
+  public static AssumeInfo lookupAssumeInfo(
+      AppView<AppInfoWithLiveness> appView, DexClassAndMember<?, ?> member) {
+    DexMember<?, ?> reference = member.getReference();
+    ProguardMemberRule assumeNoSideEffectsRule = appView.appInfo().noSideEffects.get(reference);
+    ProguardMemberRule assumeValuesRule = appView.appInfo().assumedValues.get(reference);
+    if (assumeNoSideEffectsRule == null && assumeValuesRule == null) {
+      return null;
+    }
+    AssumeType type =
+        assumeNoSideEffectsRule != null
+            ? AssumeType.ASSUME_NO_SIDE_EFFECTS
+            : AssumeType.ASSUME_VALUES;
+    if ((assumeNoSideEffectsRule != null && assumeNoSideEffectsRule.hasReturnValue())
+        || assumeValuesRule == null) {
+      return new AssumeInfo(type, assumeNoSideEffectsRule);
+    }
+    return new AssumeInfo(type, assumeValuesRule);
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/peepholes/PeepholeLayout.java b/src/main/java/com/android/tools/r8/ir/optimize/peepholes/PeepholeLayout.java
index 6e36d48..86958a5 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/peepholes/PeepholeLayout.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/peepholes/PeepholeLayout.java
@@ -35,9 +35,9 @@
 
   public Match test(InstructionListIterator it) {
     if (backwards) {
-      return testDirection(() -> it.hasPrevious(), () -> it.previous(), () -> it.next());
+      return testDirection(it::hasPrevious, it::previous, it::next);
     } else {
-      return testDirection(() -> it.hasNext(), () -> it.next(), () -> it.previous());
+      return testDirection(it::hasNext, it::next, it::previous);
     }
   }
 
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 9625b93..831a0b3 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,8 @@
 
   public static void registerSynthesizedCodeReferences(DexItemFactory factory) {
     factory.createSynthesizedType("Ljava/lang/ClassCastException;");
+    factory.createSynthesizedType("Ljava/lang/IncompatibleClassChangeError;");
+    factory.createSynthesizedType("Ljava/lang/NoSuchMethodError;");
   }
 
   public static CfCode
@@ -73,6 +75,53 @@
         ImmutableList.of());
   }
 
+  public static CfCode
+      CfUtilityMethodsForCodeOptimizationsTemplates_throwIncompatibleClassChangeError(
+          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/IncompatibleClassChangeError;")),
+            new CfStackInstruction(CfStackInstruction.Opcode.Dup),
+            new CfInvoke(
+                183,
+                options.itemFactory.createMethod(
+                    options.itemFactory.createType("Ljava/lang/IncompatibleClassChangeError;"),
+                    options.itemFactory.createProto(options.itemFactory.voidType),
+                    options.itemFactory.createString("<init>")),
+                false),
+            new CfThrow()),
+        ImmutableList.of(),
+        ImmutableList.of());
+  }
+
+  public static CfCode CfUtilityMethodsForCodeOptimizationsTemplates_throwNoSuchMethodError(
+      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/NoSuchMethodError;")),
+            new CfStackInstruction(CfStackInstruction.Opcode.Dup),
+            new CfInvoke(
+                183,
+                options.itemFactory.createMethod(
+                    options.itemFactory.createType("Ljava/lang/NoSuchMethodError;"),
+                    options.itemFactory.createProto(options.itemFactory.voidType),
+                    options.itemFactory.createString("<init>")),
+                false),
+            new CfThrow()),
+        ImmutableList.of(),
+        ImmutableList.of());
+  }
+
   public static CfCode CfUtilityMethodsForCodeOptimizationsTemplates_toStringIfNotNull(
       InternalOptions options, DexMethod method) {
     CfLabel label0 = new CfLabel();
diff --git a/src/main/java/com/android/tools/r8/ir/synthetic/FieldAccessorBuilder.java b/src/main/java/com/android/tools/r8/ir/synthetic/FieldAccessorBuilder.java
new file mode 100644
index 0000000..b02544d
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/synthetic/FieldAccessorBuilder.java
@@ -0,0 +1,134 @@
+// 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.synthetic;
+
+import com.android.tools.r8.cf.code.CfFieldInstruction;
+import com.android.tools.r8.cf.code.CfInstruction;
+import com.android.tools.r8.cf.code.CfLoad;
+import com.android.tools.r8.cf.code.CfReturn;
+import com.android.tools.r8.cf.code.CfReturnVoid;
+import com.android.tools.r8.cf.code.CfTryCatch;
+import com.android.tools.r8.graph.CfCode;
+import com.android.tools.r8.graph.DexClassAndField;
+import com.android.tools.r8.graph.DexField;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.ir.code.ValueType;
+import com.android.tools.r8.utils.BooleanUtils;
+import com.android.tools.r8.utils.OptionalBool;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableList.Builder;
+import java.util.function.Consumer;
+import org.objectweb.asm.Opcodes;
+
+public class FieldAccessorBuilder {
+
+  private DexField field;
+  private OptionalBool isInstanceField = OptionalBool.unknown();
+  private OptionalBool isSetter = OptionalBool.unknown();
+  private DexMethod sourceMethod;
+
+  private FieldAccessorBuilder() {}
+
+  public static FieldAccessorBuilder builder() {
+    return new FieldAccessorBuilder();
+  }
+
+  public FieldAccessorBuilder apply(Consumer<FieldAccessorBuilder> consumer) {
+    consumer.accept(this);
+    return this;
+  }
+
+  public FieldAccessorBuilder setField(DexClassAndField field) {
+    return field.getAccessFlags().isStatic()
+        ? setStaticField(field.getReference())
+        : setInstanceField(field.getReference());
+  }
+
+  public FieldAccessorBuilder setGetter() {
+    isSetter = OptionalBool.FALSE;
+    return this;
+  }
+
+  public FieldAccessorBuilder setInstanceField(DexField field) {
+    this.field = field;
+    this.isInstanceField = OptionalBool.TRUE;
+    return this;
+  }
+
+  public FieldAccessorBuilder setSetter() {
+    isSetter = OptionalBool.TRUE;
+    return this;
+  }
+
+  public FieldAccessorBuilder setSourceMethod(DexMethod sourceMethod) {
+    this.sourceMethod = sourceMethod;
+    return this;
+  }
+
+  public FieldAccessorBuilder setStaticField(DexField field) {
+    this.field = field;
+    this.isInstanceField = OptionalBool.FALSE;
+    return this;
+  }
+
+  public CfCode build() {
+    assert validate();
+    int maxStack = 0;
+    int maxLocals = 0;
+    Builder<CfInstruction> instructions = ImmutableList.builder();
+    if (isInstanceField()) {
+      // Load the receiver.
+      instructions.add(new CfLoad(ValueType.OBJECT, maxLocals));
+      maxStack += 1;
+      maxLocals += 1;
+    }
+
+    if (isSetter()) {
+      // Load the argument.
+      ValueType fieldType = ValueType.fromDexType(field.getType());
+      instructions.add(new CfLoad(fieldType, maxLocals));
+      maxLocals += fieldType.requiredRegisters();
+    }
+
+    // Get or set the field.
+    int opcode =
+        Opcodes.GETSTATIC + BooleanUtils.intValue(isSetter()) + (isInstanceField.ordinal() << 1);
+    instructions.add(new CfFieldInstruction(opcode, field, field));
+
+    // Return.
+    if (isSetter()) {
+      instructions.add(new CfReturnVoid());
+    } else {
+      ValueType fieldType = ValueType.fromDexType(field.getType());
+      instructions.add(new CfReturn(fieldType));
+    }
+
+    ImmutableList<CfTryCatch> tryCatchRanges = ImmutableList.of();
+    ImmutableList<CfCode.LocalVariableInfo> localVariables = ImmutableList.of();
+    return new CfCode(
+        sourceMethod.getHolderType(),
+        maxStack,
+        maxLocals,
+        instructions.build(),
+        tryCatchRanges,
+        localVariables);
+  }
+
+  private boolean isSetter() {
+    return isSetter.isTrue();
+  }
+
+  private boolean isInstanceField() {
+    return isInstanceField.isTrue();
+  }
+
+  private boolean validate() {
+    assert field != null;
+    assert !isInstanceField.isUnknown();
+    assert !isSetter.isUnknown();
+    assert sourceMethod != null;
+    return true;
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/synthetic/FieldAccessorSourceCode.java b/src/main/java/com/android/tools/r8/ir/synthetic/FieldAccessorSourceCode.java
deleted file mode 100644
index 8936c73..0000000
--- a/src/main/java/com/android/tools/r8/ir/synthetic/FieldAccessorSourceCode.java
+++ /dev/null
@@ -1,55 +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.synthetic;
-
-import com.android.tools.r8.graph.DexMethod;
-import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.ir.code.Position;
-import com.android.tools.r8.ir.code.ValueType;
-import com.android.tools.r8.ir.conversion.IRBuilder;
-import com.android.tools.r8.ir.desugar.NestBasedAccessDesugaring.DexFieldWithAccess;
-
-// Source code representing simple forwarding method.
-public final class FieldAccessorSourceCode extends SyntheticSourceCode {
-
-  private final DexFieldWithAccess fieldWithAccess;
-
-  public FieldAccessorSourceCode(
-      DexType receiver,
-      DexMethod method,
-      Position callerPosition,
-      DexMethod originalMethod,
-      DexFieldWithAccess field) {
-    super(receiver, method, callerPosition, originalMethod);
-    this.fieldWithAccess = field;
-    assert method.proto.returnType == fieldWithAccess.getType() || fieldWithAccess.isPut();
-  }
-
-  @Override
-  protected void prepareInstructions() {
-    if (fieldWithAccess.isInstanceGet()) {
-      ValueType valueType = ValueType.fromDexType(proto.returnType);
-      int objReg = getParamRegister(0);
-      int returnReg = nextRegister(valueType);
-      add(builder -> builder.addInstanceGet(returnReg, objReg, fieldWithAccess.getField()));
-      add(builder -> builder.addReturn(returnReg));
-    } else if (fieldWithAccess.isStaticGet()) {
-      ValueType valueType = ValueType.fromDexType(proto.returnType);
-      int returnReg = nextRegister(valueType);
-      add(builder -> builder.addStaticGet(returnReg, fieldWithAccess.getField()));
-      add(builder -> builder.addReturn(returnReg));
-    } else if (fieldWithAccess.isInstancePut()) {
-      int objReg = getParamRegister(0);
-      int putValueReg = getParamRegister(1);
-      add(builder -> builder.addInstancePut(putValueReg, objReg, fieldWithAccess.getField()));
-      add(IRBuilder::addReturn);
-    } else {
-      assert fieldWithAccess.isStaticPut();
-      int putValueReg = getParamRegister(0);
-      add(builder -> builder.addStaticPut(putValueReg, fieldWithAccess.getField()));
-      add(IRBuilder::addReturn);
-    }
-  }
-}
diff --git a/src/main/java/com/android/tools/r8/ir/synthetic/ForwardMethodBuilder.java b/src/main/java/com/android/tools/r8/ir/synthetic/ForwardMethodBuilder.java
index 8d8ca9f..794fd3e 100644
--- a/src/main/java/com/android/tools/r8/ir/synthetic/ForwardMethodBuilder.java
+++ b/src/main/java/com/android/tools/r8/ir/synthetic/ForwardMethodBuilder.java
@@ -20,8 +20,10 @@
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.ir.code.ValueType;
+import com.android.tools.r8.utils.BooleanUtils;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableList.Builder;
+import java.util.function.Consumer;
 import org.objectweb.asm.Opcodes;
 
 public class ForwardMethodBuilder {
@@ -41,6 +43,7 @@
   private DexMethod sourceMethod = null;
   private DexMethod targetMethod = null;
 
+  private boolean sourceMethodHasExtraUnusedParameter = false;
   private boolean staticSource = false;
 
   private InvokeType invokeType = null;
@@ -53,12 +56,24 @@
     this.factory = factory;
   }
 
+  public ForwardMethodBuilder apply(Consumer<ForwardMethodBuilder> fn) {
+    fn.accept(this);
+    return this;
+  }
+
   public ForwardMethodBuilder setNonStaticSource(DexMethod method) {
     sourceMethod = method;
     staticSource = false;
     return this;
   }
 
+  public ForwardMethodBuilder setNonStaticSourceWithExtraUnusedParameter(DexMethod method) {
+    sourceMethod = method;
+    staticSource = false;
+    sourceMethodHasExtraUnusedParameter = true;
+    return this;
+  }
+
   public ForwardMethodBuilder setStaticSource(DexMethod method) {
     sourceMethod = method;
     staticSource = true;
@@ -79,6 +94,10 @@
     return this;
   }
 
+  public ForwardMethodBuilder setConstructorTarget(DexMethod method) {
+    return setDirectTarget(method, false);
+  }
+
   public ForwardMethodBuilder setDirectTarget(DexMethod method, boolean isInterface) {
     targetMethod = method;
     invokeType = InvokeType.SPECIAL;
@@ -126,7 +145,9 @@
       maxLocals += 1;
     }
     DexType[] sourceParameters = getSourceParameters();
-    for (int i = 0; i < sourceParameters.length; i++) {
+    for (int i = 0;
+        i < sourceParameters.length - BooleanUtils.intValue(sourceMethodHasExtraUnusedParameter);
+        i++) {
       DexType parameter = sourceParameters[i];
       ValueType parameterType = ValueType.fromDexType(parameter);
       instructions.add(new CfLoad(parameterType, maxLocals));
@@ -212,7 +233,9 @@
   }
 
   private int sourceArguments() {
-    return sourceMethod.getParameters().size() + (isStaticSource() ? 0 : 1);
+    return sourceMethod.getParameters().size()
+        + (isStaticSource() ? 0 : 1)
+        - BooleanUtils.intValue(sourceMethodHasExtraUnusedParameter);
   }
 
   private int targetArguments() {
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 ef724ec..cf6a117 100644
--- a/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java
+++ b/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java
@@ -40,7 +40,7 @@
 import com.android.tools.r8.naming.NamingLens;
 import com.android.tools.r8.naming.ProguardMapSupplier;
 import com.android.tools.r8.references.Reference;
-import com.android.tools.r8.synthesis.SyntheticItems;
+import com.android.tools.r8.synthesis.SyntheticNaming;
 import com.android.tools.r8.utils.AsmUtils;
 import com.android.tools.r8.utils.ExceptionUtils;
 import com.android.tools.r8.utils.InternalOptions;
@@ -124,7 +124,7 @@
         marker.isRelocator() ? Optional.empty() : Optional.of(marker.toString());
     LensCodeRewriterUtils rewriter = new LensCodeRewriterUtils(appView);
     for (DexProgramClass clazz : application.classes()) {
-      assert SyntheticItems.verifyNotInternalSynthetic(clazz.getType());
+      assert SyntheticNaming.verifyNotInternalSynthetic(clazz.getType());
       try {
         writeClass(clazz, consumer, rewriter, markerString);
       } catch (ClassTooLargeException e) {
@@ -194,6 +194,7 @@
     for (int i = 0; i < clazz.interfaces.values.length; i++) {
       interfaces[i] = namingLens.lookupInternalName(clazz.interfaces.values[i]);
     }
+    assert SyntheticNaming.verifyNotInternalSynthetic(name);
     writer.visit(version.raw(), access, name, signature, superName, interfaces);
     writeAnnotations(writer::visitAnnotation, clazz.annotations().annotations);
     ImmutableMap<DexString, DexValue> defaults = getAnnotationDefaults(clazz.annotations());
diff --git a/src/main/java/com/android/tools/r8/naming/ClassNameMinifier.java b/src/main/java/com/android/tools/r8/naming/ClassNameMinifier.java
index e7ca5b5..771c336 100644
--- a/src/main/java/com/android/tools/r8/naming/ClassNameMinifier.java
+++ b/src/main/java/com/android/tools/r8/naming/ClassNameMinifier.java
@@ -100,13 +100,6 @@
   }
 
   ClassRenaming computeRenaming(Timing timing) {
-    return computeRenaming(timing, Collections.emptyMap());
-  }
-
-  ClassRenaming computeRenaming(Timing timing, Map<DexType, DexString> syntheticClasses) {
-    // Externally defined synthetic classes populate an initial renaming.
-    renaming.putAll(syntheticClasses);
-
     // Collect names we have to keep.
     timing.begin("reserve");
     for (DexClass clazz : classes) {
diff --git a/src/main/java/com/android/tools/r8/naming/InterfaceMethodNameMinifier.java b/src/main/java/com/android/tools/r8/naming/InterfaceMethodNameMinifier.java
index a8bd96c..0f5924d 100644
--- a/src/main/java/com/android/tools/r8/naming/InterfaceMethodNameMinifier.java
+++ b/src/main/java/com/android/tools/r8/naming/InterfaceMethodNameMinifier.java
@@ -23,7 +23,6 @@
 import com.google.common.collect.Sets;
 import java.io.PrintStream;
 import java.util.ArrayList;
-import java.util.Collection;
 import java.util.Comparator;
 import java.util.HashMap;
 import java.util.HashSet;
@@ -397,7 +396,7 @@
     return Comparator.comparing(globalStateMap::get);
   }
 
-  private void reserveNamesInInterfaces(Collection<DexClass> interfaces) {
+  private void reserveNamesInInterfaces(Iterable<DexClass> interfaces) {
     for (DexClass iface : interfaces) {
       assert iface.isInterface();
       minifierState.allocateReservationStateAndReserve(iface.type, iface.type);
@@ -407,7 +406,7 @@
     }
   }
 
-  void assignNamesToInterfaceMethods(Timing timing, Collection<DexClass> interfaces) {
+  void assignNamesToInterfaceMethods(Timing timing, Iterable<DexClass> interfaces) {
     timing.begin("Interface minification");
     // Reserve all the names that are required for interfaces.
     timing.begin("Reserve direct and compute hierarchy");
@@ -643,19 +642,26 @@
             clazz -> {
               // TODO(b/133091438): Extend the if check to test for !clazz.isLibrary().
               if (!clazz.isInterface()) {
-                for (DexType directlyImplemented :
-                    appView.appInfo().implementedInterfaces(clazz.type)) {
-                  InterfaceReservationState iState = interfaceStateMap.get(directlyImplemented);
-                  if (iState != null) {
-                    DexType frontierType = minifierState.getFrontier(clazz.type);
-                    iState.addReservationType(frontierType);
-                    // The reservation state should already be added, but if a class is extending
-                    // an interface, we will not visit the class during the sub-type traversel
-                    if (minifierState.getReservationState(clazz.type) == null) {
-                      minifierState.allocateReservationStateAndReserve(clazz.type, frontierType);
-                    }
-                  }
-                }
+                appView
+                    .appInfo()
+                    .implementedInterfaces(clazz.type)
+                    .forEach(
+                        (directlyImplemented, ignoreIsKnownAndReserveInAllCases) -> {
+                          InterfaceReservationState iState =
+                              interfaceStateMap.get(directlyImplemented);
+                          if (iState != null) {
+                            DexType frontierType = minifierState.getFrontier(clazz.type);
+                            iState.addReservationType(frontierType);
+                            // The reservation state should already be added, but if a class is
+                            // extending
+                            // an interface, we will not visit the class during the sub-type
+                            // traversel
+                            if (minifierState.getReservationState(clazz.type) == null) {
+                              minifierState.allocateReservationStateAndReserve(
+                                  clazz.type, frontierType);
+                            }
+                          }
+                        });
               }
             });
   }
diff --git a/src/main/java/com/android/tools/r8/naming/MethodNameMinifier.java b/src/main/java/com/android/tools/r8/naming/MethodNameMinifier.java
index 49dcd83..0f50ecb 100644
--- a/src/main/java/com/android/tools/r8/naming/MethodNameMinifier.java
+++ b/src/main/java/com/android/tools/r8/naming/MethodNameMinifier.java
@@ -16,7 +16,6 @@
 import com.google.common.collect.BiMap;
 import com.google.common.collect.HashBiMap;
 import com.google.common.collect.ImmutableMap;
-import java.util.Collection;
 import java.util.HashMap;
 import java.util.IdentityHashMap;
 import java.util.Map;
@@ -174,7 +173,7 @@
     }
   }
 
-  MethodRenaming computeRenaming(Collection<DexClass> interfaces, Timing timing) {
+  MethodRenaming computeRenaming(Iterable<DexClass> interfaces, Timing timing) {
     // Phase 1: Reserve all the names that need to be kept and allocate linked state in the
     //          library part.
     timing.begin("Phase 1");
diff --git a/src/main/java/com/android/tools/r8/naming/Minifier.java b/src/main/java/com/android/tools/r8/naming/Minifier.java
index 4bc638a..c00993f 100644
--- a/src/main/java/com/android/tools/r8/naming/Minifier.java
+++ b/src/main/java/com/android/tools/r8/naming/Minifier.java
@@ -3,6 +3,7 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.naming;
 
+import static com.android.tools.r8.graph.DexApplication.classesWithDeterministicOrder;
 import static com.android.tools.r8.utils.StringUtils.EMPTY_CHAR_ARRAY;
 import static com.android.tools.r8.utils.SymbolGenerationUtils.PRIMITIVE_TYPE_NAMES;
 
@@ -26,11 +27,10 @@
 import com.android.tools.r8.utils.SymbolGenerationUtils;
 import com.android.tools.r8.utils.SymbolGenerationUtils.MixedCasing;
 import com.android.tools.r8.utils.Timing;
-import java.util.Comparator;
+import java.util.ArrayList;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Set;
-import java.util.TreeSet;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.ExecutorService;
 import java.util.function.BiPredicate;
@@ -48,8 +48,7 @@
     assert appView.options().isMinifying();
     SubtypingInfo subtypingInfo = appView.appInfo().computeSubtypingInfo();
     timing.begin("ComputeInterfaces");
-    Set<DexClass> interfaces = new TreeSet<>(Comparator.comparing(a -> a.type));
-    interfaces.addAll(appView.appInfo().computeReachableInterfaces());
+    List<DexClass> interfaces = computeReachableInterfacesWithDeterministicOrder();
     timing.end();
     timing.begin("MinifyClasses");
     ClassNameMinifier classNameMinifier =
@@ -92,6 +91,12 @@
     return lens;
   }
 
+  private List<DexClass> computeReachableInterfacesWithDeterministicOrder() {
+    List<DexClass> interfaces = new ArrayList<>();
+    appView.appInfo().forEachReachableInterface(interfaces::add);
+    return classesWithDeterministicOrder(interfaces);
+  }
+
   abstract static class BaseMinificationNamingStrategy {
 
     // We have to ensure that the names proposed by the minifier is not used in the obfuscation
diff --git a/src/main/java/com/android/tools/r8/naming/ProguardMapMinifier.java b/src/main/java/com/android/tools/r8/naming/ProguardMapMinifier.java
index 6d8c889..b75956e 100644
--- a/src/main/java/com/android/tools/r8/naming/ProguardMapMinifier.java
+++ b/src/main/java/com/android/tools/r8/naming/ProguardMapMinifier.java
@@ -4,6 +4,8 @@
 
 package com.android.tools.r8.naming;
 
+import static com.android.tools.r8.graph.DexApplication.classesWithDeterministicOrder;
+
 import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexClass;
@@ -35,7 +37,9 @@
 import com.google.common.collect.BiMap;
 import com.google.common.collect.HashBiMap;
 import com.google.common.collect.Maps;
+import com.google.common.collect.Sets;
 import java.util.ArrayDeque;
+import java.util.Comparator;
 import java.util.Deque;
 import java.util.HashMap;
 import java.util.HashSet;
@@ -73,9 +77,8 @@
   private final SeedMapper seedMapper;
   private final BiMap<DexType, DexString> mappedNames = HashBiMap.create();
   // To keep the order deterministic, we sort the classes by their type, which is a unique key.
-  private final Set<DexClass> mappedClasses = new TreeSet<>((a, b) -> a.type.compareTo(b.type));
+  private final Set<DexClass> mappedClasses = Sets.newIdentityHashSet();
   private final Map<DexReference, MemberNaming> memberNames = Maps.newIdentityHashMap();
-  private final Map<DexType, DexString> syntheticCompanionClasses = Maps.newIdentityHashMap();
   private final Map<DexMethod, DexString> defaultInterfaceMethodImplementationNames =
       Maps.newIdentityHashMap();
   private final Map<DexMethod, DexString> additionalMethodNamings = Maps.newIdentityHashMap();
@@ -95,15 +98,15 @@
     Set<DexReference> notMappedReferences = new HashSet<>();
 
     timing.begin("MappingInterfaces");
-    Set<DexClass> interfaces = new TreeSet<>((a, b) -> a.type.compareTo(b.type));
+    Set<DexClass> interfaces = new TreeSet<>(Comparator.comparing(DexClass::getType));
     Consumer<DexClass> consumer =
-        dexClass -> {
-          if (dexClass.isInterface()) {
+        clazz -> {
+          if (clazz.isInterface()) {
             // Only visit top level interfaces because computeMapping will visit the hierarchy.
-            if (dexClass.interfaces.isEmpty()) {
-              computeMapping(dexClass.type, nonPrivateMembers, notMappedReferences, subtypingInfo);
+            if (clazz.interfaces.isEmpty()) {
+              computeMapping(clazz.type, nonPrivateMembers, notMappedReferences, subtypingInfo);
             }
-            interfaces.add(dexClass);
+            interfaces.add(clazz);
           }
         };
     // For union-find of interface methods we also need to add the library types above live types.
@@ -141,9 +144,8 @@
             // in the ClassNameMinifier that the strategy should produce a "fresh" name so we just
             // use the existing strategy.
             new MinificationPackageNamingStrategy(appView),
-            mappedClasses);
-    ClassRenaming classRenaming =
-        classNameMinifier.computeRenaming(timing, syntheticCompanionClasses);
+            classesWithDeterministicOrder(mappedClasses));
+    ClassRenaming classRenaming = classNameMinifier.computeRenaming(timing);
     timing.end();
 
     ApplyMappingMemberNamingStrategy nameStrategy =
@@ -341,11 +343,10 @@
       }
       // TODO(b/150736225): Is this sound? What if the type is a library type that has been pruned?
       DexClass dexClass = appView.appInfo().definitionForWithoutExistenceAssert(type);
-      if (dexClass == null) {
+      if (dexClass == null || dexClass.isClasspathClass()) {
         computeDefaultInterfaceMethodMappingsForType(
             type,
             classNaming,
-            syntheticCompanionClasses,
             defaultInterfaceMethodImplementationNames);
       }
     }
@@ -354,7 +355,6 @@
   private void computeDefaultInterfaceMethodMappingsForType(
       DexType type,
       ClassNamingForMapApplier classNaming,
-      Map<DexType, DexString> syntheticCompanionClasses,
       Map<DexMethod, DexString> defaultInterfaceMethodImplementationNames) {
     // If the class does not resolve, then check if it is a companion class for an interface on
     // the class path.
@@ -366,7 +366,6 @@
     if (interfaceType == null || !interfaceType.isClasspathClass()) {
       return;
     }
-    syntheticCompanionClasses.put(type, factory.createString(classNaming.renamedName));
     for (List<MemberNaming> namings : classNaming.getQualifiedMethodMembers().values()) {
       // If the qualified name has been mapped to multiple names we can't compute a mapping (and it
       // should not be possible that this is a default interface method in that case.)
diff --git a/src/main/java/com/android/tools/r8/optimize/ClassAndMemberPublicizer.java b/src/main/java/com/android/tools/r8/optimize/ClassAndMemberPublicizer.java
index 30d9b7d..a675776 100644
--- a/src/main/java/com/android/tools/r8/optimize/ClassAndMemberPublicizer.java
+++ b/src/main/java/com/android/tools/r8/optimize/ClassAndMemberPublicizer.java
@@ -10,7 +10,6 @@
 
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexApplication;
-import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexEncodedField;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexMethod;
@@ -83,9 +82,7 @@
 
     // Phase 2: Visit classes and promote class/member to public if possible.
     timing.begin("Phase 2: promoteToPublic");
-    for (DexClass iface : appView.appInfo().computeReachableInterfaces()) {
-      publicizeType(iface.type);
-    }
+    appView.appInfo().forEachReachableInterface(clazz -> publicizeType(clazz.getType()));
     publicizeType(appView.dexItemFactory().objectType);
     timing.end();
 
diff --git a/src/main/java/com/android/tools/r8/repackaging/Repackaging.java b/src/main/java/com/android/tools/r8/repackaging/Repackaging.java
index 51e419b..eba0a87 100644
--- a/src/main/java/com/android/tools/r8/repackaging/Repackaging.java
+++ b/src/main/java/com/android/tools/r8/repackaging/Repackaging.java
@@ -8,7 +8,6 @@
 import static com.android.tools.r8.utils.DescriptorUtils.DESCRIPTOR_PACKAGE_SEPARATOR;
 import static com.android.tools.r8.utils.DescriptorUtils.INNER_CLASS_SEPARATOR;
 
-import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.graph.DexItemFactory;
@@ -73,8 +72,7 @@
     return lens;
   }
 
-  public static boolean verifyIdentityRepackaging(
-      AppView<? extends AppInfoWithClassHierarchy> appView) {
+  public static boolean verifyIdentityRepackaging(AppView<AppInfoWithLiveness> appView) {
     // Running the tree fixer with an identity mapping helps ensure that the fixup of of items is
     // complete as the rewrite replaces all items regardless of repackaging.
     // The identity mapping should result in no move callbacks being called.
@@ -140,10 +138,11 @@
       return null;
     }
     RepackagingLens.Builder lensBuilder = new RepackagingLens.Builder();
+    RepackagingTreeFixer repackagingTreeFixer =
+        new RepackagingTreeFixer(appView, mappings, lensBuilder);
     List<DexProgramClass> newProgramClasses =
         new ArrayList<>(
-            new RepackagingTreeFixer(appView, mappings, lensBuilder)
-                .fixupClasses(appView.appInfo().classesWithDeterministicOrder()));
+            repackagingTreeFixer.fixupClasses(appView.appInfo().classesWithDeterministicOrder()));
     appBuilder.replaceProgramClasses(newProgramClasses);
     RepackagingLens lens = lensBuilder.build(appView);
     new AnnotationFixer(lens).run(appBuilder.getProgramClasses());
@@ -156,10 +155,14 @@
     private final Builder lensBuilder;
 
     public RepackagingTreeFixer(
-        AppView<?> appView, BiMap<DexType, DexType> mappings, Builder lensBuilder) {
+        AppView<AppInfoWithLiveness> appView,
+        BiMap<DexType, DexType> mappings,
+        Builder lensBuilder) {
       super(appView);
+      assert mappings != null;
       this.mappings = mappings;
       this.lensBuilder = lensBuilder;
+      recordFailedResolutionChanges();
     }
 
     @Override
diff --git a/src/main/java/com/android/tools/r8/retrace/internal/StackTraceElementStringProxy.java b/src/main/java/com/android/tools/r8/retrace/internal/StackTraceElementStringProxy.java
index f0ba88d..6decd5e 100644
--- a/src/main/java/com/android/tools/r8/retrace/internal/StackTraceElementStringProxy.java
+++ b/src/main/java/com/android/tools/r8/retrace/internal/StackTraceElementStringProxy.java
@@ -15,7 +15,6 @@
 import com.android.tools.r8.retrace.RetracedType;
 import com.android.tools.r8.retrace.StackTraceElementProxy;
 import com.android.tools.r8.utils.StringUtils;
-import com.android.tools.r8.utils.StringUtils.BraceType;
 import com.android.tools.r8.utils.TriFunction;
 import java.util.ArrayList;
 import java.util.List;
@@ -287,7 +286,7 @@
                   return original.getMethodArguments();
                 }
                 return StringUtils.join(
-                    retraced.getMethodArguments(), ",", BraceType.NONE, RetracedType::getTypeName);
+                    ",", retraced.getMethodArguments(), RetracedType::getTypeName);
               });
       orderedIndices.add(methodArguments);
       return this;
diff --git a/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java b/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
index 6b316f7..363cb30 100644
--- a/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
+++ b/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
@@ -45,9 +45,9 @@
 import com.android.tools.r8.ir.analysis.type.ClassTypeElement;
 import com.android.tools.r8.ir.code.Invoke.Type;
 import com.android.tools.r8.ir.desugar.DesugaredLibraryAPIConverter;
+import com.android.tools.r8.ir.desugar.DesugaredLibraryRetargeter;
 import com.android.tools.r8.ir.desugar.InterfaceMethodRewriter;
 import com.android.tools.r8.ir.desugar.LambdaDescriptor;
-import com.android.tools.r8.ir.desugar.TwrCloseResourceRewriter;
 import com.android.tools.r8.synthesis.CommittedItems;
 import com.android.tools.r8.utils.CollectionUtils;
 import com.android.tools.r8.utils.InternalOptions;
@@ -89,8 +89,11 @@
    */
   private final Set<DexMethod> targetedMethods;
 
-  /** Set of targets that lead to resolution errors, such as non-existing or invalid targets. */
-  private final Set<DexMethod> failedResolutionTargets;
+  /** Method targets that lead to resolution errors such as non-existing or invalid targets. */
+  private final Set<DexMethod> failedMethodResolutionTargets;
+
+  /** Field targets that lead to resolution errors, such as non-existing or invalid targets. */
+  private final Set<DexField> failedFieldResolutionTargets;
 
   /**
    * Set of program methods that are used as the bootstrap method for an invoke-dynamic instruction.
@@ -197,7 +200,8 @@
       MissingClasses missingClasses,
       Set<DexType> liveTypes,
       Set<DexMethod> targetedMethods,
-      Set<DexMethod> failedResolutionTargets,
+      Set<DexMethod> failedMethodResolutionTargets,
+      Set<DexField> failedFieldResolutionTargets,
       Set<DexMethod> bootstrapMethods,
       Set<DexMethod> methodsTargetedByInvokeDynamic,
       Set<DexMethod> virtualMethodsTargetedByInvokeDirect,
@@ -235,7 +239,8 @@
     this.deadProtoTypes = deadProtoTypes;
     this.liveTypes = liveTypes;
     this.targetedMethods = targetedMethods;
-    this.failedResolutionTargets = failedResolutionTargets;
+    this.failedMethodResolutionTargets = failedMethodResolutionTargets;
+    this.failedFieldResolutionTargets = failedFieldResolutionTargets;
     this.bootstrapMethods = bootstrapMethods;
     this.methodsTargetedByInvokeDynamic = methodsTargetedByInvokeDynamic;
     this.virtualMethodsTargetedByInvokeDirect = virtualMethodsTargetedByInvokeDirect;
@@ -269,7 +274,7 @@
     this.switchMaps = switchMaps;
     this.lockCandidates = lockCandidates;
     this.initClassReferences = initClassReferences;
-    verify();
+    assert verify();
   }
 
   private AppInfoWithLiveness(AppInfoWithLiveness previous, CommittedItems committedItems) {
@@ -279,9 +284,10 @@
         previous.getMainDexClasses(),
         previous.deadProtoTypes,
         previous.getMissingClasses(),
-        CollectionUtils.mergeSets(previous.liveTypes, committedItems.getCommittedTypes()),
+        CollectionUtils.mergeSets(previous.liveTypes, committedItems.getCommittedProgramTypes()),
         previous.targetedMethods,
-        previous.failedResolutionTargets,
+        previous.failedMethodResolutionTargets,
+        previous.failedFieldResolutionTargets,
         previous.bootstrapMethods,
         previous.methodsTargetedByInvokeDynamic,
         previous.virtualMethodsTargetedByInvokeDirect,
@@ -328,7 +334,8 @@
             ? Sets.difference(previous.liveTypes, prunedItems.getRemovedClasses())
             : previous.liveTypes,
         previous.targetedMethods,
-        previous.failedResolutionTargets,
+        previous.failedMethodResolutionTargets,
+        previous.failedFieldResolutionTargets,
         previous.bootstrapMethods,
         previous.methodsTargetedByInvokeDynamic,
         previous.virtualMethodsTargetedByInvokeDirect,
@@ -366,10 +373,11 @@
         previous.initClassReferences);
   }
 
-  private void verify() {
+  private boolean verify() {
     assert keepInfo.verifyPinnedTypesAreLive(liveTypes);
     assert objectAllocationInfoCollection.verifyAllocatedTypesAreLive(
         liveTypes, getMissingClasses(), this);
+    return true;
   }
 
   private static KeepInfoCollection extendPinnedItems(
@@ -419,7 +427,8 @@
     this.deadProtoTypes = previous.deadProtoTypes;
     this.liveTypes = previous.liveTypes;
     this.targetedMethods = previous.targetedMethods;
-    this.failedResolutionTargets = previous.failedResolutionTargets;
+    this.failedMethodResolutionTargets = previous.failedMethodResolutionTargets;
+    this.failedFieldResolutionTargets = previous.failedFieldResolutionTargets;
     this.bootstrapMethods = previous.bootstrapMethods;
     this.methodsTargetedByInvokeDynamic = previous.methodsTargetedByInvokeDynamic;
     this.virtualMethodsTargetedByInvokeDirect = previous.virtualMethodsTargetedByInvokeDirect;
@@ -454,7 +463,7 @@
     this.lockCandidates = previous.lockCandidates;
     this.initClassReferences = previous.initClassReferences;
     previous.markObsolete();
-    verify();
+    assert verify();
   }
 
   public static AppInfoWithLivenessModifier modifier() {
@@ -470,8 +479,7 @@
             // TODO(b/150693139): Remove these exceptions once fixed.
             || InterfaceMethodRewriter.isCompanionClassType(type)
             || InterfaceMethodRewriter.isEmulatedLibraryClassType(type)
-            || type.toDescriptorString().startsWith("Lj$/$r8$retargetLibraryMember$")
-            || TwrCloseResourceRewriter.isUtilityClassDescriptor(type)
+            || DesugaredLibraryRetargeter.isRetargetType(type, options())
             // TODO(b/150736225): Not sure how to remove these.
             || DesugaredLibraryAPIConverter.isVivifiedType(type)
         : "Failed lookup of non-missing type: " + type;
@@ -533,11 +541,15 @@
   }
 
   public boolean isFailedResolutionTarget(DexMethod method) {
-    return failedResolutionTargets.contains(method);
+    return failedMethodResolutionTargets.contains(method);
   }
 
-  public Set<DexMethod> getFailedResolutionTargets() {
-    return failedResolutionTargets;
+  public Set<DexMethod> getFailedMethodResolutionTargets() {
+    return failedMethodResolutionTargets;
+  }
+
+  public Set<DexField> getFailedFieldResolutionTargets() {
+    return failedFieldResolutionTargets;
   }
 
   public boolean isBootstrapMethod(DexMethod method) {
@@ -604,8 +616,7 @@
     return reprocess;
   }
 
-  public Collection<DexClass> computeReachableInterfaces() {
-    Set<DexClass> interfaces = Sets.newIdentityHashSet();
+  public void forEachReachableInterface(Consumer<DexClass> consumer) {
     WorkList<DexType> worklist = WorkList.newIdentityWorkList();
     worklist.addIfNotSeen(objectAllocationInfoCollection.getInstantiatedLambdaInterfaces());
     for (DexProgramClass clazz : classes()) {
@@ -618,14 +629,13 @@
         continue;
       }
       if (definition.isInterface()) {
-        interfaces.add(definition);
+        consumer.accept(definition);
       }
       if (definition.superType != null) {
         worklist.addIfNotSeen(definition.superType);
       }
       worklist.addIfNotSeen(definition.interfaces.values);
     }
-    return interfaces;
   }
 
   /**
@@ -999,7 +1009,8 @@
         getMissingClasses().commitSyntheticItems(committedItems),
         lens.rewriteTypes(liveTypes),
         lens.rewriteMethods(targetedMethods),
-        lens.rewriteMethods(failedResolutionTargets),
+        lens.rewriteMethods(failedMethodResolutionTargets),
+        lens.rewriteFields(failedFieldResolutionTargets),
         lens.rewriteMethods(bootstrapMethods),
         lens.rewriteMethods(methodsTargetedByInvokeDynamic),
         lens.rewriteMethods(virtualMethodsTargetedByInvokeDirect),
@@ -1351,12 +1362,7 @@
         // The type java.lang.Object could be any instantiated type. Assume a finalizer exists.
         return true;
       }
-      for (DexType iface : type.getInterfaces()) {
-        if (mayHaveFinalizer(iface)) {
-          return true;
-        }
-      }
-      return false;
+      return type.getInterfaces().anyMatch((iface, isKnown) -> mayHaveFinalizer(iface));
     }
     return mayHaveFinalizer(type.getClassType());
   }
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 c15fdb8..f7ac800 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -5,6 +5,8 @@
 
 import static com.android.tools.r8.graph.DexProgramClass.asProgramClassOrNull;
 import static com.android.tools.r8.graph.FieldAccessInfoImpl.MISSING_FIELD_ACCESS_INFO;
+import static com.android.tools.r8.ir.desugar.LambdaDescriptor.isLambdaMetafactoryMethod;
+import static com.android.tools.r8.ir.optimize.enums.UnboxedEnumMemberRelocator.ENUM_UNBOXING_UTILITY_CLASS_SUFFIX;
 import static com.android.tools.r8.naming.IdentifierNameStringUtils.identifyIdentifier;
 import static com.android.tools.r8.naming.IdentifierNameStringUtils.isReflectionMethod;
 import static com.android.tools.r8.shaking.AnnotationRemover.shouldKeepAnnotation;
@@ -19,6 +21,7 @@
 import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.CfCode;
+import com.android.tools.r8.graph.Code;
 import com.android.tools.r8.graph.DexAnnotation;
 import com.android.tools.r8.graph.DexAnnotationSet;
 import com.android.tools.r8.graph.DexApplication;
@@ -55,6 +58,7 @@
 import com.android.tools.r8.graph.LookupLambdaTarget;
 import com.android.tools.r8.graph.LookupTarget;
 import com.android.tools.r8.graph.MethodAccessInfoCollection;
+import com.android.tools.r8.graph.NestMemberClassAttribute;
 import com.android.tools.r8.graph.ObjectAllocationInfoCollectionImpl;
 import com.android.tools.r8.graph.ProgramDefinition;
 import com.android.tools.r8.graph.ProgramField;
@@ -86,6 +90,8 @@
 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.TwrCloseResourceRewriter;
+import com.android.tools.r8.ir.desugar.nest.NestBasedAccessDesugaring;
 import com.android.tools.r8.kotlin.KotlinMetadataEnqueuerExtension;
 import com.android.tools.r8.logging.Log;
 import com.android.tools.r8.naming.identifiernamestring.IdentifierNameStringLookupResult;
@@ -103,12 +109,15 @@
 import com.android.tools.r8.utils.Action;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.InternalOptions.DesugarState;
+import com.android.tools.r8.utils.InternalOptions.OutlineOptions;
 import com.android.tools.r8.utils.IteratorUtils;
+import com.android.tools.r8.utils.ListUtils;
 import com.android.tools.r8.utils.MethodSignatureEquivalence;
 import com.android.tools.r8.utils.OptionalBool;
 import com.android.tools.r8.utils.Pair;
 import com.android.tools.r8.utils.SetUtils;
 import com.android.tools.r8.utils.StringDiagnostic;
+import com.android.tools.r8.utils.ThreadUtils;
 import com.android.tools.r8.utils.Timing;
 import com.android.tools.r8.utils.Visibility;
 import com.android.tools.r8.utils.WorkList;
@@ -118,6 +127,7 @@
 import com.android.tools.r8.utils.collections.SortedProgramMethodSet;
 import com.google.common.base.Equivalence.Wrapper;
 import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Iterables;
 import com.google.common.collect.Maps;
 import com.google.common.collect.Sets;
 import com.google.common.collect.Sets.SetView;
@@ -134,6 +144,7 @@
 import java.util.IdentityHashMap;
 import java.util.LinkedHashMap;
 import java.util.LinkedHashSet;
+import java.util.LinkedList;
 import java.util.List;
 import java.util.ListIterator;
 import java.util.Map;
@@ -162,7 +173,8 @@
   public enum Mode {
     INITIAL_TREE_SHAKING,
     FINAL_TREE_SHAKING,
-    MAIN_DEX_TRACING,
+    INITIAL_MAIN_DEX_TRACING,
+    FINAL_MAIN_DEX_TRACING,
     WHY_ARE_YOU_KEEPING;
 
     public boolean isInitialTreeShaking() {
@@ -177,8 +189,16 @@
       return isInitialTreeShaking() || isFinalTreeShaking();
     }
 
-    public boolean isTracingMainDex() {
-      return this == MAIN_DEX_TRACING;
+    public boolean isInitialMainDexTracing() {
+      return this == INITIAL_MAIN_DEX_TRACING;
+    }
+
+    public boolean isFinalMainDexTracing() {
+      return this == FINAL_MAIN_DEX_TRACING;
+    }
+
+    public boolean isMainDexTracing() {
+      return isInitialMainDexTracing() || isFinalMainDexTracing();
     }
 
     public boolean isWhyAreYouKeeping() {
@@ -198,10 +218,10 @@
   // Don't hold a direct pointer to app info (use appView).
   private AppInfoWithClassHierarchy appInfo;
   private final AppView<AppInfoWithClassHierarchy> appView;
+  private final ExecutorService executorService;
   private SubtypingInfo subtypingInfo;
   private final InternalOptions options;
   private RootSet rootSet;
-  private ProguardClassFilter dontWarnPatterns;
   private final EnqueuerUseRegistryFactory useRegistryFactory;
   private AnnotationRemover.Builder annotationRemoverBuilder;
   private final EnqueuerDefinitionSupplier enqueuerDefinitionSupplier =
@@ -283,7 +303,10 @@
   private final SetWithReason<DexEncodedMethod> targetedMethods;
 
   /** Set of methods that have invalid resolutions or lookups. */
-  private final Set<DexMethod> failedResolutionTargets;
+  private final Set<DexMethod> failedMethodResolutionTargets;
+
+  /** Set of methods that have invalid resolutions or lookups. */
+  private final Set<DexField> failedFieldResolutionTargets;
 
   /**
    * Set of program methods that are used as the bootstrap method for an invoke-dynamic instruction.
@@ -359,23 +382,32 @@
 
   private final LambdaRewriter lambdaRewriter;
   private final BackportedMethodRewriter backportRewriter;
+  private final NestBasedAccessDesugaring nestBasedAccessRewriter;
+  private final TwrCloseResourceRewriter twrCloseResourceRewriter;
+
   private final DesugaredLibraryConversionWrapperAnalysis desugaredLibraryWrapperAnalysis;
   private final Map<DexType, Pair<LambdaClass, ProgramMethod>> lambdaClasses =
       new IdentityHashMap<>();
   private final ProgramMethodMap<Map<DexCallSite, LambdaClass>> lambdaCallSites =
       ProgramMethodMap.create();
   private final Map<DexMethod, ProgramMethod> methodsWithBackports = new IdentityHashMap<>();
+  private final Map<DexMethod, ProgramMethod> methodsWithTwrCloseResource = new IdentityHashMap<>();
   private final Set<DexProgramClass> classesWithSerializableLambdas = Sets.newIdentityHashSet();
+  private final ProgramMethodSet pendingDesugaring = ProgramMethodSet.create();
+  private final MainDexTracingResult previousMainDexTracingResult;
 
   Enqueuer(
       AppView<? extends AppInfoWithClassHierarchy> appView,
+      ExecutorService executorService,
       SubtypingInfo subtypingInfo,
       GraphConsumer keptGraphConsumer,
-      Mode mode) {
+      Mode mode,
+      MainDexTracingResult previousMainDexTracingResult) {
     assert appView.appServices() != null;
     InternalOptions options = appView.options();
     this.appInfo = appView.appInfo();
     this.appView = appView.withClassHierarchy();
+    this.executorService = executorService;
     this.subtypingInfo = subtypingInfo;
     this.forceProguardCompatibility = options.forceProguardCompatibility;
     this.graphReporter = new GraphReporter(appView, keptGraphConsumer);
@@ -384,6 +416,7 @@
     this.options = options;
     this.useRegistryFactory = createUseRegistryFactory();
     this.workList = EnqueuerWorklist.createWorklist();
+    this.previousMainDexTracingResult = previousMainDexTracingResult;
 
     if (mode.isInitialOrFinalTreeShaking()) {
       if (options.protoShrinking().enableGeneratedMessageLiteShrinking) {
@@ -397,12 +430,19 @@
     // 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.
-    failedResolutionTargets = SetUtils.newIdentityHashSet(2);
+    failedMethodResolutionTargets = SetUtils.newIdentityHashSet(2);
+    failedFieldResolutionTargets = SetUtils.newIdentityHashSet(0);
     liveMethods = new LiveMethodsSet(graphReporter::registerMethod);
     liveFields = new LiveFieldsSet(graphReporter::registerField);
     lambdaRewriter = options.desugarState == DesugarState.ON ? new LambdaRewriter(appView) : null;
+    nestBasedAccessRewriter =
+        options.shouldDesugarNests() ? new NestBasedAccessDesugaring(appView) : null;
     backportRewriter =
         options.desugarState == DesugarState.ON ? new BackportedMethodRewriter(appView) : null;
+    twrCloseResourceRewriter =
+        TwrCloseResourceRewriter.enableTwrCloseResourceDesugaring(options)
+            ? new TwrCloseResourceRewriter(appView)
+            : null;
 
     objectAllocationInfoCollection =
         ObjectAllocationInfoCollectionImpl.builder(mode.isInitialTreeShaking(), graphReporter);
@@ -517,10 +557,15 @@
   }
 
   private void recordMethodReference(DexMethod method, ProgramDefinition context) {
-    recordTypeReference(method.holder, context);
-    recordTypeReference(method.proto.returnType, context);
+    recordMethodReference(method, context, this::reportMissingClass);
+  }
+
+  private void recordMethodReference(
+      DexMethod method, ProgramDefinition context, Consumer<DexType> missingClassConsumer) {
+    recordTypeReference(method.holder, context, missingClassConsumer);
+    recordTypeReference(method.proto.returnType, context, missingClassConsumer);
     for (DexType type : method.proto.parameters.values) {
-      recordTypeReference(type, context);
+      recordTypeReference(type, context, missingClassConsumer);
     }
   }
 
@@ -905,13 +950,17 @@
   }
 
   void traceCallSite(DexCallSite callSite, ProgramMethod context) {
-    DexProgramClass bootstrapClass =
-        getProgramHolderOrNull(callSite.bootstrapMethod.asMethod(), context);
-    if (bootstrapClass != null) {
-      bootstrapMethods.add(callSite.bootstrapMethod.asMethod());
+    // Do not lookup java.lang.invoke.LambdaMetafactory when compiling for DEX to avoid reporting
+    // the class as missing.
+    if (options.isGeneratingClassFiles() || !isLambdaMetafactoryMethod(callSite, appInfo())) {
+      DexProgramClass bootstrapClass =
+          getProgramHolderOrNull(callSite.bootstrapMethod.asMethod(), context);
+      if (bootstrapClass != null) {
+        bootstrapMethods.add(callSite.bootstrapMethod.asMethod());
+      }
     }
 
-    LambdaDescriptor descriptor = LambdaDescriptor.tryInfer(callSite, appInfo, context);
+    LambdaDescriptor descriptor = LambdaDescriptor.tryInfer(callSite, appInfo(), context);
     if (descriptor == null) {
       return;
     }
@@ -921,7 +970,7 @@
       assert contextMethod.getCode().isCfCode() : "Unexpected input type with lambdas";
       CfCode code = contextMethod.getCode().asCfCode();
       if (code != null) {
-        LambdaClass lambdaClass = lambdaRewriter.getOrCreateLambdaClass(descriptor, context);
+        LambdaClass lambdaClass = lambdaRewriter.createLambdaClass(descriptor, context);
         lambdaClasses.put(lambdaClass.type, new Pair<>(lambdaClass, context));
         lambdaCallSites
             .computeIfAbsent(context, k -> new IdentityHashMap<>())
@@ -1234,12 +1283,24 @@
     return false;
   }
 
+  private boolean registerCloseResource(DexMethod invokedMethod, ProgramMethod context) {
+    if (twrCloseResourceRewriter != null
+        && TwrCloseResourceRewriter.isTwrCloseResourceMethod(
+            invokedMethod, appView.dexItemFactory())) {
+      methodsWithTwrCloseResource.putIfAbsent(context.getReference(), context);
+      return true;
+    }
+    return false;
+  }
+
   private void traceInvokeStatic(
       DexMethod invokedMethod, ProgramMethod context, KeepReason reason) {
     if (registerBackportInvoke(invokedMethod, context)) {
       return;
     }
-
+    if (registerCloseResource(invokedMethod, context)) {
+      return;
+    }
     DexItemFactory dexItemFactory = appView.dexItemFactory();
     if (dexItemFactory.classMethods.isReflectiveClassLookup(invokedMethod)
         || dexItemFactory.atomicFieldUpdaterMethods.isFieldUpdater(invokedMethod)) {
@@ -1659,18 +1720,38 @@
       return;
     }
 
+    assert !mode.isFinalMainDexTracing()
+            || !options.testing.checkForNotExpandingMainDexTracingResult
+            || previousMainDexTracingResult.isRoot(clazz)
+            || clazz.toSourceString().contains(ENUM_UNBOXING_UTILITY_CLASS_SUFFIX)
+            // TODO(b/177847090): Consider not outlining anything in main dex.
+            || clazz.toSourceString().contains(OutlineOptions.CLASS_NAME)
+        : "Class " + clazz.toSourceString() + " was not a main dex root in the first round";
+
     // Mark types in inner-class attributes referenced.
     for (InnerClassAttribute innerClassAttribute : clazz.getInnerClasses()) {
       recordTypeReference(innerClassAttribute.getInner(), clazz, this::ignoreMissingClass);
       recordTypeReference(innerClassAttribute.getOuter(), clazz, this::ignoreMissingClass);
     }
+
+    // Mark types in nest attributes referenced.
+    if (clazz.isNestHost()) {
+      for (NestMemberClassAttribute nestMemberClassAttribute :
+          clazz.getNestMembersClassAttributes()) {
+        recordTypeReference(nestMemberClassAttribute.getNestMember(), clazz);
+      }
+    } else {
+      recordTypeReference(clazz.getNestHost(), clazz);
+    }
+
     EnclosingMethodAttribute enclosingMethodAttribute = clazz.getEnclosingMethodAttribute();
     if (enclosingMethodAttribute != null) {
       DexMethod enclosingMethod = enclosingMethodAttribute.getEnclosingMethod();
       if (enclosingMethod != null) {
-        recordMethodReference(enclosingMethod, clazz);
+        recordMethodReference(enclosingMethod, clazz, this::ignoreMissingClass);
       } else {
-        recordTypeReference(enclosingMethodAttribute.getEnclosingClass(), clazz);
+        recordTypeReference(
+            enclosingMethodAttribute.getEnclosingClass(), clazz, this::ignoreMissingClass);
       }
     }
 
@@ -1680,7 +1761,7 @@
 
     KeepReason reason = KeepReason.reachableFromLiveType(clazz.type);
 
-    for (DexType iface : clazz.interfaces.values) {
+    for (DexType iface : clazz.getInterfaces()) {
       markInterfaceTypeAsLiveViaInheritanceClause(iface, clazz);
     }
 
@@ -1766,33 +1847,40 @@
 
     if (!appView.options().enableUnusedInterfaceRemoval
         || rootSet.noUnusedInterfaceRemoval.contains(type)
-        || mode.isTracingMainDex()) {
+        || mode.isMainDexTracing()) {
       markTypeAsLive(clazz, implementer);
-    } else {
-      if (liveTypes.contains(clazz)) {
-        // The interface is already live, so make sure to report this implements-edge.
-        graphReporter.reportClassReferencedFrom(clazz, implementer);
-      } else {
-        // No need to mark the type as live. If an interface type is only reachable via the
-        // inheritance clause of another type it can simply be removed from the inheritance clause.
-        // The interface is needed if it has a live default interface method or field, though.
-        // Therefore, we record that this implemented-by edge has not been reported, such that we
-        // can report it in the future if one its members becomes live.
-        WorkList<DexProgramClass> worklist = WorkList.newIdentityWorkList();
-        worklist.addIfNotSeen(clazz);
-        while (worklist.hasNext()) {
-          DexProgramClass current = worklist.next();
-          if (liveTypes.contains(current)) {
-            continue;
-          }
-          Set<DexProgramClass> implementors =
-              unusedInterfaceTypes.computeIfAbsent(current, ignore -> Sets.newIdentityHashSet());
-          if (implementors.add(implementer)) {
-            for (DexType iface : current.interfaces.values) {
-              DexProgramClass definition = getProgramClassOrNull(iface, current);
-              if (definition != null) {
-                worklist.addIfNotSeen(definition);
-              }
+      return;
+    }
+
+    if (liveTypes.contains(clazz)) {
+      // The interface is already live, so make sure to report this implements-edge.
+      graphReporter.reportClassReferencedFrom(clazz, implementer);
+      return;
+    }
+
+    // No need to mark the type as live. If an interface type is only reachable via the
+    // inheritance clause of another type it can simply be removed from the inheritance clause.
+    // The interface is needed if it has a live default interface method or field, though.
+    // Therefore, we record that this implemented-by edge has not been reported, such that we
+    // can report it in the future if one its members becomes live.
+    WorkList<DexProgramClass> worklist = WorkList.newIdentityWorkList();
+    worklist.addIfNotSeen(clazz);
+    while (worklist.hasNext()) {
+      DexProgramClass current = worklist.next();
+      if (liveTypes.contains(current)) {
+        continue;
+      }
+      Set<DexProgramClass> implementors =
+          unusedInterfaceTypes.computeIfAbsent(current, ignore -> Sets.newIdentityHashSet());
+      if (implementors.add(implementer)) {
+        for (DexType iface : current.getInterfaces()) {
+          DexProgramClass definition = getProgramClassOrNull(iface, current);
+          if (definition != null) {
+            if (definition.isPublic()
+                || implementer.getType().isSamePackage(definition.getType())) {
+              worklist.addIfNotSeen(definition);
+            } else {
+              markTypeAsLive(current, implementer);
             }
           }
         }
@@ -1859,6 +1947,7 @@
     FieldResolutionResult resolutionResult = appInfo.resolveField(field);
     if (resolutionResult.isFailedOrUnknownResolution()) {
       reportMissingField(field);
+      failedFieldResolutionTargets.add(field);
     }
     return resolutionResult;
   }
@@ -1870,7 +1959,8 @@
     ResolutionResult resolutionResult = appInfo.unsafeResolveMethodDueToDexFormat(method);
     if (resolutionResult.isFailedResolution()) {
       reportMissingMethod(method);
-      markFailedResolutionTargets(method, resolutionResult.asFailedResolution(), context, reason);
+      markFailedMethodResolutionTargets(
+          method, resolutionResult.asFailedResolution(), context, reason);
     }
     return resolutionResult.asSingleResolution();
   }
@@ -1882,7 +1972,8 @@
     ResolutionResult resolutionResult = appInfo.resolveMethod(method, interfaceInvoke);
     if (resolutionResult.isFailedResolution()) {
       reportMissingMethod(method);
-      markFailedResolutionTargets(method, resolutionResult.asFailedResolution(), context, reason);
+      markFailedMethodResolutionTargets(
+          method, resolutionResult.asFailedResolution(), context, reason);
     }
     return resolutionResult.asSingleResolution();
   }
@@ -2020,6 +2111,7 @@
     // TODO(zerny): Is it ok that we lookup in both the direct and virtual pool here?
     DexEncodedMethod encodedMethod = clazz.lookupMethod(reference);
     if (encodedMethod == null) {
+      failedMethodResolutionTargets.add(reference);
       reportMissingMethod(reference);
       return;
     }
@@ -2047,7 +2139,7 @@
   }
 
   private void ensureFromLibraryOrThrow(DexType type, DexLibraryClass context) {
-    if (mode.isTracingMainDex()) {
+    if (mode.isMainDexTracing()) {
       // b/72312389: android.jar contains parts of JUnit and most developers include JUnit in
       // their programs. This leads to library classes extending program classes. When tracing
       // main dex lists we allow this.
@@ -2071,7 +2163,7 @@
             }
           });
     }
-    if (dontWarnPatterns.matches(context.type)) {
+    if (appView.getDontWarnConfiguration().matches(context)) {
       // Ignore.
       return;
     }
@@ -2245,20 +2337,24 @@
   private void checkLambdaInterface(DexType itf, ProgramMethod context) {
     DexClass clazz = definitionFor(itf, context);
     if (clazz == null) {
-      StringDiagnostic message =
-          new StringDiagnostic(
-              "Lambda expression implements missing interface `" + itf.toSourceString() + "`",
-              context.getOrigin());
-      options.reporter.warning(message);
+      if (!appView.getDontWarnConfiguration().matches(itf)) {
+        StringDiagnostic message =
+            new StringDiagnostic(
+                "Lambda expression implements missing interface `" + itf.toSourceString() + "`",
+                context.getOrigin());
+        options.reporter.warning(message);
+      }
     } else if (!clazz.isInterface()) {
-      StringDiagnostic message =
-          new StringDiagnostic(
-              "Lambda expression expected to implement an interface, but found "
-                  + "`"
-                  + itf.toSourceString()
-                  + "`",
-              context.getOrigin());
-      options.reporter.warning(message);
+      if (!appView.getDontWarnConfiguration().matches(itf)) {
+        StringDiagnostic message =
+            new StringDiagnostic(
+                "Lambda expression expected to implement an interface, but found "
+                    + "`"
+                    + itf.toSourceString()
+                    + "`",
+                context.getOrigin());
+        options.reporter.warning(message);
+      }
     }
   }
 
@@ -2385,7 +2481,7 @@
   private void markLibraryAndClasspathMethodOverridesAsLive(
       InstantiatedObject instantiation, DexClass libraryClass) {
     assert libraryClass.isNotProgramClass();
-    if (mode.isTracingMainDex()) {
+    if (mode.isMainDexTracing()) {
       // Library roots must be specified for tracing of library methods. For classpath the expected
       // use case is that the classes will be classloaded, thus they should have no bearing on the
       // content of the main dex file.
@@ -2804,17 +2900,17 @@
     }
   }
 
-  private void markFailedResolutionTargets(
+  private void markFailedMethodResolutionTargets(
       DexMethod symbolicMethod,
       FailedResolutionResult failedResolution,
       ProgramDefinition context,
       KeepReason reason) {
-    failedResolutionTargets.add(symbolicMethod);
+    failedMethodResolutionTargets.add(symbolicMethod);
     failedResolution.forEachFailureDependency(
         method -> {
           DexProgramClass clazz = getProgramClassOrNull(method.getHolderType(), context);
           if (clazz != null) {
-            failedResolutionTargets.add(method.method);
+            failedMethodResolutionTargets.add(method.method);
             markMethodAsTargeted(new ProgramMethod(clazz, method), reason);
           }
         });
@@ -2860,7 +2956,7 @@
     // If invoke target is invalid (inaccessible or not an instance-method) record it and stop.
     DexClassAndMethod target = resolution.lookupInvokeSuperTarget(from.getHolder(), appInfo);
     if (target == null) {
-      failedResolutionTargets.add(resolution.getResolvedMethod().method);
+      failedMethodResolutionTargets.add(resolution.getResolvedMethod().method);
       return;
     }
 
@@ -2891,7 +2987,7 @@
   public Set<DexProgramClass> traceMainDex(
       RootSet rootSet, ExecutorService executorService, Timing timing) throws ExecutionException {
     assert analyses.isEmpty();
-    assert mode.isTracingMainDex();
+    assert mode.isMainDexTracing();
     this.rootSet = rootSet;
     // Translate the result of root-set computation into enqueuer actions.
     enqueueRootItems(rootSet.noShrinking);
@@ -2902,12 +2998,10 @@
 
   public AppInfoWithLiveness traceApplication(
       RootSet rootSet,
-      ProguardClassFilter dontWarnPatterns,
       ExecutorService executorService,
       Timing timing)
       throws ExecutionException {
     this.rootSet = rootSet;
-    this.dontWarnPatterns = dontWarnPatterns;
     // Translate the result of root-set computation into enqueuer actions.
     if (appView.options().getProguardConfiguration() != null
         && !options.kotlinOptimizationOptions().disableKotlinSpecificOptimizations) {
@@ -3012,6 +3106,8 @@
 
   private static class SyntheticAdditions {
 
+    List<ProgramMethod> desugaredMethods = new LinkedList<>();
+
     Map<DexType, Pair<DexProgramClass, ProgramMethod>> syntheticInstantiations =
         new IdentityHashMap<>();
 
@@ -3020,6 +3116,8 @@
 
     Map<DexMethod, ProgramMethod> liveMethods = new IdentityHashMap<>();
 
+    Map<DexType, DexProgramClass> syntheticProgramClasses = new IdentityHashMap<>();
+
     Map<DexType, DexClasspathClass> syntheticClasspathClasses = new IdentityHashMap<>();
 
     // Subset of live methods that need have keep requirements.
@@ -3031,20 +3129,18 @@
 
     boolean isEmpty() {
       boolean empty =
-          syntheticInstantiations.isEmpty()
+          desugaredMethods.isEmpty()
+              && syntheticInstantiations.isEmpty()
               && liveMethods.isEmpty()
+              && syntheticProgramClasses.isEmpty()
               && syntheticClasspathClasses.isEmpty();
       assert !empty || (liveMethodsWithKeepActions.isEmpty() && mainDexTypes.isEmpty());
       return empty;
     }
 
-    void addInstantiatedClass(
-        DexProgramClass clazz, ProgramMethod context, boolean isMainDexClass) {
+    void addInstantiatedClass(DexProgramClass clazz, ProgramMethod context) {
       assert !syntheticInstantiations.containsKey(clazz.type);
       syntheticInstantiations.put(clazz.type, new Pair<>(clazz, context));
-      if (isMainDexClass) {
-        mainDexTypes.add(clazz);
-      }
     }
 
     void addClasspathClass(DexClasspathClass clazz) {
@@ -3052,6 +3148,11 @@
       assert old == null;
     }
 
+    void addProgramClass(DexProgramClass clazz) {
+      DexProgramClass old = syntheticProgramClasses.put(clazz.type, clazz);
+      assert old == null;
+    }
+
     void addLiveMethod(ProgramMethod method) {
       DexMethod signature = method.getDefinition().method;
       assert !liveMethods.containsKey(signature);
@@ -3066,10 +3167,7 @@
 
     void amendApplication(Builder appBuilder) {
       assert !isEmpty();
-      for (Pair<DexProgramClass, ProgramMethod> clazzAndContext :
-          syntheticInstantiations.values()) {
-        appBuilder.addProgramClass(clazzAndContext.getFirst());
-      }
+      appBuilder.addProgramClasses(syntheticProgramClasses.values());
       appBuilder.addClasspathClasses(syntheticClasspathClasses.values());
     }
 
@@ -3081,9 +3179,14 @@
     void enqueueWorkItems(Enqueuer enqueuer) {
       assert !isEmpty();
       assert enqueuer.mode.isInitialTreeShaking();
+
       // All synthetic additions are initial tree shaking only. No need to track keep reasons.
       KeepReasonWitness fakeReason = enqueuer.graphReporter.fakeReportShouldNotBeUsed();
 
+      for (ProgramMethod desugaredMethod : desugaredMethods) {
+        enqueuer.workList.enqueueTraceCodeAction(desugaredMethod);
+      }
+
       liveMethodsWithKeepActions.forEach(
           item -> enqueuer.keepInfo.joinMethod(item.getFirst(), item.getSecond()));
       for (Pair<DexProgramClass, ProgramMethod> clazzAndContext :
@@ -3127,7 +3230,7 @@
     }
   }
 
-  private void synthesize() {
+  private void synthesize() throws ExecutionException {
     if (!mode.isInitialTreeShaking()) {
       return;
     }
@@ -3135,11 +3238,14 @@
     // In particular these additions are order independent, i.e., it does not matter which are
     // registered first and no dependencies may exist among them.
     SyntheticAdditions additions = new SyntheticAdditions();
+    desugar(additions);
     synthesizeInvokeSpecialBridges(additions);
     synthesizeInterfaceMethodBridges(additions);
     synthesizeLambdas(additions);
     synthesizeLibraryConversionWrappers(additions);
     synthesizeBackports(additions);
+    synthesizeNestConstructor(additions);
+    synthesizeTwrCloseResource(additions);
     if (additions.isEmpty()) {
       return;
     }
@@ -3161,6 +3267,50 @@
     additions.enqueueWorkItems(this);
   }
 
+  private void desugar(SyntheticAdditions additions) throws ExecutionException {
+    ThreadUtils.processItems(pendingDesugaring, this::desugar, executorService);
+    Iterables.addAll(additions.desugaredMethods, pendingDesugaring);
+    pendingDesugaring.clear();
+  }
+
+  private void desugar(ProgramMethod method) {
+    Code code = method.getDefinition().getCode();
+    if (!code.isCfCode()) {
+      appView
+          .options()
+          .reporter
+          .error(
+              new StringDiagnostic(
+                  "Unsupported attempt to desugar non-CF code",
+                  method.getOrigin(),
+                  method.getPosition()));
+      return;
+    }
+
+    CfCode cfCode = code.asCfCode();
+    List<CfInstruction> desugaredInstructions =
+        ListUtils.flatMap(
+            cfCode.getInstructions(),
+            instruction -> {
+              // TODO(b/177810578): Migrate other cf-to-cf based desugaring here, and assert that
+              //  that at most one instruction desugarer applies to each instruction.
+              if (nestBasedAccessRewriter != null) {
+                List<CfInstruction> replacement =
+                    nestBasedAccessRewriter.desugarInstruction(instruction, method, null);
+                if (replacement != null) {
+                  return replacement;
+                }
+              }
+              return null;
+            },
+            null);
+    if (desugaredInstructions != null) {
+      cfCode.setInstructions(desugaredInstructions);
+    } else {
+      assert false : "Expected code to be desugared";
+    }
+  }
+
   private void synthesizeInvokeSpecialBridges(SyntheticAdditions additions) {
     SortedProgramMethodSet bridges =
         appView.getInvokeSpecialBridgeSynthesizer().insertBridgesForR8();
@@ -3183,6 +3333,22 @@
     }
   }
 
+  private void synthesizeNestConstructor(SyntheticAdditions additions) {
+    if (nestBasedAccessRewriter != null) {
+      DexProgramClass nestConstructor = nestBasedAccessRewriter.synthesizeNestConstructor();
+      if (nestConstructor != null) {
+        // TODO(b/177638147): use getSyntheticItems().createClass().
+        additions.addProgramClass(nestConstructor);
+      }
+    }
+  }
+
+  private void synthesizeTwrCloseResource(SyntheticAdditions additions) {
+    for (ProgramMethod method : methodsWithTwrCloseResource.values()) {
+      twrCloseResourceRewriter.rewriteCf(method, additions::addLiveMethod);
+    }
+  }
+
   private void synthesizeLambdas(SyntheticAdditions additions) {
     if (lambdaRewriter == null || lambdaClasses.isEmpty()) {
       assert lambdaCallSites.isEmpty();
@@ -3193,8 +3359,8 @@
       // Add all desugared classes to the application, main-dex list, and mark them instantiated.
       LambdaClass lambdaClass = lambdaClassAndContext.getFirst();
       ProgramMethod context = lambdaClassAndContext.getSecond();
-      DexProgramClass programClass = lambdaClass.getOrCreateLambdaClass();
-      additions.addInstantiatedClass(programClass, context, lambdaClass.addToMainDexList.get());
+      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);
@@ -3299,11 +3465,12 @@
             appInfo.getMainDexClasses(),
             deadProtoTypes,
             appView.testing().enableExperimentalMissingClassesReporting
-                ? missingClassesBuilder.reportMissingClasses(options)
+                ? missingClassesBuilder.reportMissingClasses(appView)
                 : missingClassesBuilder.ignoreMissingClasses(),
             SetUtils.mapIdentityHashSet(liveTypes.getItems(), DexProgramClass::getType),
             Enqueuer.toDescriptorSet(targetedMethods.getItems()),
-            Collections.unmodifiableSet(failedResolutionTargets),
+            Collections.unmodifiableSet(failedMethodResolutionTargets),
+            Collections.unmodifiableSet(failedFieldResolutionTargets),
             Collections.unmodifiableSet(bootstrapMethods),
             Collections.unmodifiableSet(methodsTargetedByInvokeDynamic),
             Collections.unmodifiableSet(virtualMethodsTargetedByInvokeDirect),
@@ -3349,10 +3516,9 @@
     lambdaRewriter
         .getKnownLambdaClasses()
         .forEach(
-            (type, lambda) -> {
-              DexProgramClass synthesizedClass = lambda.getOrCreateLambdaClass();
+            lambda -> {
+              DexProgramClass synthesizedClass = lambda.getLambdaProgramClass();
               assert synthesizedClass != null;
-              assert synthesizedClass == appInfo().definitionForWithoutExistenceAssert(type);
               assert liveTypes.contains(synthesizedClass);
               if (synthesizedClass == null) {
                 return;
@@ -3835,7 +4001,8 @@
     processAnnotations(holder, method);
     definition.parameterAnnotationsList.forEachAnnotation(
         annotation -> processAnnotation(holder, method, annotation));
-    method.registerCodeReferences(useRegistryFactory.create(appView, method, this));
+
+    traceNonDesugaredCode(method);
 
     // Add all dependent members to the workqueue.
     enqueueRootItems(rootSet.getDependentItems(definition));
@@ -3846,6 +4013,21 @@
     analyses.forEach(analysis -> analysis.processNewlyLiveMethod(method, context));
   }
 
+  private void traceNonDesugaredCode(ProgramMethod method) {
+    if (getMode().isInitialTreeShaking()) {
+      if (nestBasedAccessRewriter != null && nestBasedAccessRewriter.needsDesugaring(method)) {
+        pendingDesugaring.add(method);
+        return;
+      }
+    }
+
+    traceCode(method);
+  }
+
+  void traceCode(ProgramMethod method) {
+    method.registerCodeReferences(useRegistryFactory.create(appView, method, this));
+  }
+
   private void checkMemberForSoftPinning(ProgramMember<?, ?> member) {
     DexMember<?, ?> reference = member.getDefinition().getReference();
     Set<ProguardKeepRuleBase> softPinRules = rootSet.softPinned.getRulesForReference(reference);
@@ -4510,7 +4692,7 @@
     }
 
     public DexClass definitionFor(DexType type, ProgramDefinition context) {
-      return enqueuer.definitionFor(type, context);
+      return enqueuer.definitionFor(type, context, enqueuer::ignoreMissingClass);
     }
   }
 }
diff --git a/src/main/java/com/android/tools/r8/shaking/EnqueuerFactory.java b/src/main/java/com/android/tools/r8/shaking/EnqueuerFactory.java
index 34a8750..04fb840 100644
--- a/src/main/java/com/android/tools/r8/shaking/EnqueuerFactory.java
+++ b/src/main/java/com/android/tools/r8/shaking/EnqueuerFactory.java
@@ -11,45 +11,82 @@
 import com.android.tools.r8.graph.SubtypingInfo;
 import com.android.tools.r8.shaking.Enqueuer.Mode;
 import java.util.Set;
+import java.util.concurrent.ExecutorService;
 
 public class EnqueuerFactory {
 
   public static Enqueuer createForInitialTreeShaking(
       AppView<? extends AppInfoWithClassHierarchy> appView,
+      ExecutorService executorService,
       SubtypingInfo subtypingInfo) {
-    return new Enqueuer(appView, subtypingInfo, null, Mode.INITIAL_TREE_SHAKING);
+    return new Enqueuer(
+        appView,
+        executorService,
+        subtypingInfo,
+        null,
+        Mode.INITIAL_TREE_SHAKING,
+        MainDexTracingResult.NONE);
   }
 
   public static Enqueuer createForFinalTreeShaking(
       AppView<? extends AppInfoWithClassHierarchy> appView,
+      ExecutorService executorService,
       SubtypingInfo subtypingInfo,
       GraphConsumer keptGraphConsumer,
       Set<DexType> initialPrunedTypes) {
     Enqueuer enqueuer =
-        new Enqueuer(appView, subtypingInfo, keptGraphConsumer, Mode.FINAL_TREE_SHAKING);
+        new Enqueuer(
+            appView,
+            executorService,
+            subtypingInfo,
+            keptGraphConsumer,
+            Mode.FINAL_TREE_SHAKING,
+            MainDexTracingResult.NONE);
     appView.withProtoShrinker(
         shrinker -> enqueuer.setInitialDeadProtoTypes(shrinker.getDeadProtoTypes()));
     enqueuer.setInitialPrunedTypes(initialPrunedTypes);
     return enqueuer;
   }
 
-  public static Enqueuer createForMainDexTracing(
+  public static Enqueuer createForInitialMainDexTracing(
       AppView<? extends AppInfoWithClassHierarchy> appView,
+      ExecutorService executorService,
       SubtypingInfo subtypingInfo) {
-    return createForMainDexTracing(appView, subtypingInfo, null);
+    return new Enqueuer(
+        appView,
+        executorService,
+        subtypingInfo,
+        null,
+        Mode.INITIAL_MAIN_DEX_TRACING,
+        MainDexTracingResult.NONE);
   }
 
-  public static Enqueuer createForMainDexTracing(
+  public static Enqueuer createForFinalMainDexTracing(
       AppView<? extends AppInfoWithClassHierarchy> appView,
+      ExecutorService executorService,
       SubtypingInfo subtypingInfo,
-      GraphConsumer keptGraphConsumer) {
-    return new Enqueuer(appView, subtypingInfo, keptGraphConsumer, Mode.MAIN_DEX_TRACING);
+      GraphConsumer keptGraphConsumer,
+      MainDexTracingResult previousMainDexTracingResult) {
+    return new Enqueuer(
+        appView,
+        executorService,
+        subtypingInfo,
+        keptGraphConsumer,
+        Mode.FINAL_MAIN_DEX_TRACING,
+        previousMainDexTracingResult);
   }
 
   public static Enqueuer createForWhyAreYouKeeping(
       AppView<? extends AppInfoWithClassHierarchy> appView,
+      ExecutorService executorService,
       SubtypingInfo subtypingInfo,
       GraphConsumer keptGraphConsumer) {
-    return new Enqueuer(appView, subtypingInfo, keptGraphConsumer, Mode.WHY_ARE_YOU_KEEPING);
+    return new Enqueuer(
+        appView,
+        executorService,
+        subtypingInfo,
+        keptGraphConsumer,
+        Mode.WHY_ARE_YOU_KEEPING,
+        MainDexTracingResult.NONE);
   }
 }
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 0be900c..b403b54 100644
--- a/src/main/java/com/android/tools/r8/shaking/EnqueuerWorklist.java
+++ b/src/main/java/com/android/tools/r8/shaking/EnqueuerWorklist.java
@@ -174,6 +174,19 @@
     }
   }
 
+  static class TraceCodeAction extends EnqueuerAction {
+    private final ProgramMethod method;
+
+    TraceCodeAction(ProgramMethod method) {
+      this.method = method;
+    }
+
+    @Override
+    public void run(Enqueuer enqueuer) {
+      enqueuer.traceCode(method);
+    }
+  }
+
   static class TraceConstClassAction extends EnqueuerAction {
     private final DexType type;
     // TODO(b/175854431): Avoid pushing context on worklist.
@@ -304,6 +317,10 @@
     queue.add(new MarkFieldKeptAction(field, witness));
   }
 
+  public void enqueueTraceCodeAction(ProgramMethod method) {
+    queue.add(new TraceCodeAction(method));
+  }
+
   public void enqueueTraceConstClassAction(DexType type, ProgramMethod context) {
     queue.add(new TraceConstClassAction(type, context));
   }
diff --git a/src/main/java/com/android/tools/r8/shaking/GraphReporter.java b/src/main/java/com/android/tools/r8/shaking/GraphReporter.java
index 86b0d08..6459b7f 100644
--- a/src/main/java/com/android/tools/r8/shaking/GraphReporter.java
+++ b/src/main/java/com/android/tools/r8/shaking/GraphReporter.java
@@ -423,7 +423,7 @@
   }
 
   GraphEdgeInfo getEdgeInfo(EdgeKind kind) {
-    return reasonInfo.computeIfAbsent(kind, k -> new GraphEdgeInfo(k));
+    return reasonInfo.computeIfAbsent(kind, GraphEdgeInfo::new);
   }
 
   private DexClass definitionFor(DexType type) {
diff --git a/src/main/java/com/android/tools/r8/shaking/MainDexTracingResult.java b/src/main/java/com/android/tools/r8/shaking/MainDexTracingResult.java
index 3af323c..5bf287f 100644
--- a/src/main/java/com/android/tools/r8/shaking/MainDexTracingResult.java
+++ b/src/main/java/com/android/tools/r8/shaking/MainDexTracingResult.java
@@ -146,6 +146,10 @@
     return getRoots().contains(definition.getContextType());
   }
 
+  public boolean isRoot(DexType type) {
+    return getRoots().contains(type);
+  }
+
   public boolean isDependency(ProgramDefinition definition) {
     return getDependencies().contains(definition.getContextType());
   }
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 d3cd164..e296cd7 100644
--- a/src/main/java/com/android/tools/r8/shaking/MissingClasses.java
+++ b/src/main/java/com/android/tools/r8/shaking/MissingClasses.java
@@ -4,6 +4,7 @@
 
 package com.android.tools.r8.shaking;
 
+import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.synthesis.CommittedItems;
@@ -90,11 +91,13 @@
       return build();
     }
 
-    public MissingClasses reportMissingClasses(InternalOptions options) {
+    public MissingClasses reportMissingClasses(AppView<?> appView) {
+      InternalOptions options = appView.options();
       Set<DexType> newMissingClassesWithoutDontWarn =
-          options.getProguardConfiguration().getDontWarnPatterns().getNonMatches(newMissingClasses);
+          appView.getDontWarnConfiguration().getNonMatches(newMissingClasses);
+      newMissingClassesWithoutDontWarn.removeAll(alreadyMissingClasses);
       newMissingClassesWithoutDontWarn.removeAll(
-          getAllowedMissingClasses(options.dexItemFactory()));
+          getAllowedMissingClasses(appView.dexItemFactory()));
       if (!newMissingClassesWithoutDontWarn.isEmpty()) {
         MissingClassesDiagnostic diagnostic =
             new MissingClassesDiagnostic.Builder()
@@ -111,10 +114,18 @@
     }
 
     private static Collection<DexType> getAllowedMissingClasses(DexItemFactory dexItemFactory) {
-      return ImmutableList.of(dexItemFactory.annotationThrows);
+      return ImmutableList.of(
+          dexItemFactory.annotationDefault,
+          dexItemFactory.annotationMethodParameters,
+          dexItemFactory.annotationSourceDebugExtension,
+          dexItemFactory.annotationThrows,
+          // TODO(b/176133674) StringConcatFactory is backported, but the class is reported as
+          //  missing because the enqueuer runs prior to backporting and thus sees the non-desugared
+          //  code.
+          dexItemFactory.stringConcatFactoryType);
     }
 
-    /** Intentionally private, use {@link Builder#reportMissingClasses(InternalOptions)}. */
+    /** Intentionally private, use {@link Builder#reportMissingClasses(AppView)}. */
     private MissingClasses build() {
       // Extend the newMissingClasses set with all other missing classes.
       //
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardClassFilter.java b/src/main/java/com/android/tools/r8/shaking/ProguardClassFilter.java
index 88c13ec..2580817 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardClassFilter.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardClassFilter.java
@@ -4,14 +4,9 @@
 
 package com.android.tools.r8.shaking;
 
-import static com.android.tools.r8.utils.PredicateUtils.not;
-
 import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.shaking.ProguardTypeMatcher.MatchSpecificType;
-import com.android.tools.r8.utils.TraversalContinuation;
 import com.google.common.collect.ImmutableList;
-import com.google.common.collect.Sets;
-import java.util.Set;
+import java.util.List;
 
 public class ProguardClassFilter {
   private static ProguardClassFilter EMPTY = new ProguardClassFilter(ImmutableList.of());
@@ -46,6 +41,10 @@
     return EMPTY;
   }
 
+  public List<ProguardClassNameList> getPatterns() {
+    return patterns;
+  }
+
   public boolean isEmpty() {
     return patterns.size() == 0;
   }
@@ -58,47 +57,4 @@
     }
     return false;
   }
-
-  public Set<DexType> getNonMatches(Set<DexType> types) {
-    Set<DexType> nonMatches = Sets.newIdentityHashSet();
-    for (DexType type : types) {
-      TraversalContinuation traversalContinuation = TraversalContinuation.CONTINUE;
-      for (ProguardClassNameList pattern : patterns) {
-        traversalContinuation =
-            pattern.traverseTypeMatchers(
-                matcher -> {
-                  if (matcher.matches(type)) {
-                    return TraversalContinuation.BREAK;
-                  }
-                  return TraversalContinuation.CONTINUE;
-                },
-                not(ProguardTypeMatcher::hasSpecificType));
-        if (traversalContinuation.shouldBreak()) {
-          break;
-        }
-      }
-      if (traversalContinuation.shouldContinue()) {
-        nonMatches.add(type);
-      }
-    }
-    for (ProguardClassNameList pattern : patterns) {
-      pattern.forEachTypeMatcher(
-          matcher -> nonMatches.remove(matcher.getSpecificType()),
-          ProguardTypeMatcher::hasSpecificType);
-    }
-    return nonMatches;
-  }
-
-  public void filterOutMatches(Set<DexType> types) {
-    for (ProguardClassNameList pattern : patterns) {
-      pattern.forEachTypeMatcher(matcher -> {
-        if (matcher instanceof MatchSpecificType) {
-          assert matcher.getSpecificType() != null;
-          types.remove(matcher.getSpecificType());
-        } else {
-          types.removeIf(matcher::matches);
-        }
-      });
-    }
-  }
 }
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardConfiguration.java b/src/main/java/com/android/tools/r8/shaking/ProguardConfiguration.java
index 7d8d88a..8fdd330 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardConfiguration.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardConfiguration.java
@@ -3,6 +3,7 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.shaking;
 
+import com.android.tools.r8.errors.dontwarn.DontWarnConfiguration;
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.naming.DictionaryReader;
 import com.android.tools.r8.origin.Origin;
@@ -627,7 +628,12 @@
     return keepPackageNamesPatterns;
   }
 
-  public ProguardClassFilter getDontWarnPatterns() {
+  public boolean hasDontWarnPatterns() {
+    return !dontWarnPatterns.isEmpty();
+  }
+
+  public ProguardClassFilter getDontWarnPatterns(DontWarnConfiguration.Witness witness) {
+    assert witness != null;
     return dontWarnPatterns;
   }
 
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
index 47ac1e3..49e9f1e 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
@@ -40,6 +40,7 @@
 import java.util.List;
 import java.util.function.BiConsumer;
 import java.util.function.Consumer;
+import java.util.function.IntPredicate;
 import java.util.function.Predicate;
 
 public class ProguardConfigurationParser {
@@ -1726,25 +1727,25 @@
       return Integer.parseInt(s);
     }
 
-    private final Predicate<Integer> CLASS_NAME_PREDICATE =
-        codePoint ->
-            IdentifierUtils.isDexIdentifierPart(codePoint)
-                || codePoint == '.'
-                || codePoint == '*'
-                || codePoint == '?'
-                || codePoint == '%'
-                || codePoint == '['
-                || codePoint == ']';
+    private boolean isClassName(int codePoint) {
+      return IdentifierUtils.isDexIdentifierPart(codePoint)
+          || codePoint == '.'
+          || codePoint == '*'
+          || codePoint == '?'
+          || codePoint == '%'
+          || codePoint == '['
+          || codePoint == ']';
+    }
 
-    private final Predicate<Integer> PACKAGE_NAME_PREDICATE =
-        codePoint ->
-            IdentifierUtils.isDexIdentifierPart(codePoint)
-                || codePoint == '.'
-                || codePoint == '*'
-                || codePoint == '?';
+    private boolean isPackageName(int codePoint) {
+      return IdentifierUtils.isDexIdentifierPart(codePoint)
+          || codePoint == '.'
+          || codePoint == '*'
+          || codePoint == '?';
+    }
 
     private String acceptClassName() {
-      return acceptString(CLASS_NAME_PREDICATE);
+      return acceptString(this::isClassName);
     }
 
     private IdentifierPatternWithWildcards acceptIdentifierWithBackreference(IdentifierType kind) {
@@ -1842,8 +1843,8 @@
           wildcardsCollector.add(new ProguardWildcard.Pattern(String.valueOf((char) current)));
           end += Character.charCount(current);
         } else if (kind == IdentifierType.PACKAGE_NAME
-            ? PACKAGE_NAME_PREDICATE.test(current)
-            : (CLASS_NAME_PREDICATE.test(current) || current == '>')) {
+            ? isPackageName(current)
+            : (isClassName(current) || current == '>')) {
           end += Character.charCount(current);
         } else if (kind != IdentifierType.PACKAGE_NAME && current == '<') {
           currentBackreference = new StringBuilder();
@@ -1942,7 +1943,7 @@
                   || codePoint == '.');
     }
 
-    private String acceptString(Predicate<Integer> codepointAcceptor) {
+    private String acceptString(IntPredicate codepointAcceptor) {
       int start = position;
       int end = position;
       while (!eof(end)) {
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationUtils.java b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationUtils.java
index 42b56bd..c6afc11 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationUtils.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationUtils.java
@@ -134,11 +134,15 @@
       if (rule.getClassType() != ProguardClassType.CLASS) {
         continue;
       }
+      if (!rule.getClassAnnotations().isEmpty() || !rule.getInheritanceAnnotations().isEmpty()) {
+        continue;
+      }
       if (rule.hasInheritanceClassName()
           && !rule.getInheritanceClassName().matches(factory.objectType)) {
         continue;
       }
-      if (!rule.getClassNames().matches(factory.androidOsBuildVersionType)) {
+      if (rule.getClassNames().hasWildcards()
+          || !rule.getClassNames().matches(factory.androidOsBuildVersionType)) {
         continue;
       }
       for (ProguardMemberRule memberRule : rule.getMemberRules()) {
@@ -149,6 +153,9 @@
         if (memberRule.getRuleType() != ProguardMemberType.FIELD) {
           continue;
         }
+        if (!memberRule.getAnnotations().isEmpty()) {
+          continue;
+        }
         if (memberRule.getAccessFlags().isProtected()
             || memberRule.getAccessFlags().isPrivate()
             || memberRule.getAccessFlags().isAbstract()
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardMemberRuleReturnValue.java b/src/main/java/com/android/tools/r8/shaking/ProguardMemberRuleReturnValue.java
index c8545c7..11b8868 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardMemberRuleReturnValue.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardMemberRuleReturnValue.java
@@ -60,6 +60,10 @@
     return type == Type.FIELD;
   }
 
+  public boolean isNonNull() {
+    return isValueRange() && getValueRange().getMin() > 0;
+  }
+
   public boolean isNull() {
     return type == Type.NULL;
   }
diff --git a/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java b/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java
index 7f18fff..33bfc85 100644
--- a/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java
+++ b/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java
@@ -1727,11 +1727,7 @@
   }
 
   private void generateAssumeNoSideEffectsWarnings() {
-    ProguardClassFilter dontWarnPatterns =
-        options.getProguardConfiguration() != null
-            ? options.getProguardConfiguration().getDontWarnPatterns()
-            : ProguardClassFilter.empty();
-    if (dontWarnPatterns.matches(options.itemFactory.objectType)) {
+    if (appView.getDontWarnConfiguration().matches(options.itemFactory.objectType)) {
       // Don't report any warnings since we don't apply -assumenosideeffects rules to notify() or
       // wait() anyway.
       return;
diff --git a/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java b/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
index e049766..c52fd0f 100644
--- a/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
+++ b/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
@@ -294,7 +294,7 @@
     }
 
     // The set of targets that must remain for proper resolution error cases should not be merged.
-    for (DexMethod method : appInfo.getFailedResolutionTargets()) {
+    for (DexMethod method : appInfo.getFailedMethodResolutionTargets()) {
       markTypeAsPinned(method.holder, AbortReason.RESOLUTION_FOR_METHODS_MAY_CHANGE);
     }
   }
diff --git a/src/main/java/com/android/tools/r8/synthesis/CommittedItems.java b/src/main/java/com/android/tools/r8/synthesis/CommittedItems.java
index 424fd8d..2681a90 100644
--- a/src/main/java/com/android/tools/r8/synthesis/CommittedItems.java
+++ b/src/main/java/com/android/tools/r8/synthesis/CommittedItems.java
@@ -7,8 +7,6 @@
 import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexType;
 import com.google.common.collect.ImmutableList;
-import com.google.common.collect.ImmutableMap;
-import com.google.common.collect.ImmutableSet;
 import java.util.Collection;
 import java.util.function.Function;
 
@@ -28,23 +26,19 @@
   // Immutable package accessible fields to allow SyntheticItems creation.
   final DexApplication application;
   final int nextSyntheticId;
-  final ImmutableSet<DexType> legacySyntheticTypes;
-  final ImmutableMap<DexType, SyntheticReference> syntheticItems;
-  final ImmutableList<DexType> committedTypes;
+  final CommittedSyntheticsCollection committed;
+  final ImmutableList<DexType> committedProgramTypes;
 
   CommittedItems(
       int nextSyntheticId,
       DexApplication application,
-      ImmutableSet<DexType> legacySyntheticTypes,
-      ImmutableMap<DexType, SyntheticReference> syntheticItems,
-      ImmutableList<DexType> committedTypes) {
-    assert verifyTypesAreInApp(application, legacySyntheticTypes);
-    assert verifyTypesAreInApp(application, syntheticItems.keySet());
+      CommittedSyntheticsCollection committed,
+      ImmutableList<DexType> committedProgramTypes) {
     this.nextSyntheticId = nextSyntheticId;
     this.application = application;
-    this.legacySyntheticTypes = legacySyntheticTypes;
-    this.syntheticItems = syntheticItems;
-    this.committedTypes = committedTypes;
+    this.committed = committed;
+    this.committedProgramTypes = committedProgramTypes;
+    committed.verifyTypesAreInApp(application);
   }
 
   // Conversion to a mutable synthetic items collection. Should only be used in AppInfo creation.
@@ -56,13 +50,13 @@
     return application;
   }
 
-  public Collection<DexType> getCommittedTypes() {
-    return committedTypes;
+  public Collection<DexType> getCommittedProgramTypes() {
+    return committedProgramTypes;
   }
 
   @Deprecated
   public Collection<DexType> getLegacySyntheticTypes() {
-    return legacySyntheticTypes;
+    return committed.getLegacyTypes();
   }
 
   @Override
@@ -70,11 +64,4 @@
     // All synthetic types are committed to the application so lookup is just the base lookup.
     return baseDefinitionFor.apply(type);
   }
-
-  private static boolean verifyTypesAreInApp(DexApplication app, Collection<DexType> types) {
-    for (DexType type : types) {
-      assert app.programDefinitionFor(type) != null : "Missing synthetic: " + type;
-    }
-    return true;
-  }
 }
diff --git a/src/main/java/com/android/tools/r8/synthesis/CommittedSyntheticsCollection.java b/src/main/java/com/android/tools/r8/synthesis/CommittedSyntheticsCollection.java
new file mode 100644
index 0000000..053b20b
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/synthesis/CommittedSyntheticsCollection.java
@@ -0,0 +1,246 @@
+// 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.synthesis;
+
+import com.android.tools.r8.graph.DexApplication;
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.GraphLens.NonIdentityGraphLens;
+import com.android.tools.r8.graph.PrunedItems;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Sets;
+import java.util.Collection;
+import java.util.Map;
+import java.util.Set;
+import java.util.function.Consumer;
+
+/**
+ * Immutable collection of committed items.
+ *
+ * <p>This structure is to make it easier to pass the items from SyntheticItems to CommittedItems
+ * and back while also providing a builder for updating the committed synthetics.
+ */
+class CommittedSyntheticsCollection {
+
+  static class Builder {
+    private final CommittedSyntheticsCollection parent;
+    private ImmutableMap.Builder<DexType, SyntheticProgramClassReference> newNonLegacyClasses =
+        null;
+    private ImmutableMap.Builder<DexType, SyntheticMethodReference> newNonLegacyMethods = null;
+    private ImmutableSet.Builder<DexType> newLegacyClasses = null;
+
+    public Builder(CommittedSyntheticsCollection parent) {
+      this.parent = parent;
+    }
+
+    public Builder addItem(SyntheticDefinition<?, ?, ?> definition) {
+      if (definition.isProgramDefinition()) {
+        definition.asProgramDefinition().apply(this::addNonLegacyMethod, this::addNonLegacyClass);
+      }
+      return this;
+    }
+
+    public Builder addNonLegacyClass(SyntheticProgramClassDefinition definition) {
+      return addNonLegacyClass(definition.toReference());
+    }
+
+    public Builder addNonLegacyClass(SyntheticProgramClassReference reference) {
+      if (newNonLegacyClasses == null) {
+        newNonLegacyClasses = ImmutableMap.builder();
+      }
+      newNonLegacyClasses.put(reference.getHolder(), reference);
+      return this;
+    }
+
+    public Builder addNonLegacyMethod(SyntheticMethodDefinition definition) {
+      return addNonLegacyMethod(definition.toReference());
+    }
+
+    public Builder addNonLegacyMethod(SyntheticMethodReference reference) {
+      if (newNonLegacyMethods == null) {
+        newNonLegacyMethods = ImmutableMap.builder();
+      }
+      newNonLegacyMethods.put(reference.getHolder(), reference);
+      return this;
+    }
+
+    public Builder addLegacyClasses(Collection<DexProgramClass> classes) {
+      if (newLegacyClasses == null) {
+        newLegacyClasses = ImmutableSet.builder();
+      }
+      classes.forEach(c -> newLegacyClasses.add(c.getType()));
+      return this;
+    }
+
+    public Builder addLegacyClass(DexType type) {
+      if (newLegacyClasses == null) {
+        newLegacyClasses = ImmutableSet.builder();
+      }
+      newLegacyClasses.add(type);
+      return this;
+    }
+
+    public CommittedSyntheticsCollection build() {
+      if (newNonLegacyClasses == null && newNonLegacyMethods == null && newLegacyClasses == null) {
+        return parent;
+      }
+      ImmutableMap<DexType, SyntheticProgramClassReference> allNonLegacyClasses =
+          newNonLegacyClasses == null
+              ? parent.nonLegacyClasses
+              : newNonLegacyClasses.putAll(parent.nonLegacyClasses).build();
+      ImmutableMap<DexType, SyntheticMethodReference> allNonLegacyMethods =
+          newNonLegacyMethods == null
+              ? parent.nonLegacyMethods
+              : newNonLegacyMethods.putAll(parent.nonLegacyMethods).build();
+      ImmutableSet<DexType> allLegacyClasses =
+          newLegacyClasses == null
+              ? parent.legacyTypes
+              : newLegacyClasses.addAll(parent.legacyTypes).build();
+      return new CommittedSyntheticsCollection(
+          allLegacyClasses, allNonLegacyMethods, allNonLegacyClasses);
+    }
+  }
+
+  private static final CommittedSyntheticsCollection EMPTY =
+      new CommittedSyntheticsCollection(ImmutableSet.of(), ImmutableMap.of(), ImmutableMap.of());
+
+  /**
+   * Immutable set of synthetic types in the application (eg, committed).
+   *
+   * <p>TODO(b/158159959): Remove legacy support.
+   */
+  private final ImmutableSet<DexType> legacyTypes;
+
+  /** Mapping from synthetic type to its synthetic method item description. */
+  private final ImmutableMap<DexType, SyntheticMethodReference> nonLegacyMethods;
+
+  /** Mapping from synthetic type to its synthetic class item description. */
+  private final ImmutableMap<DexType, SyntheticProgramClassReference> nonLegacyClasses;
+
+  public CommittedSyntheticsCollection(
+      ImmutableSet<DexType> legacyTypes,
+      ImmutableMap<DexType, SyntheticMethodReference> nonLegacyMethods,
+      ImmutableMap<DexType, SyntheticProgramClassReference> nonLegacyClasses) {
+    this.legacyTypes = legacyTypes;
+    this.nonLegacyMethods = nonLegacyMethods;
+    this.nonLegacyClasses = nonLegacyClasses;
+    assert legacyTypes.size() + nonLegacyMethods.size() + nonLegacyClasses.size()
+        == Sets.union(Sets.union(nonLegacyMethods.keySet(), nonLegacyClasses.keySet()), legacyTypes)
+            .size();
+  }
+
+  public static CommittedSyntheticsCollection empty() {
+    return EMPTY;
+  }
+
+  Builder builder() {
+    return new Builder(this);
+  }
+
+  boolean isEmpty() {
+    return legacyTypes.isEmpty() && nonLegacyMethods.isEmpty() && nonLegacyClasses.isEmpty();
+  }
+
+  boolean containsType(DexType type) {
+    return containsLegacyType(type) || containsNonLegacyType(type);
+  }
+
+  public boolean containsLegacyType(DexType type) {
+    return legacyTypes.contains(type);
+  }
+
+  public boolean containsNonLegacyType(DexType type) {
+    return nonLegacyMethods.containsKey(type) || nonLegacyClasses.containsKey(type);
+  }
+
+  public ImmutableSet<DexType> getLegacyTypes() {
+    return legacyTypes;
+  }
+
+  public ImmutableMap<DexType, SyntheticMethodReference> getNonLegacyMethods() {
+    return nonLegacyMethods;
+  }
+
+  public ImmutableMap<DexType, SyntheticProgramClassReference> getNonLegacyClasses() {
+    return nonLegacyClasses;
+  }
+
+  public SyntheticReference<?, ?, ?> getNonLegacyItem(DexType type) {
+    SyntheticMethodReference reference = nonLegacyMethods.get(type);
+    if (reference != null) {
+      return reference;
+    }
+    return nonLegacyClasses.get(type);
+  }
+
+  public void forEachNonLegacyItem(Consumer<SyntheticReference<?, ?, ?>> fn) {
+    nonLegacyMethods.forEach((t, r) -> fn.accept(r));
+    nonLegacyClasses.forEach((t, r) -> fn.accept(r));
+  }
+
+  CommittedSyntheticsCollection pruneItems(PrunedItems prunedItems) {
+    Set<DexType> removed = prunedItems.getNoLongerSyntheticItems();
+    if (removed.isEmpty()) {
+      return this;
+    }
+    Builder builder = CommittedSyntheticsCollection.empty().builder();
+    boolean changed = false;
+    for (DexType type : legacyTypes) {
+      if (removed.contains(type)) {
+        changed = true;
+      } else {
+        builder.addLegacyClass(type);
+      }
+    }
+    for (SyntheticMethodReference reference : nonLegacyMethods.values()) {
+      if (removed.contains(reference.getHolder())) {
+        changed = true;
+      } else {
+        builder.addNonLegacyMethod(reference);
+      }
+    }
+    for (SyntheticProgramClassReference reference : nonLegacyClasses.values()) {
+      if (removed.contains(reference.getHolder())) {
+        changed = true;
+      } else {
+        builder.addNonLegacyClass(reference);
+      }
+    }
+    return changed ? builder.build() : this;
+  }
+
+  CommittedSyntheticsCollection rewriteWithLens(NonIdentityGraphLens lens) {
+    return new CommittedSyntheticsCollection(
+        lens.rewriteTypes(legacyTypes),
+        rewriteItems(nonLegacyMethods, lens),
+        rewriteItems(nonLegacyClasses, lens));
+  }
+
+  private static <R extends SyntheticReference<R, ?, ?>> ImmutableMap<DexType, R> rewriteItems(
+      Map<DexType, R> items, NonIdentityGraphLens lens) {
+    ImmutableMap.Builder<DexType, R> rewrittenItems = ImmutableMap.builder();
+    for (R reference : items.values()) {
+      R rewritten = reference.rewrite(lens);
+      if (rewritten != null) {
+        rewrittenItems.put(rewritten.getHolder(), rewritten);
+      }
+    }
+    return rewrittenItems.build();
+  }
+
+  boolean verifyTypesAreInApp(DexApplication application) {
+    assert verifyTypesAreInApp(application, legacyTypes);
+    assert verifyTypesAreInApp(application, nonLegacyMethods.keySet());
+    assert verifyTypesAreInApp(application, nonLegacyClasses.keySet());
+    return true;
+  }
+
+  private static boolean verifyTypesAreInApp(DexApplication app, Collection<DexType> types) {
+    for (DexType type : types) {
+      assert app.programDefinitionFor(type) != null : "Missing synthetic: " + type;
+    }
+    return true;
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/synthesis/SynthesizingContext.java b/src/main/java/com/android/tools/r8/synthesis/SynthesizingContext.java
index f5d4b66..932359d 100644
--- a/src/main/java/com/android/tools/r8/synthesis/SynthesizingContext.java
+++ b/src/main/java/com/android/tools/r8/synthesis/SynthesizingContext.java
@@ -7,6 +7,7 @@
 import static com.android.tools.r8.utils.DescriptorUtils.getDescriptorFromClassBinaryName;
 
 import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexType;
@@ -14,6 +15,7 @@
 import com.android.tools.r8.graph.ProgramDefinition;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.shaking.MainDexClasses;
+import com.android.tools.r8.synthesis.SyntheticNaming.Phase;
 import java.util.Comparator;
 import java.util.Set;
 
@@ -38,6 +40,13 @@
   private final DexType inputContextType;
   private final Origin inputContextOrigin;
 
+  static SynthesizingContext fromNonSyntheticInputContext(DexClass context) {
+    // A context that is itself non-synthetic is the single context, thus both the input context
+    // and synthesizing context coincide.
+    return new SynthesizingContext(
+        context.getContextType(), context.getContextType(), context.getOrigin());
+  }
+
   static SynthesizingContext fromNonSyntheticInputContext(ProgramDefinition context) {
     // A context that is itself non-synthetic is the single context, thus both the input context
     // and synthesizing context coincide.
@@ -53,6 +62,20 @@
     return new SynthesizingContext(synthesizingContextType, clazz.type, clazz.origin);
   }
 
+  static SynthesizingContext fromSyntheticContextChange(
+      DexType syntheticType, SynthesizingContext oldContext, DexItemFactory factory) {
+    String descriptor = syntheticType.toDescriptorString();
+    int i = descriptor.indexOf(SyntheticNaming.getPhaseSeparator(Phase.INTERNAL));
+    if (i <= 0) {
+      assert false : "Unexpected synthetic without internal separator: " + syntheticType;
+      return null;
+    }
+    DexType newContext = factory.createType(descriptor.substring(0, i) + ";");
+    return newContext == oldContext.getSynthesizingContextType()
+        ? oldContext
+        : new SynthesizingContext(newContext, newContext, oldContext.inputContextOrigin);
+  }
+
   private SynthesizingContext(
       DexType synthesizingContextType, DexType inputContextType, Origin inputContextOrigin) {
     this.synthesizingContextType = synthesizingContextType;
@@ -75,14 +98,6 @@
     return inputContextOrigin;
   }
 
-  DexType createHygienicType(String syntheticId, DexItemFactory factory) {
-    // If the context is a synthetic input, then use its annotated context as the hygienic context.
-    String contextDesc = synthesizingContextType.toDescriptorString();
-    String prefix = contextDesc.substring(0, contextDesc.length() - 1);
-    String suffix = SyntheticItems.INTERNAL_SYNTHETIC_CLASS_SEPARATOR + syntheticId + ";";
-    return factory.createType(prefix + suffix);
-  }
-
   SynthesizingContext rewrite(NonIdentityGraphLens lens) {
     DexType rewrittenInputeContextType = lens.lookupType(inputContextType);
     DexType rewrittenSynthesizingContextType = lens.lookupType(synthesizingContextType);
@@ -127,15 +142,17 @@
   void addIfDerivedFromMainDexClass(
       DexProgramClass externalSyntheticClass,
       MainDexClasses mainDexClasses,
-      Set<DexType> allMainDexTypes,
-      Set<DexType> derivedMainDexTypesToIgnore) {
+      Set<DexType> allMainDexTypes) {
     // The input context type (not the annotated context) determines if the derived class is to be
     // in main dex.
     // TODO(b/168584485): Once resolved allMainDexTypes == mainDexClasses.
     if (allMainDexTypes.contains(inputContextType)) {
       mainDexClasses.add(externalSyntheticClass);
-      // Mark the type as to be ignored when computing main-dex placement for legacy types.
-      derivedMainDexTypesToIgnore.add(inputContextType);
     }
   }
+
+  @Override
+  public String toString() {
+    return "SynthesizingContext{" + getSynthesizingContextType() + "}";
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/synthesis/SyntheticClassBuilder.java b/src/main/java/com/android/tools/r8/synthesis/SyntheticClassBuilder.java
index 4f289c7..01e6c75 100644
--- a/src/main/java/com/android/tools/r8/synthesis/SyntheticClassBuilder.java
+++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticClassBuilder.java
@@ -1,16 +1,18 @@
 // 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.synthesis;
 
 import com.android.tools.r8.ProgramResource.Kind;
 import com.android.tools.r8.dex.Constants;
 import com.android.tools.r8.graph.ClassAccessFlags;
+import com.android.tools.r8.graph.ClassKind;
 import com.android.tools.r8.graph.DexAnnotationSet;
+import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexEncodedField;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexItemFactory;
-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.DexTypeList;
@@ -25,16 +27,20 @@
 import java.util.List;
 import java.util.function.Consumer;
 
-public class SyntheticClassBuilder {
+abstract class SyntheticClassBuilder<B extends SyntheticClassBuilder<B, C>, C extends DexClass> {
+
   private final DexItemFactory factory;
 
   private final DexType type;
   private final Origin origin;
 
+  private Kind originKind;
   private DexType superType;
   private DexTypeList interfaces = DexTypeList.empty();
-
-  private int nextMethodId = 0;
+  private List<DexEncodedField> staticFields = new ArrayList<>();
+  private List<DexEncodedField> instanceFields = new ArrayList<>();
+  private List<DexEncodedMethod> directMethods = new ArrayList<>();
+  private List<DexEncodedMethod> virtualMethods = new ArrayList<>();
   private List<SyntheticMethodBuilder> methods = new ArrayList<>();
 
   SyntheticClassBuilder(DexType type, SynthesizingContext context, DexItemFactory factory) {
@@ -44,6 +50,10 @@
     this.superType = factory.objectType;
   }
 
+  public abstract B self();
+
+  public abstract ClassKind<C> getClassKind();
+
   public DexItemFactory getFactory() {
     return factory;
   }
@@ -52,67 +62,92 @@
     return type;
   }
 
-  private String getNextMethodName() {
-    return SyntheticItems.INTERNAL_SYNTHETIC_METHOD_PREFIX + nextMethodId++;
+  public B setInterfaces(List<DexType> interfaces) {
+    this.interfaces =
+        interfaces.isEmpty()
+            ? DexTypeList.empty()
+            : new DexTypeList(interfaces.toArray(DexType.EMPTY_ARRAY));
+    return self();
   }
 
-  public SyntheticClassBuilder addMethod(Consumer<SyntheticMethodBuilder> fn) {
-    SyntheticMethodBuilder method = new SyntheticMethodBuilder(this, getNextMethodName());
+  public B setOriginKind(Kind originKind) {
+    this.originKind = originKind;
+    return self();
+  }
+
+  public B setStaticFields(List<DexEncodedField> fields) {
+    staticFields.clear();
+    staticFields.addAll(fields);
+    return self();
+  }
+
+  public B setInstanceFields(List<DexEncodedField> fields) {
+    instanceFields.clear();
+    instanceFields.addAll(fields);
+    return self();
+  }
+
+  public B setDirectMethods(Iterable<DexEncodedMethod> methods) {
+    directMethods.clear();
+    methods.forEach(directMethods::add);
+    return self();
+  }
+
+  public B setVirtualMethods(Iterable<DexEncodedMethod> methods) {
+    virtualMethods.clear();
+    methods.forEach(virtualMethods::add);
+    return self();
+  }
+
+  public B addMethod(Consumer<SyntheticMethodBuilder> fn) {
+    SyntheticMethodBuilder method = new SyntheticMethodBuilder(this);
     fn.accept(method);
     methods.add(method);
-    return this;
+    return self();
   }
 
-  DexProgramClass build() {
+  public C build() {
     ClassAccessFlags accessFlags =
-        ClassAccessFlags.fromSharedAccessFlags(Constants.ACC_PUBLIC | Constants.ACC_SYNTHETIC);
-    Kind originKind = null;
+        ClassAccessFlags.fromSharedAccessFlags(
+            Constants.ACC_FINAL | Constants.ACC_PUBLIC | Constants.ACC_SYNTHETIC);
     DexString sourceFile = null;
     NestHostClassAttribute nestHost = null;
     List<NestMemberClassAttribute> nestMembers = Collections.emptyList();
     EnclosingMethodAttribute enclosingMembers = null;
     List<InnerClassAttribute> innerClasses = Collections.emptyList();
-    DexEncodedField[] staticFields = DexEncodedField.EMPTY_ARRAY;
-    DexEncodedField[] instanceFields = DexEncodedField.EMPTY_ARRAY;
-    DexEncodedMethod[] directMethods = DexEncodedMethod.EMPTY_ARRAY;
-    DexEncodedMethod[] virtualMethods = DexEncodedMethod.EMPTY_ARRAY;
-    assert !methods.isEmpty();
-    List<DexEncodedMethod> directs = new ArrayList<>(methods.size());
-    List<DexEncodedMethod> virtuals = new ArrayList<>(methods.size());
     for (SyntheticMethodBuilder builder : methods) {
       DexEncodedMethod method = builder.build();
       if (method.isNonPrivateVirtualMethod()) {
-        virtuals.add(method);
+        virtualMethods.add(method);
       } else {
-        directs.add(method);
+        directMethods.add(method);
       }
     }
-    if (!directs.isEmpty()) {
-      directMethods = directs.toArray(new DexEncodedMethod[directs.size()]);
-    }
-    if (!virtuals.isEmpty()) {
-      virtualMethods = virtuals.toArray(new DexEncodedMethod[virtuals.size()]);
-    }
-    long checksum = 7 * (long) directs.hashCode() + 11 * (long) virtuals.hashCode();
-    return new DexProgramClass(
-        type,
-        originKind,
-        origin,
-        accessFlags,
-        superType,
-        interfaces,
-        sourceFile,
-        nestHost,
-        nestMembers,
-        enclosingMembers,
-        innerClasses,
-        ClassSignature.noSignature(),
-        DexAnnotationSet.empty(),
-        staticFields,
-        instanceFields,
-        directMethods,
-        virtualMethods,
-        factory.getSkipNameValidationForTesting(),
-        c -> checksum);
+    long checksum =
+        7 * (long) directMethods.hashCode()
+            + 11 * (long) virtualMethods.hashCode()
+            + 13 * (long) staticFields.hashCode()
+            + 17 * (long) instanceFields.hashCode();
+    return getClassKind()
+        .create(
+            type,
+            originKind,
+            origin,
+            accessFlags,
+            superType,
+            interfaces,
+            sourceFile,
+            nestHost,
+            nestMembers,
+            enclosingMembers,
+            innerClasses,
+            ClassSignature.noSignature(),
+            DexAnnotationSet.empty(),
+            staticFields.toArray(DexEncodedField.EMPTY_ARRAY),
+            instanceFields.toArray(DexEncodedField.EMPTY_ARRAY),
+            directMethods.toArray(DexEncodedMethod.EMPTY_ARRAY),
+            virtualMethods.toArray(DexEncodedMethod.EMPTY_ARRAY),
+            factory.getSkipNameValidationForTesting(),
+            c -> checksum);
   }
 }
diff --git a/src/main/java/com/android/tools/r8/synthesis/SyntheticClassDefinition.java b/src/main/java/com/android/tools/r8/synthesis/SyntheticClassDefinition.java
new file mode 100644
index 0000000..3a915bf
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticClassDefinition.java
@@ -0,0 +1,47 @@
+// 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.synthesis;
+
+import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.synthesis.SyntheticNaming.SyntheticKind;
+
+/**
+ * Definition of a synthetic class item.
+ *
+ * <p>This class is internal to the synthetic items collection, thus package-protected.
+ */
+public abstract class SyntheticClassDefinition<
+        R extends SyntheticClassReference<R, D, C>,
+        D extends SyntheticClassDefinition<R, D, C>,
+        C extends DexClass>
+    extends SyntheticDefinition<R, D, C> {
+
+  final C clazz;
+
+  SyntheticClassDefinition(SyntheticKind kind, SynthesizingContext context, C clazz) {
+    super(kind, context);
+    this.clazz = clazz;
+  }
+
+  @Override
+  public final C getHolder() {
+    return clazz;
+  }
+
+  @Override
+  public boolean isValid() {
+    return clazz.isPublic() && clazz.isFinal() && clazz.accessFlags.isSynthetic();
+  }
+
+  @Override
+  public String toString() {
+    return "SyntheticClass{ clazz = "
+        + clazz.type.toSourceString()
+        + ", kind = "
+        + getKind()
+        + ", context = "
+        + getContext()
+        + " }";
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/synthesis/SyntheticClassReference.java b/src/main/java/com/android/tools/r8/synthesis/SyntheticClassReference.java
new file mode 100644
index 0000000..25142ee
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticClassReference.java
@@ -0,0 +1,33 @@
+// 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.synthesis;
+
+import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.synthesis.SyntheticNaming.SyntheticKind;
+
+/**
+ * Reference to a synthetic class item.
+ *
+ * <p>This class is internal to the synthetic items collection, thus package-protected.
+ */
+abstract class SyntheticClassReference<
+        R extends SyntheticClassReference<R, D, C>,
+        D extends SyntheticClassDefinition<R, D, C>,
+        C extends DexClass>
+    extends SyntheticReference<R, D, C> {
+
+  final DexType type;
+
+  SyntheticClassReference(SyntheticKind kind, SynthesizingContext context, DexType type) {
+    super(kind, context);
+    this.type = type;
+  }
+
+  @Override
+  DexType getHolder() {
+    return type;
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/synthesis/SyntheticClasspathClassBuilder.java b/src/main/java/com/android/tools/r8/synthesis/SyntheticClasspathClassBuilder.java
new file mode 100644
index 0000000..20cd075
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticClasspathClassBuilder.java
@@ -0,0 +1,30 @@
+// 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.synthesis;
+
+import com.android.tools.r8.ProgramResource.Kind;
+import com.android.tools.r8.graph.ClassKind;
+import com.android.tools.r8.graph.DexClasspathClass;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexType;
+
+public class SyntheticClasspathClassBuilder
+    extends SyntheticClassBuilder<SyntheticClasspathClassBuilder, DexClasspathClass> {
+
+  SyntheticClasspathClassBuilder(
+      DexType type, SynthesizingContext context, DexItemFactory factory) {
+    super(type, context, factory);
+    setOriginKind(Kind.CF);
+  }
+
+  @Override
+  public ClassKind<DexClasspathClass> getClassKind() {
+    return ClassKind.CLASSPATH;
+  }
+
+  @Override
+  public SyntheticClasspathClassBuilder self() {
+    return this;
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/synthesis/SyntheticClasspathClassDefinition.java b/src/main/java/com/android/tools/r8/synthesis/SyntheticClasspathClassDefinition.java
new file mode 100644
index 0000000..a05d505
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticClasspathClassDefinition.java
@@ -0,0 +1,66 @@
+// 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.synthesis;
+
+import com.android.tools.r8.graph.DexClasspathClass;
+import com.android.tools.r8.synthesis.SyntheticNaming.SyntheticKind;
+import com.android.tools.r8.utils.structural.RepresentativeMap;
+import com.google.common.hash.Hasher;
+
+/**
+ * Definition of a synthetic classpath class.
+ *
+ * <p>This class is internal to the synthetic items collection, thus package-protected.
+ */
+class SyntheticClasspathClassDefinition
+    extends SyntheticClassDefinition<
+        SyntheticClasspathClassReference, SyntheticClasspathClassDefinition, DexClasspathClass>
+    implements SyntheticClasspathDefinition {
+
+  SyntheticClasspathClassDefinition(
+      SyntheticKind kind, SynthesizingContext context, DexClasspathClass clazz) {
+    super(kind, context, clazz);
+  }
+
+  @Override
+  public boolean isClasspathDefinition() {
+    return true;
+  }
+
+  @Override
+  public SyntheticClasspathDefinition asClasspathDefinition() {
+    return this;
+  }
+
+  @Override
+  SyntheticClasspathClassReference toReference() {
+    return new SyntheticClasspathClassReference(getKind(), getContext(), clazz.getType());
+  }
+
+  @Override
+  public boolean isValid() {
+    return clazz.isPublic() && clazz.isFinal() && clazz.accessFlags.isSynthetic();
+  }
+
+  @Override
+  void internalComputeHash(Hasher hasher, RepresentativeMap map) {
+    clazz.hashWithTypeEquivalence(hasher, map);
+  }
+
+  @Override
+  int internalCompareTo(SyntheticClasspathClassDefinition o, RepresentativeMap map) {
+    return clazz.compareWithTypeEquivalenceTo(o.clazz, map);
+  }
+
+  @Override
+  public String toString() {
+    return "SyntheticClasspathClass{ clazz = "
+        + clazz.type.toSourceString()
+        + ", kind = "
+        + getKind()
+        + ", context = "
+        + getContext()
+        + " }";
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/synthesis/SyntheticClasspathClassReference.java b/src/main/java/com/android/tools/r8/synthesis/SyntheticClasspathClassReference.java
new file mode 100644
index 0000000..5b9ba3c
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticClasspathClassReference.java
@@ -0,0 +1,42 @@
+// 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.synthesis;
+
+import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexClasspathClass;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.GraphLens.NonIdentityGraphLens;
+import com.android.tools.r8.synthesis.SyntheticNaming.SyntheticKind;
+import java.util.function.Function;
+
+/**
+ * Reference to a synthetic class item.
+ *
+ * <p>This class is internal to the synthetic items collection, thus package-protected.
+ */
+class SyntheticClasspathClassReference
+    extends SyntheticClassReference<
+        SyntheticClasspathClassReference, SyntheticClasspathClassDefinition, DexClasspathClass> {
+
+  SyntheticClasspathClassReference(SyntheticKind kind, SynthesizingContext context, DexType type) {
+    super(kind, context, type);
+  }
+
+  @Override
+  SyntheticClasspathClassDefinition lookupDefinition(Function<DexType, DexClass> definitions) {
+    DexClass clazz = definitions.apply(type);
+    if (clazz == null) {
+      return null;
+    }
+    assert clazz.isClasspathClass();
+    return new SyntheticClasspathClassDefinition(getKind(), getContext(), clazz.asClasspathClass());
+  }
+
+  @Override
+  SyntheticClasspathClassReference rewrite(NonIdentityGraphLens lens) {
+    assert type == lens.lookupType(type)
+        : "Unexpected classpath rewrite of type " + type.toSourceString();
+    return this;
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/synthesis/SyntheticClasspathDefinition.java b/src/main/java/com/android/tools/r8/synthesis/SyntheticClasspathDefinition.java
new file mode 100644
index 0000000..0904dff
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticClasspathDefinition.java
@@ -0,0 +1,12 @@
+// 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.synthesis;
+
+import com.android.tools.r8.graph.DexClasspathClass;
+
+public interface SyntheticClasspathDefinition {
+
+  DexClasspathClass getHolder();
+}
diff --git a/src/main/java/com/android/tools/r8/synthesis/SyntheticDefinition.java b/src/main/java/com/android/tools/r8/synthesis/SyntheticDefinition.java
index 01f8825..071e7b6 100644
--- a/src/main/java/com/android/tools/r8/synthesis/SyntheticDefinition.java
+++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticDefinition.java
@@ -3,31 +3,111 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.synthesis;
 
-import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.GraphLens;
+import com.android.tools.r8.synthesis.SyntheticNaming.SyntheticKind;
 import com.android.tools.r8.utils.structural.RepresentativeMap;
 import com.google.common.hash.HashCode;
+import com.google.common.hash.Hasher;
+import com.google.common.hash.Hashing;
 
 /**
  * Base type for the definition of a synthetic item.
  *
  * <p>This class is internal to the synthetic items collection, thus package-protected.
  */
-abstract class SyntheticDefinition {
+abstract class SyntheticDefinition<
+    R extends SyntheticReference<R, D, C>,
+    D extends SyntheticDefinition<R, D, C>,
+    C extends DexClass> {
+
+  private final SyntheticKind kind;
   private final SynthesizingContext context;
 
-  SyntheticDefinition(SynthesizingContext context) {
+  SyntheticDefinition(SyntheticKind kind, SynthesizingContext context) {
+    assert kind != null;
+    assert context != null;
+    this.kind = kind;
     this.context = context;
   }
 
-  abstract SyntheticReference toReference();
+  public boolean isClasspathDefinition() {
+    return false;
+  }
 
-  SynthesizingContext getContext() {
+  public SyntheticClasspathDefinition asClasspathDefinition() {
+    return null;
+  }
+
+  public boolean isProgramDefinition() {
+    return false;
+  }
+
+  public SyntheticProgramDefinition asProgramDefinition() {
+    return null;
+  }
+
+  abstract R toReference();
+
+  final SyntheticKind getKind() {
+    return kind;
+  }
+
+  final SynthesizingContext getContext() {
     return context;
   }
 
-  abstract DexProgramClass getHolder();
+  public abstract C getHolder();
 
-  abstract HashCode computeHash(RepresentativeMap map, boolean intermediate);
+  final HashCode computeHash(RepresentativeMap map, boolean intermediate) {
+    Hasher hasher = Hashing.murmur3_128().newHasher();
+    if (intermediate) {
+      // If in intermediate mode, include the context type as sharing is restricted to within a
+      // single context.
+      getContext().getSynthesizingContextType().hashWithTypeEquivalence(hasher, map);
+    }
+    internalComputeHash(hasher, map);
+    return hasher.hash();
+  }
 
-  abstract boolean isEquivalentTo(SyntheticDefinition other, boolean intermediate);
+  abstract void internalComputeHash(Hasher hasher, RepresentativeMap map);
+
+  final boolean isEquivalentTo(D other, boolean includeContext, GraphLens graphLens) {
+    return compareTo(other, includeContext, graphLens) == 0;
+  }
+
+  int compareTo(D other, boolean includeContext, GraphLens graphLens) {
+    if (includeContext) {
+      int order = getContext().compareTo(other.getContext());
+      if (order != 0) {
+        return order;
+      }
+    }
+    DexType thisType = getHolder().getType();
+    DexType otherType = other.getHolder().getType();
+    RepresentativeMap map = null;
+    // If the synthetics have been moved include the original types in the equivalence.
+    if (graphLens.isNonIdentityLens()) {
+      DexType thisOrigType = graphLens.getOriginalType(thisType);
+      DexType otherOrigType = graphLens.getOriginalType(otherType);
+      if (thisType != thisOrigType || otherType != otherOrigType) {
+        map =
+            t -> {
+              if (t == otherType || t == thisOrigType || t == otherOrigType) {
+                return thisType;
+              }
+              return t;
+            };
+      }
+    }
+    if (map == null) {
+      map = t -> t == otherType ? thisType : t;
+    }
+    return internalCompareTo(other, map);
+  }
+
+  abstract int internalCompareTo(D other, RepresentativeMap map);
+
+  public abstract boolean isValid();
 }
diff --git a/src/main/java/com/android/tools/r8/synthesis/SyntheticFinalization.java b/src/main/java/com/android/tools/r8/synthesis/SyntheticFinalization.java
index c18b29d..a802761 100644
--- a/src/main/java/com/android/tools/r8/synthesis/SyntheticFinalization.java
+++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticFinalization.java
@@ -6,22 +6,32 @@
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexAnnotation;
 import com.android.tools.r8.graph.DexApplication;
+import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.GraphLens;
-import com.android.tools.r8.graph.GraphLens.Builder;
 import com.android.tools.r8.graph.GraphLens.NestedGraphLens;
+import com.android.tools.r8.graph.GraphLens.NonIdentityGraphLens;
 import com.android.tools.r8.graph.PrunedItems;
+import com.android.tools.r8.graph.TreeFixerBase;
+import com.android.tools.r8.ir.code.NumberGenerator;
 import com.android.tools.r8.shaking.MainDexClasses;
-import com.android.tools.r8.utils.DescriptorUtils;
+import com.android.tools.r8.synthesis.SyntheticNaming.SyntheticKind;
 import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.SetUtils;
+import com.android.tools.r8.utils.collections.BidirectionalManyToManyRepresentativeMap;
+import com.android.tools.r8.utils.collections.BidirectionalManyToOneRepresentativeHashMap;
+import com.android.tools.r8.utils.collections.BidirectionalManyToOneRepresentativeMap;
+import com.android.tools.r8.utils.collections.BidirectionalOneToOneHashMap;
 import com.android.tools.r8.utils.structural.RepresentativeMap;
 import com.google.common.collect.ArrayListMultimap;
+import com.google.common.collect.ImmutableCollection;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
-import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.ListMultimap;
 import com.google.common.collect.Sets;
 import com.google.common.hash.HashCode;
@@ -32,37 +42,147 @@
 import java.util.IdentityHashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.Map.Entry;
 import java.util.Set;
 import java.util.TreeSet;
-import java.util.function.Predicate;
+import java.util.function.BiConsumer;
+import java.util.function.Function;
 
 public class SyntheticFinalization {
 
   public static class Result {
     public final CommittedItems commit;
+    public final NonIdentityGraphLens lens;
     public final PrunedItems prunedItems;
 
-    public Result(CommittedItems commit, PrunedItems prunedItems) {
+    public Result(
+        CommittedItems commit, SyntheticFinalizationGraphLens lens, PrunedItems prunedItems) {
       this.commit = commit;
+      this.lens = lens;
       this.prunedItems = prunedItems;
     }
   }
 
-  private static class EquivalenceGroup<T extends SyntheticDefinition & Comparable<T>>
-      implements Comparable<EquivalenceGroup<T>> {
-    private List<T> members;
+  public static class SyntheticFinalizationGraphLens extends NestedGraphLens {
 
-    EquivalenceGroup(T singleton) {
-      this(singleton, Collections.singletonList(singleton));
+    private final Map<DexType, DexType> syntheticTypeMap;
+    private final Map<DexMethod, DexMethod> syntheticMethodsMap;
+
+    private SyntheticFinalizationGraphLens(
+        GraphLens previous,
+        Map<DexType, DexType> syntheticClassesMap,
+        Map<DexMethod, DexMethod> syntheticMethodsMap,
+        Map<DexType, DexType> typeMap,
+        BidirectionalManyToOneRepresentativeMap<DexField, DexField> fieldMap,
+        Map<DexMethod, DexMethod> methodMap,
+        BidirectionalManyToManyRepresentativeMap<DexMethod, DexMethod> originalMethodSignatures,
+        DexItemFactory factory) {
+      super(typeMap, methodMap, fieldMap, originalMethodSignatures, previous, factory);
+      this.syntheticTypeMap = syntheticClassesMap;
+      this.syntheticMethodsMap = syntheticMethodsMap;
     }
 
-    EquivalenceGroup(T representative, List<T> members) {
+    // The mapping is many to one, so the inverse is only defined up to equivalence groups.
+    // Override the access to renamed signatures to first check for synthetic mappings before
+    // using the original item mappings of the
+
+    @Override
+    public DexField getRenamedFieldSignature(DexField originalField) {
+      if (syntheticTypeMap.containsKey(originalField.holder)) {
+        DexField renamed = fieldMap.get(originalField);
+        if (renamed != null) {
+          return renamed;
+        }
+      }
+      return super.getRenamedFieldSignature(originalField);
+    }
+
+    @Override
+    public DexMethod getRenamedMethodSignature(DexMethod originalMethod, GraphLens applied) {
+      if (syntheticTypeMap.containsKey(originalMethod.holder)) {
+        DexMethod renamed = methodMap.get(originalMethod);
+        if (renamed != null) {
+          return renamed;
+        }
+      }
+      DexMethod renamed = syntheticMethodsMap.get(originalMethod);
+      return renamed != null ? renamed : super.getRenamedMethodSignature(originalMethod, applied);
+    }
+  }
+
+  private static class Builder {
+
+    // Forward mapping of internal to external synthetics.
+    Map<DexType, DexType> syntheticClassesMap = new IdentityHashMap<>();
+    Map<DexMethod, DexMethod> syntheticMethodsMap = new IdentityHashMap<>();
+
+    Map<DexType, DexType> typeMap = new IdentityHashMap<>();
+    BidirectionalManyToOneRepresentativeHashMap<DexField, DexField> fieldMap =
+        new BidirectionalManyToOneRepresentativeHashMap<>();
+    Map<DexMethod, DexMethod> methodMap = new IdentityHashMap<>();
+
+    protected final BidirectionalOneToOneHashMap<DexMethod, DexMethod> originalMethodSignatures =
+        new BidirectionalOneToOneHashMap<>();
+
+    void moveSyntheticClass(DexType from, DexType to) {
+      assert !syntheticClassesMap.containsKey(from);
+      syntheticClassesMap.put(from, to);
+      typeMap.put(from, to);
+    }
+
+    void moveSyntheticMethod(DexMethod from, DexMethod to) {
+      assert !syntheticMethodsMap.containsKey(from);
+      syntheticMethodsMap.put(from, to);
+      methodMap.put(from, to);
+    }
+
+    void move(DexType from, DexType to) {
+      typeMap.put(from, to);
+    }
+
+    void move(DexField from, DexField to) {
+      fieldMap.put(from, to);
+    }
+
+    void move(DexMethod from, DexMethod to) {
+      methodMap.put(from, to);
+      originalMethodSignatures.put(to, from);
+    }
+
+    SyntheticFinalizationGraphLens build(GraphLens previous, DexItemFactory factory) {
+      assert verifySubMap(syntheticClassesMap, typeMap);
+      if (typeMap.isEmpty() && fieldMap.isEmpty() && methodMap.isEmpty()) {
+        return null;
+      }
+      return new SyntheticFinalizationGraphLens(
+          previous,
+          syntheticClassesMap,
+          syntheticMethodsMap,
+          typeMap,
+          fieldMap,
+          methodMap,
+          originalMethodSignatures,
+          factory);
+    }
+
+    private static <K, V> boolean verifySubMap(Map<K, V> sub, Map<K, V> sup) {
+      for (Entry<K, V> entry : sub.entrySet()) {
+        assert sup.get(entry.getKey()) == entry.getValue();
+      }
+      return true;
+    }
+  }
+
+  public static class EquivalenceGroup<T extends SyntheticDefinition<?, T, ?>> {
+    private final List<T> members;
+
+    public EquivalenceGroup(T representative, List<T> members) {
       assert !members.isEmpty();
       assert members.get(0) == representative;
       this.members = members;
     }
 
-    T getRepresentative() {
+    public T getRepresentative() {
       return members.get(0);
     }
 
@@ -70,89 +190,115 @@
       return members;
     }
 
+    public int compareToIncludingContext(EquivalenceGroup<T> other, GraphLens graphLens) {
+      return getRepresentative().compareTo(other.getRepresentative(), true, graphLens);
+    }
+
     @Override
-    public int compareTo(EquivalenceGroup<T> other) {
-      return getRepresentative().compareTo(other.getRepresentative());
+    public String toString() {
+      return "EquivalenceGroup{ members = "
+          + members.size()
+          + ", repr = "
+          + getRepresentative()
+          + " }";
     }
   }
 
   private final InternalOptions options;
-  private final ImmutableSet<DexType> legacySyntheticTypes;
-  private final ImmutableMap<DexType, SyntheticReference> syntheticItems;
+  private final CommittedSyntheticsCollection synthetics;
 
-  SyntheticFinalization(
-      InternalOptions options,
-      ImmutableSet<DexType> legacySyntheticTypes,
-      ImmutableMap<DexType, SyntheticReference> syntheticItems) {
+  SyntheticFinalization(InternalOptions options, CommittedSyntheticsCollection synthetics) {
     this.options = options;
-    this.legacySyntheticTypes = legacySyntheticTypes;
-    this.syntheticItems = syntheticItems;
+    this.synthetics = synthetics;
   }
 
   public Result computeFinalSynthetics(AppView<?> appView) {
     assert verifyNoNestedSynthetics();
-    DexApplication application = appView.appInfo().app();
+    DexApplication application;
     MainDexClasses mainDexClasses = appView.appInfo().getMainDexClasses();
-    GraphLens graphLens = appView.graphLens();
-
-    Map<DexType, SyntheticMethodDefinition> methodDefinitions =
-        lookupSyntheticMethodDefinitions(application);
-
-    Collection<List<SyntheticMethodDefinition>> potentialEquivalences =
-        computePotentialEquivalences(methodDefinitions, options.intermediate);
-
-    Map<DexType, EquivalenceGroup<SyntheticMethodDefinition>> equivalences =
-        computeActualEquivalences(potentialEquivalences, options.intermediate, options.itemFactory);
-
-    Builder lensBuilder = NestedGraphLens.builder();
-    List<DexProgramClass> newProgramClasses = new ArrayList<>();
-    List<DexProgramClass> finalSyntheticClasses = new ArrayList<>();
-    Set<DexType> derivedMainDexTypesToIgnore = Sets.newIdentityHashSet();
-    buildLensAndProgram(
-        appView,
-        equivalences,
-        syntheticItems::containsKey,
-        mainDexClasses,
-        lensBuilder,
-        newProgramClasses,
-        finalSyntheticClasses,
-        derivedMainDexTypesToIgnore);
-
-    newProgramClasses.addAll(finalSyntheticClasses);
+    Builder lensBuilder = new Builder();
+    ImmutableMap.Builder<DexType, SyntheticMethodReference> finalMethodsBuilder =
+        ImmutableMap.builder();
+    ImmutableMap.Builder<DexType, SyntheticProgramClassReference> finalClassesBuilder =
+        ImmutableMap.builder();
+    List<DexProgramClass> finalSyntheticProgramDefinitions = new ArrayList<>();
+    {
+      Map<DexType, NumberGenerator> generators = new IdentityHashMap<>();
+      application =
+          buildLensAndProgram(
+              appView,
+              computeEquivalences(appView, synthetics.getNonLegacyMethods().values(), generators),
+              computeEquivalences(appView, synthetics.getNonLegacyClasses().values(), generators),
+              mainDexClasses,
+              lensBuilder,
+              (clazz, reference) -> {
+                finalSyntheticProgramDefinitions.add(clazz);
+                finalClassesBuilder.put(clazz.getType(), reference);
+              },
+              (clazz, reference) -> {
+                finalSyntheticProgramDefinitions.add(clazz);
+                finalMethodsBuilder.put(clazz.getType(), reference);
+              });
+    }
+    ImmutableMap<DexType, SyntheticMethodReference> finalMethods = finalMethodsBuilder.build();
+    ImmutableMap<DexType, SyntheticProgramClassReference> finalClasses =
+        finalClassesBuilder.build();
 
     handleSynthesizedClassMapping(
-        finalSyntheticClasses, application, options, mainDexClasses, derivedMainDexTypesToIgnore);
+        finalSyntheticProgramDefinitions,
+        application,
+        options,
+        mainDexClasses,
+        lensBuilder.typeMap);
 
-    DexApplication app = application.builder().replaceProgramClasses(newProgramClasses).build();
-
-    appView.setGraphLens(lensBuilder.build(options.itemFactory, graphLens));
     assert appView.appInfo().getMainDexClasses() == mainDexClasses;
 
-    Set<DexType> finalSyntheticTypes = Sets.newIdentityHashSet();
-    finalSyntheticClasses.forEach(clazz -> finalSyntheticTypes.add(clazz.getType()));
-
     Set<DexType> prunedSynthetics = Sets.newIdentityHashSet();
-    for (DexType type : syntheticItems.keySet()) {
-      if (!finalSyntheticTypes.contains(type)) {
-        prunedSynthetics.add(type);
-      }
-    }
+    synthetics.forEachNonLegacyItem(
+        reference -> {
+          DexType type = reference.getHolder();
+          if (!finalMethods.containsKey(type) && !finalClasses.containsKey(type)) {
+            prunedSynthetics.add(type);
+          }
+        });
 
     return new Result(
         new CommittedItems(
             SyntheticItems.INVALID_ID_AFTER_SYNTHETIC_FINALIZATION,
-            app,
-            legacySyntheticTypes,
-            ImmutableMap.of(),
+            application,
+            new CommittedSyntheticsCollection(
+                synthetics.getLegacyTypes(), finalMethods, finalClasses),
             ImmutableList.of()),
-        PrunedItems.builder().setPrunedApp(app).addRemovedClasses(prunedSynthetics).build());
+        lensBuilder.build(appView.graphLens(), appView.dexItemFactory()),
+        PrunedItems.builder()
+            .setPrunedApp(application)
+            .addRemovedClasses(prunedSynthetics)
+            .build());
+  }
+
+  private <R extends SyntheticReference<R, D, ?>, D extends SyntheticDefinition<R, D, ?>>
+      Map<DexType, EquivalenceGroup<D>> computeEquivalences(
+          AppView<?> appView,
+          ImmutableCollection<R> references,
+          Map<DexType, NumberGenerator> generators) {
+    boolean intermediate = appView.options().intermediate;
+    Map<DexType, D> definitions = lookupDefinitions(appView, references);
+    Collection<List<D>> potentialEquivalences =
+        computePotentialEquivalences(
+            definitions, intermediate, appView.dexItemFactory(), appView.graphLens());
+    return computeActualEquivalences(potentialEquivalences, generators, appView, intermediate);
+  }
+
+  private boolean isNotSyntheticType(DexType type) {
+    return !synthetics.containsNonLegacyType(type);
   }
 
   private boolean verifyNoNestedSynthetics() {
     // Check that a context is never itself synthetic class.
-    for (SyntheticReference item : syntheticItems.values()) {
-      assert !syntheticItems.containsKey(item.getContext().getSynthesizingContextType());
-    }
+    synthetics.forEachNonLegacyItem(
+        item -> {
+          assert isNotSyntheticType(item.getContext().getSynthesizingContextType());
+        });
     return true;
   }
 
@@ -161,7 +307,7 @@
       DexApplication application,
       InternalOptions options,
       MainDexClasses mainDexClasses,
-      Set<DexType> derivedMainDexTypesToIgnore) {
+      Map<DexType, DexType> derivedMainDexTypesToIgnore) {
     boolean includeSynthesizedClassMappingInOutput = shouldAnnotateSynthetics(options);
     if (includeSynthesizedClassMappingInOutput) {
       updateSynthesizedClassMapping(application, finalSyntheticClasses);
@@ -177,7 +323,7 @@
       DexApplication application, List<DexProgramClass> finalSyntheticClasses) {
     ListMultimap<DexProgramClass, DexProgramClass> originalToSynthesized =
         ArrayListMultimap.create();
-    for (DexType type : legacySyntheticTypes) {
+    for (DexType type : synthetics.getLegacyTypes()) {
       DexProgramClass clazz = DexProgramClass.asProgramClassOrNull(application.definitionFor(type));
       if (clazz != null) {
         for (DexProgramClass origin : clazz.getSynthesizedFrom()) {
@@ -214,7 +360,7 @@
   private void updateMainDexListWithSynthesizedClassMap(
       DexApplication application,
       MainDexClasses mainDexClasses,
-      Set<DexType> derivedMainDexTypesToIgnore) {
+      Map<DexType, DexType> derivedMainDexTypesToIgnore) {
     if (mainDexClasses.isEmpty()) {
       return;
     }
@@ -228,12 +374,11 @@
                 DexAnnotation.readAnnotationSynthesizedClassMap(
                     programClass, application.dexItemFactory);
             for (DexType type : derived) {
-              if (!derivedMainDexTypesToIgnore.contains(type)) {
-                DexProgramClass syntheticClass =
-                    DexProgramClass.asProgramClassOrNull(application.definitionFor(type));
-                if (syntheticClass != null) {
-                  newMainDexClasses.add(syntheticClass);
-                }
+              DexType mappedType = derivedMainDexTypesToIgnore.getOrDefault(type, type);
+              DexProgramClass syntheticClass =
+                  DexProgramClass.asProgramClassOrNull(application.definitionFor(mappedType));
+              if (syntheticClass != null) {
+                newMainDexClasses.add(syntheticClass);
               }
             }
           }
@@ -248,23 +393,17 @@
     }
   }
 
-  private static void buildLensAndProgram(
+  private static DexApplication buildLensAndProgram(
       AppView<?> appView,
       Map<DexType, EquivalenceGroup<SyntheticMethodDefinition>> syntheticMethodGroups,
-      Predicate<DexType> isSyntheticType,
+      Map<DexType, EquivalenceGroup<SyntheticProgramClassDefinition>> syntheticClassGroups,
       MainDexClasses mainDexClasses,
       Builder lensBuilder,
-      List<DexProgramClass> normalClasses,
-      List<DexProgramClass> newSyntheticClasses,
-      Set<DexType> derivedMainDexTypesToIgnore) {
+      BiConsumer<DexProgramClass, SyntheticProgramClassReference> addFinalSyntheticClass,
+      BiConsumer<DexProgramClass, SyntheticMethodReference> addFinalSyntheticMethod) {
+    DexApplication application = appView.appInfo().app();
     DexItemFactory factory = appView.dexItemFactory();
-
-    for (DexProgramClass clazz : appView.appInfo().classes()) {
-      if (!isSyntheticType.test(clazz.type)) {
-        assert SyntheticItems.verifyNotInternalSynthetic(clazz.type);
-        normalClasses.add(clazz);
-      }
-    }
+    List<DexProgramClass> newProgramClasses = new ArrayList<>();
 
     // TODO(b/168584485): Remove this once class-mapping support is removed.
     Set<DexType> derivedMainDexTypes = Sets.newIdentityHashSet();
@@ -280,60 +419,177 @@
           }
         });
 
+    Set<DexType> pruned = Sets.newIdentityHashSet();
     syntheticMethodGroups.forEach(
         (syntheticType, syntheticGroup) -> {
           SyntheticMethodDefinition representative = syntheticGroup.getRepresentative();
           SynthesizingContext context = representative.getContext();
           context.registerPrefixRewriting(syntheticType, appView);
-          SyntheticClassBuilder builder =
-              new SyntheticClassBuilder(syntheticType, context, factory);
-          // TODO(b/158159959): Support grouping multiple methods per synthetic class.
-          builder.addMethod(
-              methodBuilder -> {
-                DexEncodedMethod definition = representative.getMethod().getDefinition();
-                methodBuilder
-                    .setAccessFlags(definition.accessFlags)
-                    .setProto(definition.getProto())
-                    .setClassFileVersion(
-                        definition.hasClassFileVersion() ? definition.getClassFileVersion() : null)
-                    .setCode(m -> definition.getCode());
-              });
-          DexProgramClass externalSyntheticClass = builder.build();
-          if (shouldAnnotateSynthetics(appView.options())) {
-            externalSyntheticClass.setAnnotations(
-                externalSyntheticClass
-                    .annotations()
-                    .getWithAddedOrReplaced(
-                        DexAnnotation.createAnnotationSynthesizedClass(
-                            context.getSynthesizingContextType(), factory)));
-          }
+          DexProgramClass externalSyntheticClass =
+              createExternalMethodClass(syntheticType, representative, factory);
+          newProgramClasses.add(externalSyntheticClass);
+          addSyntheticMarker(representative.getKind(), externalSyntheticClass, context, appView);
           assert externalSyntheticClass.getMethodCollection().size() == 1;
           DexEncodedMethod externalSyntheticMethod =
               externalSyntheticClass.methods().iterator().next();
-          newSyntheticClasses.add(externalSyntheticClass);
           for (SyntheticMethodDefinition member : syntheticGroup.getMembers()) {
-            if (member.getMethod().getReference() != externalSyntheticMethod.method) {
-              lensBuilder.map(member.getMethod().getReference(), externalSyntheticMethod.method);
-            }
-            member
-                .getContext()
-                .addIfDerivedFromMainDexClass(
-                    externalSyntheticClass,
-                    mainDexClasses,
-                    derivedMainDexTypes,
-                    derivedMainDexTypesToIgnore);
-            // TODO(b/168584485): Remove this once class-mapping support is removed.
-            DexProgramClass from =
-                DexProgramClass.asProgramClassOrNull(
-                    appView
-                        .appInfo()
-                        .definitionForWithoutExistenceAssert(
-                            member.getContext().getSynthesizingContextType()));
-            if (from != null) {
-              externalSyntheticClass.addSynthesizedFrom(from);
+            DexMethod memberReference = member.getMethod().getReference();
+            pruned.add(member.getHolder().getType());
+            if (memberReference != externalSyntheticMethod.method) {
+              lensBuilder.moveSyntheticMethod(memberReference, externalSyntheticMethod.method);
             }
           }
         });
+
+    List<DexProgramClass> deduplicatedClasses = new ArrayList<>();
+    syntheticClassGroups.forEach(
+        (syntheticType, syntheticGroup) -> {
+          SyntheticProgramClassDefinition representative = syntheticGroup.getRepresentative();
+          SynthesizingContext context = representative.getContext();
+          context.registerPrefixRewriting(syntheticType, appView);
+          DexProgramClass externalSyntheticClass = representative.getHolder();
+          newProgramClasses.add(externalSyntheticClass);
+          addSyntheticMarker(representative.getKind(), externalSyntheticClass, context, appView);
+          for (SyntheticProgramClassDefinition member : syntheticGroup.getMembers()) {
+            DexProgramClass memberClass = member.getHolder();
+            DexType memberType = memberClass.getType();
+            pruned.add(memberType);
+            if (memberType != syntheticType) {
+              lensBuilder.moveSyntheticClass(memberType, syntheticType);
+            }
+            // The aliasing of the non-representative members needs to be recorded manually.
+            if (member != representative) {
+              deduplicatedClasses.add(memberClass);
+            }
+          }
+        });
+
+    for (DexProgramClass clazz : application.classes()) {
+      if (!pruned.contains(clazz.type)) {
+        newProgramClasses.add(clazz);
+      }
+    }
+    application = application.builder().replaceProgramClasses(newProgramClasses).build();
+
+    // We can only assert that the method container classes are in here as the classes need
+    // to be rewritten by the tree-fixer.
+    for (DexType key : syntheticMethodGroups.keySet()) {
+      assert application.definitionFor(key) != null;
+    }
+
+    DexApplication.Builder<?> builder = application.builder();
+    TreeFixerBase treeFixer =
+        new TreeFixerBase(appView) {
+          @Override
+          public DexType mapClassType(DexType type) {
+            return lensBuilder.syntheticClassesMap.getOrDefault(type, type);
+          }
+
+          @Override
+          public void recordFieldChange(DexField from, DexField to) {
+            lensBuilder.move(from, to);
+          }
+
+          @Override
+          public void recordMethodChange(DexMethod from, DexMethod to) {
+            lensBuilder.move(from, to);
+          }
+
+          @Override
+          public void recordClassChange(DexType from, DexType to) {
+            lensBuilder.move(from, to);
+          }
+        };
+    treeFixer.fixupClasses(deduplicatedClasses);
+    builder.replaceProgramClasses(treeFixer.fixupClasses(application.classes()));
+    application = builder.build();
+
+    // Add the synthesized from after repackaging which changed class definitions.
+    final DexApplication appForLookup = application;
+    syntheticClassGroups.forEach(
+        (syntheticType, syntheticGroup) -> {
+          DexProgramClass externalSyntheticClass = appForLookup.programDefinitionFor(syntheticType);
+          addFinalSyntheticClass.accept(
+              externalSyntheticClass, syntheticGroup.getRepresentative().toReference());
+          for (SyntheticProgramClassDefinition member : syntheticGroup.getMembers()) {
+            addMainDexAndSynthesizedFromForMember(
+                member,
+                externalSyntheticClass,
+                mainDexClasses,
+                derivedMainDexTypes,
+                appForLookup::programDefinitionFor);
+          }
+        });
+    syntheticMethodGroups.forEach(
+        (syntheticType, syntheticGroup) -> {
+          DexProgramClass externalSyntheticClass = appForLookup.programDefinitionFor(syntheticType);
+          addFinalSyntheticMethod.accept(
+              externalSyntheticClass, syntheticGroup.getRepresentative().toReference());
+          for (SyntheticMethodDefinition member : syntheticGroup.getMembers()) {
+            addMainDexAndSynthesizedFromForMember(
+                member,
+                externalSyntheticClass,
+                mainDexClasses,
+                derivedMainDexTypes,
+                appForLookup::programDefinitionFor);
+          }
+        });
+
+    for (DexType key : syntheticMethodGroups.keySet()) {
+      assert application.definitionFor(key) != null;
+    }
+
+    for (DexType key : syntheticClassGroups.keySet()) {
+      assert application.definitionFor(key) != null;
+    }
+
+    return application;
+  }
+
+  private static void addSyntheticMarker(
+      SyntheticKind kind,
+      DexProgramClass externalSyntheticClass,
+      SynthesizingContext context,
+      AppView<?> appView) {
+    if (shouldAnnotateSynthetics(appView.options())) {
+      SyntheticMarker.addMarkerToClass(
+          externalSyntheticClass, kind, context, appView.dexItemFactory());
+    }
+  }
+
+  private static DexProgramClass createExternalMethodClass(
+      DexType syntheticType, SyntheticMethodDefinition representative, DexItemFactory factory) {
+    SyntheticProgramClassBuilder builder =
+        new SyntheticProgramClassBuilder(syntheticType, representative.getContext(), factory);
+    // TODO(b/158159959): Support grouping multiple methods per synthetic class.
+    builder.addMethod(
+        methodBuilder -> {
+          DexEncodedMethod definition = representative.getMethod().getDefinition();
+          methodBuilder
+              .setName(SyntheticNaming.INTERNAL_SYNTHETIC_METHOD_PREFIX)
+              .setAccessFlags(definition.accessFlags)
+              .setProto(definition.getProto())
+              .setClassFileVersion(
+                  definition.hasClassFileVersion() ? definition.getClassFileVersion() : null)
+              .setCode(m -> definition.getCode());
+        });
+    return builder.build();
+  }
+
+  private static void addMainDexAndSynthesizedFromForMember(
+      SyntheticDefinition<?, ?, ?> member,
+      DexProgramClass externalSyntheticClass,
+      MainDexClasses mainDexClasses,
+      Set<DexType> derivedMainDexTypes,
+      Function<DexType, DexProgramClass> definitions) {
+    member
+        .getContext()
+        .addIfDerivedFromMainDexClass(externalSyntheticClass, mainDexClasses, derivedMainDexTypes);
+    // TODO(b/168584485): Remove this once class-mapping support is removed.
+    DexProgramClass from = definitions.apply(member.getContext().getSynthesizingContextType());
+    if (from != null) {
+      externalSyntheticClass.addSynthesizedFrom(from);
+    }
   }
 
   private static boolean shouldAnnotateSynthetics(InternalOptions options) {
@@ -345,15 +601,18 @@
     return options.intermediate && !options.cfToCfDesugar;
   }
 
-  private static <T extends SyntheticDefinition & Comparable<T>>
+  private <T extends SyntheticDefinition<?, T, ?>>
       Map<DexType, EquivalenceGroup<T>> computeActualEquivalences(
-          Collection<List<T>> potentialEquivalences, boolean intermediate, DexItemFactory factory) {
+          Collection<List<T>> potentialEquivalences,
+          Map<DexType, NumberGenerator> generators,
+          AppView<?> appView,
+          boolean intermediate) {
     Map<DexType, List<EquivalenceGroup<T>>> groupsPerContext = new IdentityHashMap<>();
     potentialEquivalences.forEach(
         members -> {
-          List<List<T>> groups = groupEquivalent(members, intermediate);
+          List<List<T>> groups = groupEquivalent(members, intermediate, appView.graphLens());
           for (List<T> group : groups) {
-            T representative = findDeterministicRepresentative(group);
+            T representative = findDeterministicRepresentative(group, appView.graphLens());
             // The representative is required to be the first element of the group.
             group.remove(representative);
             group.add(0, representative);
@@ -368,27 +627,31 @@
     Map<DexType, EquivalenceGroup<T>> equivalences = new IdentityHashMap<>();
     groupsPerContext.forEach(
         (context, groups) -> {
-          groups.sort(EquivalenceGroup::compareTo);
+          // Sort the equivalence groups that go into 'context' including the context type of the
+          // representative which is equal to 'context' here (see assert below).
+          groups.sort((a, b) -> a.compareToIncludingContext(b, appView.graphLens()));
           for (int i = 0; i < groups.size(); i++) {
             EquivalenceGroup<T> group = groups.get(i);
+            assert group.getRepresentative().getContext().getSynthesizingContextType() == context;
             // Two equivalence groups in same context type must be distinct otherwise the assignment
             // of the synthetic name will be non-deterministic between the two.
-            assert i == 0 || checkGroupsAreDistinct(groups.get(i - 1), group);
-            DexType representativeType = createExternalType(context, i, factory);
+            assert i == 0 || checkGroupsAreDistinct(groups.get(i - 1), group, appView.graphLens());
+            SyntheticKind kind = group.members.get(0).getKind();
+            DexType representativeType = createExternalType(kind, context, generators, appView);
             equivalences.put(representativeType, group);
           }
         });
     return equivalences;
   }
 
-  private static <T extends SyntheticDefinition & Comparable<T>> List<List<T>> groupEquivalent(
-      List<T> potentialEquivalence, boolean intermediate) {
+  private static <T extends SyntheticDefinition<?, T, ?>> List<List<T>> groupEquivalent(
+      List<T> potentialEquivalence, boolean intermediate, GraphLens graphLens) {
     List<List<T>> groups = new ArrayList<>();
     // Each other member is in a shared group if it is actually equivalent to the first member.
     for (T synthetic : potentialEquivalence) {
       boolean requireNewGroup = true;
       for (List<T> group : groups) {
-        if (synthetic.isEquivalentTo(group.get(0), intermediate)) {
+        if (synthetic.isEquivalentTo(group.get(0), intermediate, graphLens)) {
           requireNewGroup = false;
           group.add(synthetic);
           break;
@@ -403,42 +666,78 @@
     return groups;
   }
 
-  private static <T extends SyntheticDefinition & Comparable<T>> boolean checkGroupsAreDistinct(
-      EquivalenceGroup<T> g1, EquivalenceGroup<T> g2) {
-    assert g1.compareTo(g2) != 0;
+  private static <T extends SyntheticDefinition<?, T, ?>> boolean checkGroupsAreDistinct(
+      EquivalenceGroup<T> g1, EquivalenceGroup<T> g2, GraphLens graphLens) {
+    int order = g1.compareToIncludingContext(g2, graphLens);
+    assert order != 0;
+    assert order != g2.compareToIncludingContext(g1, graphLens);
     return true;
   }
 
-  private static <T extends SyntheticDefinition & Comparable<T>> T findDeterministicRepresentative(
-      List<T> members) {
+  private static <T extends SyntheticDefinition<?, T, ?>> T findDeterministicRepresentative(
+      List<T> members, GraphLens graphLens) {
     // Pick a deterministic member as representative.
     T smallest = members.get(0);
     for (int i = 1; i < members.size(); i++) {
       T next = members.get(i);
-      if (next.compareTo(smallest) < 0) {
+      if (next.compareTo(smallest, true, graphLens) < 0) {
         smallest = next;
       }
     }
     return smallest;
   }
 
-  private static DexType createExternalType(
-      DexType representativeContext, int nextContextId, DexItemFactory factory) {
-    return factory.createType(
-        DescriptorUtils.getDescriptorFromClassBinaryName(
-            representativeContext.getInternalName()
-                + SyntheticItems.EXTERNAL_SYNTHETIC_CLASS_SEPARATOR
-                + nextContextId));
+  private DexType createExternalType(
+      SyntheticKind kind,
+      DexType representativeContext,
+      Map<DexType, NumberGenerator> generators,
+      AppView<?> appView) {
+    NumberGenerator generator =
+        generators.computeIfAbsent(representativeContext, k -> new NumberGenerator());
+    DexType externalType;
+    do {
+      externalType =
+          SyntheticNaming.createExternalType(
+              kind,
+              representativeContext,
+              Integer.toString(generator.next()),
+              appView.dexItemFactory());
+      DexClass clazz = appView.appInfo().definitionForWithoutExistenceAssert(externalType);
+      if (clazz != null && isNotSyntheticType(clazz.type)) {
+        assert options.testing.allowConflictingSyntheticTypes
+            : "Unexpected creation of an existing external synthetic type: " + clazz;
+        externalType = null;
+      }
+    } while (externalType == null);
+    return externalType;
   }
 
-  private static <T extends SyntheticDefinition> Collection<List<T>> computePotentialEquivalences(
-      Map<DexType, T> definitions, boolean intermediate) {
+  private static <T extends SyntheticDefinition<?, T, ?>>
+      Collection<List<T>> computePotentialEquivalences(
+          Map<DexType, T> definitions,
+          boolean intermediate,
+          DexItemFactory factory,
+          GraphLens graphLens) {
     if (definitions.isEmpty()) {
       return Collections.emptyList();
     }
-    Set<DexType> allTypes = definitions.keySet();
-    DexType representative = allTypes.iterator().next();
-    RepresentativeMap map = t -> allTypes.contains(t) ? representative : t;
+    // Map all synthetic types to the java 'void' type. This is not an actual valid type, so it
+    // cannot collide with any valid java type providing a good hashing key for the synthetics.
+    Set<DexType> syntheticTypes;
+    if (graphLens.isIdentityLens()) {
+      syntheticTypes = definitions.keySet();
+    } else {
+      // If the synthetics are renamed include their original names in the equivalence too.
+      syntheticTypes = SetUtils.newIdentityHashSet(definitions.size() * 2);
+      definitions
+          .keySet()
+          .forEach(
+              t -> {
+                syntheticTypes.add(t);
+                syntheticTypes.add(graphLens.getOriginalType(t));
+              });
+    }
+    RepresentativeMap map = t -> syntheticTypes.contains(t) ? factory.voidType : t;
     Map<HashCode, List<T>> equivalences = new HashMap<>(definitions.size());
     for (T definition : definitions.values()) {
       HashCode hash = definition.computeHash(map, intermediate);
@@ -447,25 +746,24 @@
     return equivalences.values();
   }
 
-  private Map<DexType, SyntheticMethodDefinition> lookupSyntheticMethodDefinitions(
-      DexApplication finalApp) {
-    Map<DexType, SyntheticMethodDefinition> methods = new IdentityHashMap<>(syntheticItems.size());
-    for (SyntheticReference reference : syntheticItems.values()) {
-      SyntheticDefinition definition = reference.lookupDefinition(finalApp::definitionFor);
-      if (definition == null || !(definition instanceof SyntheticMethodDefinition)) {
+  private <R extends SyntheticReference<R, D, ?>, D extends SyntheticDefinition<R, D, ?>>
+      Map<DexType, D> lookupDefinitions(AppView<?> appView, Collection<R> references) {
+    Map<DexType, D> definitions = new IdentityHashMap<>(references.size());
+    for (R reference : references) {
+      D definition = reference.lookupDefinition(appView::definitionFor);
+      if (definition == null) {
         // We expect pruned definitions to have been removed.
         assert false;
         continue;
       }
-      SyntheticMethodDefinition method = (SyntheticMethodDefinition) definition;
-      if (SyntheticMethodBuilder.isValidSyntheticMethod(method.getMethod().getDefinition())) {
-        methods.put(method.getHolder().getType(), method);
+      if (definition.isValid()) {
+        definitions.put(reference.getHolder(), definition);
       } else {
         // Failing this check indicates that an optimization has modified the synthetic in a
         // disruptive way.
         assert false;
       }
     }
-    return methods;
+    return definitions;
   }
 }
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 bc7d42e..657db43 100644
--- a/src/main/java/com/android/tools/r8/synthesis/SyntheticItems.java
+++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticItems.java
@@ -6,12 +6,9 @@
 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.ClassAccessFlags;
-import com.android.tools.r8.graph.DexAnnotation;
-import com.android.tools.r8.graph.DexAnnotationSet;
 import com.android.tools.r8.graph.DexApplication;
 import com.android.tools.r8.graph.DexClass;
-import com.android.tools.r8.graph.DexEncodedMethod;
+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.DexType;
@@ -21,11 +18,8 @@
 import com.android.tools.r8.graph.PrunedItems;
 import com.android.tools.r8.ir.conversion.MethodProcessingId;
 import com.android.tools.r8.synthesis.SyntheticFinalization.Result;
+import com.android.tools.r8.synthesis.SyntheticNaming.SyntheticKind;
 import com.google.common.collect.ImmutableList;
-import com.google.common.collect.ImmutableMap;
-import com.google.common.collect.ImmutableSet;
-import com.google.common.collect.ImmutableSet.Builder;
-import com.google.common.collect.Sets;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
@@ -35,148 +29,114 @@
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.function.Consumer;
 import java.util.function.Function;
+import java.util.function.Predicate;
 import java.util.function.Supplier;
+import java.util.stream.Collectors;
 
 public class SyntheticItems implements SyntheticDefinitionsProvider {
 
   static final int INVALID_ID_AFTER_SYNTHETIC_FINALIZATION = -1;
 
-  /**
-   * The internal synthetic class separator is only used for representing synthetic items during
-   * compilation. In particular, this separator must never be used to write synthetic classes to the
-   * final compilation result.
-   */
-  public static final String INTERNAL_SYNTHETIC_CLASS_SEPARATOR = "-$$InternalSynthetic";
-
-  /**
-   * The external synthetic class separator is used when writing classes. It may appear in types
-   * during compilation as the output of a compilation may be the input to another.
-   */
-  public static final String EXTERNAL_SYNTHETIC_CLASS_SEPARATOR = "-$$ExternalSynthetic";
-
-  /** Method prefix when generating synthetic methods in a class. */
-  public static final String INTERNAL_SYNTHETIC_METHOD_PREFIX = "m";
-
-  public static boolean verifyNotInternalSynthetic(DexType type) {
-    assert !type.toDescriptorString().contains(SyntheticItems.INTERNAL_SYNTHETIC_CLASS_SEPARATOR);
-    return true;
-  }
-
   /** Globally incremented id for the next internal synthetic class. */
   private int nextSyntheticId;
 
-  /**
-   * Thread safe collection of synthesized classes that are not yet committed to the application.
-   *
-   * <p>TODO(b/158159959): Remove legacy support.
-   */
-  private final Map<DexType, DexProgramClass> legacyPendingClasses = new ConcurrentHashMap<>();
+  /** Collection of pending items. */
+  private static class PendingSynthetics {
+    /**
+     * Thread safe collection of synthesized classes that are not yet committed to the application.
+     *
+     * <p>TODO(b/158159959): Remove legacy support.
+     */
+    private final Map<DexType, DexProgramClass> legacyClasses = new ConcurrentHashMap<>();
 
-  /**
-   * Immutable set of synthetic types in the application (eg, committed).
-   *
-   * <p>TODO(b/158159959): Remove legacy support.
-   */
-  private final ImmutableSet<DexType> legacySyntheticTypes;
+    /** Thread safe collection of synthetic items not yet committed to the application. */
+    private final ConcurrentHashMap<DexType, SyntheticDefinition<?, ?, ?>> nonLegacyDefinitions =
+        new ConcurrentHashMap<>();
 
-  /** Thread safe collection of synthetic items not yet committed to the application. */
-  private final ConcurrentHashMap<DexType, SyntheticDefinition> pendingDefinitions =
-      new ConcurrentHashMap<>();
+    boolean isEmpty() {
+      return legacyClasses.isEmpty() && nonLegacyDefinitions.isEmpty();
+    }
 
-  /** Mapping from synthetic type to its synthetic description. */
-  private final ImmutableMap<DexType, SyntheticReference> nonLecacySyntheticItems;
+    boolean containsType(DexType type) {
+      return legacyClasses.containsKey(type) || nonLegacyDefinitions.containsKey(type);
+    }
+
+    boolean verifyNotRewritten(NonIdentityGraphLens lens) {
+      assert legacyClasses.keySet().equals(lens.rewriteTypes(legacyClasses.keySet()));
+      assert nonLegacyDefinitions.keySet().equals(lens.rewriteTypes(nonLegacyDefinitions.keySet()));
+      return true;
+    }
+
+    Collection<DexProgramClass> getAllProgramClasses() {
+      List<DexProgramClass> allPending =
+          new ArrayList<>(nonLegacyDefinitions.size() + legacyClasses.size());
+      for (SyntheticDefinition<?, ?, ?> item : nonLegacyDefinitions.values()) {
+        if (item.isProgramDefinition()) {
+          allPending.add(item.asProgramDefinition().getHolder());
+        }
+      }
+      allPending.addAll(legacyClasses.values());
+      return Collections.unmodifiableList(allPending);
+    }
+  }
+
+  private final CommittedSyntheticsCollection committed;
+
+  private final PendingSynthetics pending = new PendingSynthetics();
 
   // Only for use from initial AppInfo/AppInfoWithClassHierarchy create functions. */
   public static CommittedItems createInitialSyntheticItems(DexApplication application) {
     return new CommittedItems(
-        0, application, ImmutableSet.of(), ImmutableMap.of(), ImmutableList.of());
+        0, application, CommittedSyntheticsCollection.empty(), ImmutableList.of());
   }
 
   // Only for conversion to a mutable synthetic items collection.
   SyntheticItems(CommittedItems commit) {
-    this(commit.nextSyntheticId, commit.legacySyntheticTypes, commit.syntheticItems);
+    this(commit.nextSyntheticId, commit.committed);
   }
 
-  private SyntheticItems(
-      int nextSyntheticId,
-      ImmutableSet<DexType> legacySyntheticTypes,
-      ImmutableMap<DexType, SyntheticReference> nonLecacySyntheticItems) {
+  private SyntheticItems(int nextSyntheticId, CommittedSyntheticsCollection committed) {
     this.nextSyntheticId = nextSyntheticId;
-    this.legacySyntheticTypes = legacySyntheticTypes;
-    this.nonLecacySyntheticItems = nonLecacySyntheticItems;
-    assert Sets.intersection(nonLecacySyntheticItems.keySet(), legacySyntheticTypes).isEmpty();
+    this.committed = committed;
   }
 
   public static void collectSyntheticInputs(AppView<AppInfo> appView) {
     // Collecting synthetic items must be the very first task after application build.
     SyntheticItems synthetics = appView.getSyntheticItems();
     assert synthetics.nextSyntheticId == 0;
-    assert synthetics.nonLecacySyntheticItems.isEmpty();
-    assert !synthetics.hasPendingSyntheticClasses();
+    assert synthetics.committed.isEmpty();
+    assert synthetics.pending.isEmpty();
     if (appView.options().intermediate) {
       // If the compilation is in intermediate mode the synthetics should just be passed through.
       return;
     }
-    ImmutableMap.Builder<DexType, SyntheticReference> pending = ImmutableMap.builder();
+    CommittedSyntheticsCollection.Builder builder = synthetics.committed.builder();
     // TODO(b/158159959): Consider identifying synthetics in the input reader to speed this up.
     for (DexProgramClass clazz : appView.appInfo().classes()) {
-      DexType annotatedContextType = isSynthesizedMethodsContainer(clazz, appView.dexItemFactory());
-      if (annotatedContextType == null) {
-        continue;
+      SyntheticMarker marker =
+          SyntheticMarker.stripMarkerFromClass(clazz, appView.dexItemFactory());
+      if (marker.isSyntheticMethods()) {
+        clazz.forEachProgramMethod(
+            // TODO(b/158159959): Support having multiple methods per class.
+            method -> {
+              builder.addNonLegacyMethod(
+                  new SyntheticMethodDefinition(marker.getKind(), marker.getContext(), method));
+            });
+      } else if (marker.isSyntheticClass()) {
+        builder.addNonLegacyClass(
+            new SyntheticProgramClassDefinition(marker.getKind(), marker.getContext(), clazz));
       }
-      clazz.setAnnotations(DexAnnotationSet.empty());
-      SynthesizingContext context =
-          SynthesizingContext.fromSyntheticInputClass(clazz, annotatedContextType);
-      clazz.forEachProgramMethod(
-          // TODO(b/158159959): Support having multiple methods per class.
-          method -> {
-            method.getDefinition().setAnnotations(DexAnnotationSet.empty());
-            pending.put(clazz.type, new SyntheticMethodDefinition(context, method).toReference());
-          });
     }
-    pending.putAll(synthetics.nonLecacySyntheticItems);
-    ImmutableMap<DexType, SyntheticReference> nonLegacySyntheticItems = pending.build();
-    if (nonLegacySyntheticItems.isEmpty()) {
+    CommittedSyntheticsCollection committed = builder.build();
+    if (committed.isEmpty()) {
       return;
     }
     CommittedItems commit =
         new CommittedItems(
-            synthetics.nextSyntheticId,
-            appView.appInfo().app(),
-            synthetics.legacySyntheticTypes,
-            nonLegacySyntheticItems,
-            ImmutableList.of());
+            synthetics.nextSyntheticId, appView.appInfo().app(), committed, ImmutableList.of());
     appView.setAppInfo(new AppInfo(commit, appView.appInfo().getMainDexClasses()));
   }
 
-  private static DexType isSynthesizedMethodsContainer(
-      DexProgramClass clazz, DexItemFactory factory) {
-    ClassAccessFlags flags = clazz.accessFlags;
-    if (!flags.isSynthetic() || flags.isAbstract() || flags.isEnum()) {
-      return null;
-    }
-    DexType contextType =
-        DexAnnotation.getSynthesizedClassAnnotationContextType(clazz.annotations(), factory);
-    if (contextType == null) {
-      return null;
-    }
-    if (clazz.superType != factory.objectType) {
-      return null;
-    }
-    if (!clazz.interfaces.isEmpty()) {
-      return null;
-    }
-    if (clazz.annotations().size() != 1) {
-      return null;
-    }
-    for (DexEncodedMethod method : clazz.methods()) {
-      if (!SyntheticMethodBuilder.isValidSyntheticMethod(method)) {
-        return null;
-      }
-    }
-    return contextType;
-  }
-
   // Internal synthetic id creation helpers.
 
   private synchronized String getNextSyntheticId() {
@@ -191,49 +151,54 @@
 
   @Override
   public DexClass definitionFor(DexType type, Function<DexType, DexClass> baseDefinitionFor) {
-    DexProgramClass pending = legacyPendingClasses.get(type);
-    if (pending == null) {
-      SyntheticDefinition item = pendingDefinitions.get(type);
+    DexClass clazz = pending.legacyClasses.get(type);
+    if (clazz == null) {
+      SyntheticDefinition<?, ?, ?> item = pending.nonLegacyDefinitions.get(type);
       if (item != null) {
-        pending = item.getHolder();
+        clazz = item.getHolder();
+        assert clazz.isProgramClass() == item.isProgramDefinition();
+        assert clazz.isClasspathClass() == item.isClasspathDefinition();
       }
     }
-    if (pending != null) {
+    if (clazz != null) {
       assert baseDefinitionFor.apply(type) == null
           : "Pending synthetic definition also present in the active program: " + type;
-      return pending;
+      return clazz;
     }
     return baseDefinitionFor.apply(type);
   }
 
+  public boolean verifyNonLegacySyntheticsAreCommitted() {
+    assert pending.nonLegacyDefinitions.isEmpty()
+        : "Uncommitted synthetics: "
+            + pending.nonLegacyDefinitions.keySet().stream()
+                .map(DexType::getName)
+                .collect(Collectors.joining(", "));
+    return true;
+  }
+
   public boolean hasPendingSyntheticClasses() {
-    return !legacyPendingClasses.isEmpty() || !pendingDefinitions.isEmpty();
+    return !pending.isEmpty();
   }
 
   public Collection<DexProgramClass> getPendingSyntheticClasses() {
-    List<DexProgramClass> pending =
-        new ArrayList<>(pendingDefinitions.size() + legacyPendingClasses.size());
-    for (SyntheticDefinition item : pendingDefinitions.values()) {
-      pending.add(item.getHolder());
-    }
-    pending.addAll(legacyPendingClasses.values());
-    return Collections.unmodifiableList(pending);
+    return pending.getAllProgramClasses();
   }
 
   private boolean isCommittedSynthetic(DexType type) {
-    return nonLecacySyntheticItems.containsKey(type) || legacySyntheticTypes.contains(type);
+    return committed.containsType(type);
   }
 
   private boolean isLegacyCommittedSynthetic(DexType type) {
-    return legacySyntheticTypes.contains(type);
+    return committed.containsLegacyType(type);
   }
 
   public boolean isPendingSynthetic(DexType type) {
-    return pendingDefinitions.containsKey(type) || legacyPendingClasses.containsKey(type);
+    return pending.containsType(type);
   }
 
   public boolean isLegacyPendingSynthetic(DexType type) {
-    return legacyPendingClasses.containsKey(type);
+    return pending.legacyClasses.containsKey(type);
   }
 
   public boolean isSyntheticClass(DexType type) {
@@ -247,6 +212,27 @@
     return isSyntheticClass(clazz.type);
   }
 
+  // The compiler should not inspect the kind of a synthetic, so this provided only as a assertion
+  // utility.
+  public boolean verifySyntheticLambdaProperty(
+      DexProgramClass clazz,
+      Predicate<DexProgramClass> ifIsLambda,
+      Predicate<DexProgramClass> ifNotLambda) {
+    SyntheticReference<?, ?, ?> reference = committed.getNonLegacyItem(clazz.getType());
+    if (reference == null) {
+      SyntheticDefinition<?, ?, ?> definition = pending.nonLegacyDefinitions.get(clazz.getType());
+      if (definition != null) {
+        reference = definition.toReference();
+      }
+    }
+    if (reference != null && reference.getKind() == SyntheticKind.LAMBDA) {
+      assert ifIsLambda.test(clazz);
+    } else {
+      assert ifNotLambda.test(clazz);
+    }
+    return true;
+  }
+
   public boolean isLegacySyntheticClass(DexType type) {
     return isLegacyCommittedSynthetic(type) || isLegacyPendingSynthetic(type);
   }
@@ -256,18 +242,21 @@
   }
 
   public Collection<DexProgramClass> getLegacyPendingClasses() {
-    return Collections.unmodifiableCollection(legacyPendingClasses.values());
+    return Collections.unmodifiableCollection(pending.legacyClasses.values());
   }
 
   private SynthesizingContext getSynthesizingContext(ProgramDefinition context) {
-    SyntheticDefinition pendingItemContext = pendingDefinitions.get(context.getContextType());
-    if (pendingItemContext != null) {
-      return pendingItemContext.getContext();
+    DexType contextType = context.getContextType();
+    SyntheticDefinition<?, ?, ?> existingDefinition = pending.nonLegacyDefinitions.get(contextType);
+    if (existingDefinition != null) {
+      return existingDefinition.getContext();
     }
-    SyntheticReference committedItemContext = nonLecacySyntheticItems.get(context.getContextType());
-    return committedItemContext != null
-        ? committedItemContext.getContext()
-        : SynthesizingContext.fromNonSyntheticInputContext(context);
+    SyntheticReference<?, ?, ?> existingReference = committed.getNonLegacyItem(contextType);
+    if (existingReference != null) {
+      return existingReference.getContext();
+    }
+    // This context is not nested in an existing synthetic context so create a new "leaf" context.
+    return SynthesizingContext.fromNonSyntheticInputContext(context);
   }
 
   // Addition and creation of synthetic items.
@@ -276,25 +265,71 @@
   public void addLegacySyntheticClass(DexProgramClass clazz) {
     assert clazz.type.isD8R8SynthesizedClassType();
     assert !isCommittedSynthetic(clazz.type);
-    DexProgramClass previous = legacyPendingClasses.put(clazz.type, clazz);
+    assert !pending.nonLegacyDefinitions.containsKey(clazz.type);
+    DexProgramClass previous = pending.legacyClasses.put(clazz.type, clazz);
     assert previous == null || previous == clazz;
   }
 
+  public DexProgramClass createClass(
+      SyntheticKind kind,
+      DexProgramClass context,
+      DexItemFactory factory,
+      Supplier<String> syntheticIdSupplier,
+      Consumer<SyntheticProgramClassBuilder> fn) {
+    // Obtain the outer synthesizing context in the case the context itself is synthetic.
+    // This is to ensure a flat input-type -> synthetic-item mapping.
+    SynthesizingContext outerContext = getSynthesizingContext(context);
+    DexType type =
+        SyntheticNaming.createInternalType(kind, outerContext, syntheticIdSupplier.get(), factory);
+    SyntheticProgramClassBuilder classBuilder =
+        new SyntheticProgramClassBuilder(type, outerContext, factory);
+    fn.accept(classBuilder);
+    DexProgramClass clazz = classBuilder.build();
+    addPendingDefinition(new SyntheticProgramClassDefinition(kind, outerContext, clazz));
+    return clazz;
+  }
+
+  public DexClasspathClass createClasspathClass(
+      SyntheticKind kind, DexType type, DexClass context, DexItemFactory factory) {
+    // Obtain the outer synthesizing context in the case the context itself is synthetic.
+    // This is to ensure a flat input-type -> synthetic-item mapping.
+    SynthesizingContext outerContext = SynthesizingContext.fromNonSyntheticInputContext(context);
+    SyntheticClasspathClassBuilder classBuilder =
+        new SyntheticClasspathClassBuilder(type, outerContext, factory);
+    DexClasspathClass clazz = classBuilder.build();
+    addPendingDefinition(new SyntheticClasspathClassDefinition(kind, outerContext, clazz));
+    return clazz;
+  }
+
   /** Create a single synthetic method item. */
   public ProgramMethod createMethod(
-      ProgramDefinition context, DexItemFactory factory, Consumer<SyntheticMethodBuilder> fn) {
-    return createMethod(context, factory, fn, this::getNextSyntheticId);
+      SyntheticKind kind,
+      ProgramDefinition context,
+      DexItemFactory factory,
+      Consumer<SyntheticMethodBuilder> fn) {
+    return createMethod(kind, context, factory, fn, this::getNextSyntheticId);
   }
 
+  // TODO(b/172194101): Remove this once the uniqueness is a property of the context.
   public ProgramMethod createMethod(
+      SyntheticKind kind,
       ProgramDefinition context,
       DexItemFactory factory,
       Consumer<SyntheticMethodBuilder> fn,
       MethodProcessingId methodProcessingId) {
-    return createMethod(context, factory, fn, methodProcessingId::getFullyQualifiedIdAndIncrement);
+    return createMethod(
+        kind,
+        context,
+        factory,
+        fn,
+        methodProcessingId != null
+            ? methodProcessingId::getFullyQualifiedIdAndIncrement
+            : this::getNextSyntheticId);
   }
 
-  private ProgramMethod createMethod(
+  // TODO(b/172194101): Remove/private this once the uniqueness is a property of the context.
+  public ProgramMethod createMethod(
+      SyntheticKind kind,
       ProgramDefinition context,
       DexItemFactory factory,
       Consumer<SyntheticMethodBuilder> fn,
@@ -303,16 +338,21 @@
     // Obtain the outer synthesizing context in the case the context itself is synthetic.
     // This is to ensure a flat input-type -> synthetic-item mapping.
     SynthesizingContext outerContext = getSynthesizingContext(context);
-    DexType type = outerContext.createHygienicType(syntheticIdSupplier.get(), factory);
-    SyntheticClassBuilder classBuilder = new SyntheticClassBuilder(type, outerContext, factory);
-    DexProgramClass clazz = classBuilder.addMethod(fn).build();
+    DexType type =
+        SyntheticNaming.createInternalType(kind, outerContext, syntheticIdSupplier.get(), factory);
+    SyntheticProgramClassBuilder classBuilder =
+        new SyntheticProgramClassBuilder(type, outerContext, factory);
+    DexProgramClass clazz =
+        classBuilder
+            .addMethod(fn.andThen(m -> m.setName(SyntheticNaming.INTERNAL_SYNTHETIC_METHOD_PREFIX)))
+            .build();
     ProgramMethod method = new ProgramMethod(clazz, clazz.methods().iterator().next());
-    addPendingDefinition(new SyntheticMethodDefinition(outerContext, method));
+    addPendingDefinition(new SyntheticMethodDefinition(kind, outerContext, method));
     return method;
   }
 
-  private void addPendingDefinition(SyntheticDefinition definition) {
-    pendingDefinitions.put(definition.getHolder().getType(), definition);
+  private void addPendingDefinition(SyntheticDefinition<?, ?, ?> definition) {
+    pending.nonLegacyDefinitions.put(definition.getHolder().getType(), definition);
   }
 
   // Commit of the synthetic items to a new fully populated application.
@@ -322,125 +362,56 @@
   }
 
   public CommittedItems commitPrunedItems(PrunedItems prunedItems) {
-    return commit(
-        prunedItems.getPrunedApp(),
-        prunedItems.getNoLongerSyntheticItems(),
-        legacyPendingClasses,
-        legacySyntheticTypes,
-        pendingDefinitions,
-        nonLecacySyntheticItems,
-        nextSyntheticId);
+    return commit(prunedItems, pending, committed, nextSyntheticId);
   }
 
   public CommittedItems commitRewrittenWithLens(
       DexApplication application, NonIdentityGraphLens lens) {
-    // Rewrite the previously committed synthetic types.
-    ImmutableSet<DexType> rewrittenLegacyTypes = lens.rewriteTypes(this.legacySyntheticTypes);
-    ImmutableMap.Builder<DexType, SyntheticReference> rewrittenItems = ImmutableMap.builder();
-    for (SyntheticReference reference : nonLecacySyntheticItems.values()) {
-      SyntheticReference rewritten = reference.rewrite(lens);
-      if (rewritten != null) {
-        rewrittenItems.put(rewritten.getHolder(), rewritten);
-      }
-    }
-    // No pending item should need rewriting.
-    assert legacyPendingClasses.keySet().equals(lens.rewriteTypes(legacyPendingClasses.keySet()));
-    assert pendingDefinitions.keySet().equals(lens.rewriteTypes(pendingDefinitions.keySet()));
+    assert pending.verifyNotRewritten(lens);
     return commit(
-        application,
-        Collections.emptySet(),
-        legacyPendingClasses,
-        rewrittenLegacyTypes,
-        pendingDefinitions,
-        rewrittenItems.build(),
-        nextSyntheticId);
+        PrunedItems.empty(application), pending, committed.rewriteWithLens(lens), nextSyntheticId);
   }
 
   private static CommittedItems commit(
-      DexApplication application,
-      Set<DexType> removedClasses,
-      Map<DexType, DexProgramClass> legacyPendingClasses,
-      ImmutableSet<DexType> legacySyntheticTypes,
-      ConcurrentHashMap<DexType, SyntheticDefinition> pendingDefinitions,
-      ImmutableMap<DexType, SyntheticReference> syntheticItems,
+      PrunedItems prunedItems,
+      PendingSynthetics pending,
+      CommittedSyntheticsCollection committed,
       int nextSyntheticId) {
-    // Legacy synthetics must already have been committed.
-    assert verifyClassesAreInApp(application, legacyPendingClasses.values());
-    // Add the set of legacy definitions to the synthetic types.
-    ImmutableSet<DexType> mergedLegacyTypes = legacySyntheticTypes;
-    if (!legacyPendingClasses.isEmpty() || !removedClasses.isEmpty()) {
-      ImmutableSet.Builder<DexType> legacyBuilder = ImmutableSet.builder();
-      filteredAdd(legacySyntheticTypes, removedClasses, legacyBuilder);
-      filteredAdd(legacyPendingClasses.keySet(), removedClasses, legacyBuilder);
-      mergedLegacyTypes = legacyBuilder.build();
-    }
-    // The set of synthetic items is the union of the previous types plus the pending additions.
-    ImmutableMap<DexType, SyntheticReference> mergedItems;
-    ImmutableList<DexType> additions;
+    DexApplication application = prunedItems.getPrunedApp();
+    Set<DexType> removedClasses = prunedItems.getNoLongerSyntheticItems();
+    CommittedSyntheticsCollection.Builder builder = committed.builder();
+    // Legacy synthetics must already have been committed to the app.
+    assert verifyClassesAreInApp(application, pending.legacyClasses.values());
+    builder.addLegacyClasses(pending.legacyClasses.values());
+    // Compute the synthetic additions and add them to the application.
+    ImmutableList<DexType> committedProgramTypes;
     DexApplication amendedApplication;
-    if (pendingDefinitions.isEmpty()) {
-      mergedItems = filteredCopy(syntheticItems, removedClasses);
-      additions = ImmutableList.of();
+    if (pending.nonLegacyDefinitions.isEmpty()) {
+      committedProgramTypes = ImmutableList.of();
       amendedApplication = application;
     } else {
       DexApplication.Builder<?> appBuilder = application.builder();
-      ImmutableMap.Builder<DexType, SyntheticReference> itemsBuilder = ImmutableMap.builder();
-      ImmutableList.Builder<DexType> additionsBuilder = ImmutableList.builder();
-      for (SyntheticDefinition definition : pendingDefinitions.values()) {
-        if (removedClasses.contains(definition.getHolder().getType())) {
-          continue;
+      ImmutableList.Builder<DexType> committedProgramTypesBuilder = ImmutableList.builder();
+      for (SyntheticDefinition<?, ?, ?> definition : pending.nonLegacyDefinitions.values()) {
+        if (!removedClasses.contains(definition.getHolder().getType())) {
+          if (definition.isProgramDefinition()) {
+            committedProgramTypesBuilder.add(definition.getHolder().getType());
+            appBuilder.addProgramClass(definition.asProgramDefinition().getHolder());
+          } else {
+            assert definition.isClasspathDefinition();
+            appBuilder.addClasspathClass(definition.asClasspathDefinition().getHolder());
+          }
+          builder.addItem(definition);
         }
-        SyntheticReference reference = definition.toReference();
-        itemsBuilder.put(reference.getHolder(), reference);
-        additionsBuilder.add(definition.getHolder().getType());
-        appBuilder.addProgramClass(definition.getHolder());
       }
-      filteredAdd(syntheticItems, removedClasses, itemsBuilder);
-      mergedItems = itemsBuilder.build();
-      additions = additionsBuilder.build();
+      committedProgramTypes = committedProgramTypesBuilder.build();
       amendedApplication = appBuilder.build();
     }
     return new CommittedItems(
-        nextSyntheticId, amendedApplication, mergedLegacyTypes, mergedItems, additions);
-  }
-
-  private static void filteredAdd(
-      Set<DexType> input, Set<DexType> excludeSet, Builder<DexType> result) {
-    if (excludeSet.isEmpty()) {
-      result.addAll(input);
-    } else {
-      for (DexType type : input) {
-        if (!excludeSet.contains(type)) {
-          result.add(type);
-        }
-      }
-    }
-  }
-
-  private static ImmutableMap<DexType, SyntheticReference> filteredCopy(
-      ImmutableMap<DexType, SyntheticReference> syntheticItems, Set<DexType> removedClasses) {
-    if (removedClasses.isEmpty()) {
-      return syntheticItems;
-    }
-    ImmutableMap.Builder<DexType, SyntheticReference> builder = ImmutableMap.builder();
-    filteredAdd(syntheticItems, removedClasses, builder);
-    return builder.build();
-  }
-
-  private static void filteredAdd(
-      ImmutableMap<DexType, SyntheticReference> syntheticItems,
-      Set<DexType> removedClasses,
-      ImmutableMap.Builder<DexType, SyntheticReference> builder) {
-    if (removedClasses.isEmpty()) {
-      builder.putAll(syntheticItems);
-    } else {
-      syntheticItems.forEach(
-          (t, r) -> {
-            if (!removedClasses.contains(t)) {
-              builder.put(t, r);
-            }
-          });
-    }
+        nextSyntheticId,
+        amendedApplication,
+        builder.build().pruneItems(prunedItems),
+        committedProgramTypes);
   }
 
   private static boolean verifyClassesAreInApp(
@@ -455,8 +426,6 @@
 
   public Result computeFinalSynthetics(AppView<?> appView) {
     assert !hasPendingSyntheticClasses();
-    return new SyntheticFinalization(
-            appView.options(), legacySyntheticTypes, nonLecacySyntheticItems)
-        .computeFinalSynthetics(appView);
+    return new SyntheticFinalization(appView.options(), committed).computeFinalSynthetics(appView);
   }
 }
diff --git a/src/main/java/com/android/tools/r8/synthesis/SyntheticMarker.java b/src/main/java/com/android/tools/r8/synthesis/SyntheticMarker.java
new file mode 100644
index 0000000..03b7e36
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticMarker.java
@@ -0,0 +1,96 @@
+// 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.synthesis;
+
+import com.android.tools.r8.graph.ClassAccessFlags;
+import com.android.tools.r8.graph.DexAnnotation;
+import com.android.tools.r8.graph.DexAnnotationSet;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.synthesis.SyntheticNaming.SyntheticKind;
+import com.android.tools.r8.utils.Pair;
+
+public class SyntheticMarker {
+
+  public static void addMarkerToClass(
+      DexProgramClass clazz,
+      SyntheticKind kind,
+      SynthesizingContext context,
+      DexItemFactory factory) {
+    clazz.setAnnotations(
+        clazz
+            .annotations()
+            .getWithAddedOrReplaced(
+                DexAnnotation.createAnnotationSynthesizedClass(
+                    kind, context.getSynthesizingContextType(), factory)));
+  }
+
+  public static SyntheticMarker stripMarkerFromClass(
+      DexProgramClass clazz, DexItemFactory factory) {
+    SyntheticMarker marker = internalStripMarkerFromClass(clazz, factory);
+    assert marker != NO_MARKER
+        || DexAnnotation.getSynthesizedClassAnnotationContextType(clazz.annotations(), factory)
+            == null;
+    return marker;
+  }
+
+  private static SyntheticMarker internalStripMarkerFromClass(
+      DexProgramClass clazz, DexItemFactory factory) {
+    ClassAccessFlags flags = clazz.accessFlags;
+    if (clazz.superType != factory.objectType) {
+      return NO_MARKER;
+    }
+    if (!flags.isSynthetic() || flags.isAbstract() || flags.isEnum()) {
+      return NO_MARKER;
+    }
+    Pair<SyntheticKind, DexType> info =
+        DexAnnotation.getSynthesizedClassAnnotationContextType(clazz.annotations(), factory);
+    if (info == null) {
+      return NO_MARKER;
+    }
+    assert clazz.annotations().size() == 1;
+    SyntheticKind kind = info.getFirst();
+    DexType context = info.getSecond();
+    if (kind.isSingleSyntheticMethod) {
+      if (!clazz.interfaces.isEmpty()) {
+        return NO_MARKER;
+      }
+      for (DexEncodedMethod method : clazz.methods()) {
+        if (!SyntheticMethodBuilder.isValidSyntheticMethod(method)) {
+          return NO_MARKER;
+        }
+      }
+    }
+    clazz.setAnnotations(DexAnnotationSet.empty());
+    return new SyntheticMarker(kind, SynthesizingContext.fromSyntheticInputClass(clazz, context));
+  }
+
+  private static final SyntheticMarker NO_MARKER = new SyntheticMarker(null, null);
+
+  private final SyntheticKind kind;
+  private final SynthesizingContext context;
+
+  public SyntheticMarker(SyntheticKind kind, SynthesizingContext context) {
+    this.kind = kind;
+    this.context = context;
+  }
+
+  public boolean isSyntheticMethods() {
+    return kind != null && kind.isSingleSyntheticMethod;
+  }
+
+  public boolean isSyntheticClass() {
+    return kind != null && !kind.isSingleSyntheticMethod;
+  }
+
+  public SyntheticKind getKind() {
+    return kind;
+  }
+
+  public SynthesizingContext getContext() {
+    return context;
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/synthesis/SyntheticMethodBuilder.java b/src/main/java/com/android/tools/r8/synthesis/SyntheticMethodBuilder.java
index 9fcc7d9..ab4cd49 100644
--- a/src/main/java/com/android/tools/r8/synthesis/SyntheticMethodBuilder.java
+++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticMethodBuilder.java
@@ -9,6 +9,7 @@
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexProto;
+import com.android.tools.r8.graph.DexString;
 import com.android.tools.r8.graph.GenericSignature.MethodTypeSignature;
 import com.android.tools.r8.graph.MethodAccessFlags;
 import com.android.tools.r8.graph.ParameterAnnotationsList;
@@ -19,16 +20,26 @@
     Code generate(DexMethod method);
   }
 
-  private final SyntheticClassBuilder parent;
-  private final String name;
+  private final SyntheticClassBuilder<?, ?> parent;
+  private DexString name = null;
   private DexProto proto = null;
   private CfVersion classFileVersion;
   private SyntheticCodeGenerator codeGenerator = null;
   private MethodAccessFlags accessFlags = null;
 
-  SyntheticMethodBuilder(SyntheticClassBuilder parent, String name) {
+  SyntheticMethodBuilder(SyntheticClassBuilder<?, ?> parent) {
     this.parent = parent;
+  }
+
+  public SyntheticMethodBuilder setName(String name) {
+    return setName(parent.getFactory().createString(name));
+  }
+
+  public SyntheticMethodBuilder setName(DexString name) {
+    assert name != null;
+    assert this.name == null;
     this.name = name;
+    return this;
   }
 
   public SyntheticMethodBuilder setProto(DexProto proto) {
@@ -52,6 +63,7 @@
   }
 
   DexEncodedMethod build() {
+    assert name != null;
     boolean isCompilerSynthesized = true;
     DexMethod methodSignature = getMethodSignature();
     DexEncodedMethod method =
diff --git a/src/main/java/com/android/tools/r8/synthesis/SyntheticMethodDefinition.java b/src/main/java/com/android/tools/r8/synthesis/SyntheticMethodDefinition.java
index ecc1a81..87393f7 100644
--- a/src/main/java/com/android/tools/r8/synthesis/SyntheticMethodDefinition.java
+++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticMethodDefinition.java
@@ -3,78 +3,74 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.synthesis;
 
-import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.synthesis.SyntheticNaming.SyntheticKind;
 import com.android.tools.r8.utils.structural.RepresentativeMap;
-import com.google.common.hash.HashCode;
 import com.google.common.hash.Hasher;
-import com.google.common.hash.Hashing;
-import java.util.Comparator;
+import java.util.function.Consumer;
 
 /**
  * Definition of a synthetic method item.
  *
  * <p>This class is internal to the synthetic items collection, thus package-protected.
  */
-class SyntheticMethodDefinition extends SyntheticDefinition
-    implements Comparable<SyntheticMethodDefinition> {
+class SyntheticMethodDefinition
+    extends SyntheticDefinition<
+        SyntheticMethodReference, SyntheticMethodDefinition, DexProgramClass>
+    implements SyntheticProgramDefinition {
 
   private final ProgramMethod method;
 
-  SyntheticMethodDefinition(SynthesizingContext context, ProgramMethod method) {
-    super(context);
+  SyntheticMethodDefinition(SyntheticKind kind, SynthesizingContext context, ProgramMethod method) {
+    super(kind, context);
     this.method = method;
   }
 
+  @Override
+  public void apply(
+      Consumer<SyntheticMethodDefinition> onMethod,
+      Consumer<SyntheticProgramClassDefinition> onClass) {
+    onMethod.accept(this);
+  }
+
   public ProgramMethod getMethod() {
     return method;
   }
 
   @Override
-  SyntheticReference toReference() {
-    return new SyntheticMethodReference(getContext(), method.getReference());
+  public boolean isProgramDefinition() {
+    return true;
   }
 
   @Override
-  DexProgramClass getHolder() {
+  public SyntheticProgramDefinition asProgramDefinition() {
+    return this;
+  }
+
+  @Override
+  SyntheticMethodReference toReference() {
+    return new SyntheticMethodReference(getKind(), getContext(), method.getReference());
+  }
+
+  @Override
+  public DexProgramClass getHolder() {
     return method.getHolder();
   }
 
   @Override
-  HashCode computeHash(RepresentativeMap map, boolean intermediate) {
-    Hasher hasher = Hashing.sha256().newHasher();
-    if (intermediate) {
-      // If in intermediate mode, include the context type as sharing is restricted to within a
-      // single context.
-      hasher.putInt(getContext().getSynthesizingContextType().hashCode());
-    }
-    method.getDefinition().hashSyntheticContent(hasher, map);
-    return hasher.hash();
+  void internalComputeHash(Hasher hasher, RepresentativeMap map) {
+    method.getDefinition().hashWithTypeEquivalence(hasher, map);
   }
 
   @Override
-  boolean isEquivalentTo(SyntheticDefinition other, boolean intermediate) {
-    if (!(other instanceof SyntheticMethodDefinition)) {
-      return false;
-    }
-    if (intermediate
-        && getContext().getSynthesizingContextType()
-            != other.getContext().getSynthesizingContextType()) {
-      // If in intermediate mode, only synthetics within the same context should be considered
-      // equal.
-      return false;
-    }
-    SyntheticMethodDefinition o = (SyntheticMethodDefinition) other;
-    return method.getDefinition().isSyntheticContentEqual(o.method.getDefinition());
+  int internalCompareTo(SyntheticMethodDefinition other, RepresentativeMap map) {
+    return method.getDefinition().compareWithTypeEquivalenceTo(other.method.getDefinition(), map);
   }
 
-  // Since methods are sharable they must define an order from which representatives can be found.
   @Override
-  public int compareTo(SyntheticMethodDefinition other) {
-    return Comparator.comparing(SyntheticMethodDefinition::getContext)
-        .thenComparing(m -> m.method.getDefinition(), DexEncodedMethod::syntheticCompareTo)
-        .compare(this, other);
+  public boolean isValid() {
+    return SyntheticMethodBuilder.isValidSyntheticMethod(method.getDefinition());
   }
 
   @Override
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 28913d0..09a0f1a 100644
--- a/src/main/java/com/android/tools/r8/synthesis/SyntheticMethodReference.java
+++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticMethodReference.java
@@ -5,10 +5,12 @@
 
 import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexMethod;
-import com.android.tools.r8.graph.DexReference;
+import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.GraphLens.NonIdentityGraphLens;
 import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.synthesis.SyntheticNaming.SyntheticKind;
+import java.util.function.Consumer;
 import java.util.function.Function;
 
 /**
@@ -16,37 +18,36 @@
  *
  * <p>This class is internal to the synthetic items collection, thus package-protected.
  */
-class SyntheticMethodReference extends SyntheticReference {
+class SyntheticMethodReference
+    extends SyntheticReference<SyntheticMethodReference, SyntheticMethodDefinition, DexProgramClass>
+    implements SyntheticProgramReference {
   final DexMethod method;
 
-  SyntheticMethodReference(SynthesizingContext context, DexMethod method) {
-    super(context);
+  SyntheticMethodReference(SyntheticKind kind, SynthesizingContext context, DexMethod method) {
+    super(kind, context);
     this.method = method;
   }
 
   @Override
-  DexReference getReference() {
-    return method;
-  }
-
-  @Override
   DexType getHolder() {
     return method.holder;
   }
 
   @Override
-  SyntheticDefinition lookupDefinition(Function<DexType, DexClass> definitions) {
+  SyntheticMethodDefinition lookupDefinition(Function<DexType, DexClass> definitions) {
     DexClass clazz = definitions.apply(method.holder);
     if (clazz == null) {
       return null;
     }
     assert clazz.isProgramClass();
     ProgramMethod definition = clazz.asProgramClass().lookupProgramMethod(method);
-    return definition != null ? new SyntheticMethodDefinition(getContext(), definition) : null;
+    return definition != null
+        ? new SyntheticMethodDefinition(getKind(), getContext(), definition)
+        : null;
   }
 
   @Override
-  SyntheticReference rewrite(NonIdentityGraphLens lens) {
+  SyntheticMethodReference rewrite(NonIdentityGraphLens lens) {
     DexMethod rewritten = lens.lookupMethod(method);
     // If the reference has been non-trivially rewritten the compiler has changed it and it can no
     // longer be considered a synthetic. The context may or may not have changed.
@@ -54,12 +55,29 @@
       // 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 SyntheticItems.verifyNotInternalSynthetic(rewritten.holder);
+      assert SyntheticNaming.verifyNotInternalSynthetic(rewritten.holder);
       return null;
     }
     SynthesizingContext context = getContext().rewrite(lens);
-    return context == getContext() && rewritten == method
-        ? this
-        : new SyntheticMethodReference(context, rewritten);
+    if (context == getContext() && rewritten == method) {
+      return this;
+    }
+    // Ensure that if a synthetic moves its context moves consistently.
+    if (method != rewritten) {
+      context =
+          SynthesizingContext.fromSyntheticContextChange(
+              rewritten.holder, context, lens.dexItemFactory());
+      if (context == null) {
+        return null;
+      }
+    }
+    return new SyntheticMethodReference(getKind(), context, rewritten);
+  }
+
+  @Override
+  public void apply(
+      Consumer<SyntheticMethodReference> onMethod,
+      Consumer<SyntheticProgramClassReference> onClass) {
+    onMethod.accept(this);
   }
 }
diff --git a/src/main/java/com/android/tools/r8/synthesis/SyntheticNaming.java b/src/main/java/com/android/tools/r8/synthesis/SyntheticNaming.java
new file mode 100644
index 0000000..b33e260
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticNaming.java
@@ -0,0 +1,170 @@
+// 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.synthesis;
+
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.references.ClassReference;
+import com.android.tools.r8.references.Reference;
+import com.android.tools.r8.utils.DescriptorUtils;
+
+public class SyntheticNaming {
+
+  /**
+   * Enumeration of all kinds of synthetic items.
+   *
+   * <p>The synthetic kinds are used to provide hinting about what a synthetic item represents. The
+   * kinds must *not* be used be the compiler and are only meant for "debugging". The compiler and
+   * its test may use the kind information as part of asserting properties of the compiler. The kind
+   * will be put into any non-minified synthetic name and thus the kind "descriptor" must be a
+   * distinct for each kind.
+   */
+  public enum SyntheticKind {
+    // Class synthetics.
+    COMPANION_CLASS("CompanionClass", false),
+    LAMBDA("Lambda", false),
+    // Method synthetics.
+    BACKPORT("Backport", true),
+    STATIC_INTERFACE_CALL("StaticInterfaceCall", true),
+    TO_STRING_IF_NOT_NULL("ToStringIfNotNull", true),
+    THROW_CCE_IF_NOT_NULL("ThrowCCEIfNotNull", true),
+    THROW_ICCE("ThrowICCE", true),
+    THROW_NSME("ThrowNSME", true),
+    TWR_CLOSE_RESOURCE("TwrCloseResource", true),
+    SERVICE_LOADER("ServiceLoad", true);
+
+    public final String descriptor;
+    public final boolean isSingleSyntheticMethod;
+
+    SyntheticKind(String descriptor, boolean isSingleSyntheticMethod) {
+      this.descriptor = descriptor;
+      this.isSingleSyntheticMethod = isSingleSyntheticMethod;
+    }
+
+    public static SyntheticKind fromDescriptor(String descriptor) {
+      for (SyntheticKind kind : values()) {
+        if (kind.descriptor.equals(descriptor)) {
+          return kind;
+        }
+      }
+      return null;
+    }
+  }
+
+  /**
+   * The internal synthetic class separator is only used for representing synthetic items during
+   * compilation. In particular, this separator must never be used to write synthetic classes to the
+   * final compilation result.
+   */
+  private static final String INTERNAL_SYNTHETIC_CLASS_SEPARATOR = "-$$InternalSynthetic";
+  /**
+   * The external synthetic class separator is used when writing classes. It may appear in types
+   * during compilation as the output of a compilation may be the input to another.
+   */
+  private static final String EXTERNAL_SYNTHETIC_CLASS_SEPARATOR = "-$$ExternalSynthetic";
+  /** Method prefix when generating synthetic methods in a class. */
+  static final String INTERNAL_SYNTHETIC_METHOD_PREFIX = "m";
+
+  // TODO(b/158159959): Remove usage of name-based identification.
+  public static boolean isSyntheticName(String typeName) {
+    return typeName.contains(INTERNAL_SYNTHETIC_CLASS_SEPARATOR)
+        || typeName.contains(EXTERNAL_SYNTHETIC_CLASS_SEPARATOR);
+  }
+
+  static DexType createInternalType(
+      SyntheticKind kind, SynthesizingContext context, String id, DexItemFactory factory) {
+    return createType(
+        INTERNAL_SYNTHETIC_CLASS_SEPARATOR,
+        kind,
+        context.getSynthesizingContextType(),
+        id,
+        factory);
+  }
+
+  static DexType createExternalType(
+      SyntheticKind kind, DexType context, String id, DexItemFactory factory) {
+    return createType(EXTERNAL_SYNTHETIC_CLASS_SEPARATOR, kind, context, id, factory);
+  }
+
+  private static DexType createType(
+      String separator, SyntheticKind kind, DexType context, String id, DexItemFactory factory) {
+    return factory.createType(createDescriptor(separator, kind, context.getInternalName(), id));
+  }
+
+  private static String createDescriptor(
+      String separator, SyntheticKind kind, String context, String id) {
+    return DescriptorUtils.getDescriptorFromClassBinaryName(
+        context + separator + kind.descriptor + id);
+  }
+
+  public static boolean verifyNotInternalSynthetic(DexType type) {
+    return verifyNotInternalSynthetic(type.toDescriptorString());
+  }
+
+  public static boolean verifyNotInternalSynthetic(ClassReference reference) {
+    return verifyNotInternalSynthetic(reference.getDescriptor());
+  }
+
+  public static boolean verifyNotInternalSynthetic(String typeBinaryNameOrDescriptor) {
+    assert !typeBinaryNameOrDescriptor.contains(INTERNAL_SYNTHETIC_CLASS_SEPARATOR);
+    return true;
+  }
+
+  // Visible via package protection in SyntheticItemsTestUtils.
+
+  enum Phase {
+    INTERNAL,
+    EXTERNAL
+  }
+
+  static String getPhaseSeparator(Phase phase) {
+    assert phase != null;
+    return phase == Phase.INTERNAL
+        ? INTERNAL_SYNTHETIC_CLASS_SEPARATOR
+        : EXTERNAL_SYNTHETIC_CLASS_SEPARATOR;
+  }
+
+  static ClassReference makeSyntheticReferenceForTest(
+      ClassReference context, SyntheticKind kind, String id) {
+    return Reference.classFromDescriptor(
+        createDescriptor(EXTERNAL_SYNTHETIC_CLASS_SEPARATOR, kind, context.getBinaryName(), id));
+  }
+
+  static boolean isSynthetic(ClassReference clazz, Phase phase, SyntheticKind kind) {
+    String typeName = clazz.getTypeName();
+    String separator = getPhaseSeparator(phase);
+    int i = typeName.indexOf(separator);
+    return i >= 0 && checkMatchFrom(kind, typeName, i, separator, phase == Phase.EXTERNAL);
+  }
+
+  private static boolean checkMatchFrom(
+      SyntheticKind kind,
+      String name,
+      int i,
+      String externalSyntheticClassSeparator,
+      boolean checkIntSuffix) {
+    int end = i + externalSyntheticClassSeparator.length() + kind.descriptor.length();
+    if (end >= name.length()) {
+      return false;
+    }
+    String prefix = name.substring(i, end);
+    return prefix.equals(externalSyntheticClassSeparator + kind.descriptor)
+        && (!checkIntSuffix || isInt(name.substring(end)));
+  }
+
+  private static boolean isInt(String str) {
+    if (str.isEmpty()) {
+      return false;
+    }
+    if ('0' == str.charAt(0)) {
+      return str.length() == 1;
+    }
+    for (int i = 0; i < str.length(); i++) {
+      if (!Character.isDigit(str.charAt(i))) {
+        return false;
+      }
+    }
+    return true;
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/synthesis/SyntheticProgramClassBuilder.java b/src/main/java/com/android/tools/r8/synthesis/SyntheticProgramClassBuilder.java
new file mode 100644
index 0000000..6408621
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticProgramClassBuilder.java
@@ -0,0 +1,27 @@
+// 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.synthesis;
+
+import com.android.tools.r8.graph.ClassKind;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.DexType;
+
+public class SyntheticProgramClassBuilder
+    extends SyntheticClassBuilder<SyntheticProgramClassBuilder, DexProgramClass> {
+
+  SyntheticProgramClassBuilder(DexType type, SynthesizingContext context, DexItemFactory factory) {
+    super(type, context, factory);
+  }
+
+  @Override
+  public ClassKind<DexProgramClass> getClassKind() {
+    return ClassKind.PROGRAM;
+  }
+
+  @Override
+  public SyntheticProgramClassBuilder self() {
+    return this;
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/synthesis/SyntheticProgramClassDefinition.java b/src/main/java/com/android/tools/r8/synthesis/SyntheticProgramClassDefinition.java
new file mode 100644
index 0000000..8b42eb6
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticProgramClassDefinition.java
@@ -0,0 +1,74 @@
+// 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.synthesis;
+
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.synthesis.SyntheticNaming.SyntheticKind;
+import com.android.tools.r8.utils.structural.RepresentativeMap;
+import com.google.common.hash.Hasher;
+import java.util.function.Consumer;
+
+/**
+ * Definition of a synthetic class item.
+ *
+ * <p>This class is internal to the synthetic items collection, thus package-protected.
+ */
+class SyntheticProgramClassDefinition
+    extends SyntheticClassDefinition<
+        SyntheticProgramClassReference, SyntheticProgramClassDefinition, DexProgramClass>
+    implements SyntheticProgramDefinition {
+
+  SyntheticProgramClassDefinition(
+      SyntheticKind kind, SynthesizingContext context, DexProgramClass clazz) {
+    super(kind, context, clazz);
+  }
+
+  @Override
+  public void apply(
+      Consumer<SyntheticMethodDefinition> onMethod,
+      Consumer<SyntheticProgramClassDefinition> onClass) {
+    onClass.accept(this);
+  }
+
+  @Override
+  public boolean isProgramDefinition() {
+    return true;
+  }
+
+  @Override
+  public SyntheticProgramDefinition asProgramDefinition() {
+    return this;
+  }
+
+  @Override
+  SyntheticProgramClassReference toReference() {
+    return new SyntheticProgramClassReference(getKind(), getContext(), clazz.getType());
+  }
+
+  @Override
+  public boolean isValid() {
+    return clazz.isPublic() && clazz.isFinal() && clazz.accessFlags.isSynthetic();
+  }
+
+  @Override
+  void internalComputeHash(Hasher hasher, RepresentativeMap map) {
+    clazz.hashWithTypeEquivalence(hasher, map);
+  }
+
+  @Override
+  int internalCompareTo(SyntheticProgramClassDefinition o, RepresentativeMap map) {
+    return clazz.compareWithTypeEquivalenceTo(o.clazz, map);
+  }
+
+  @Override
+  public String toString() {
+    return "SyntheticProgramClass{ clazz = "
+        + clazz.type.toSourceString()
+        + ", kind = "
+        + getKind()
+        + ", context = "
+        + getContext()
+        + " }";
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/synthesis/SyntheticProgramClassReference.java b/src/main/java/com/android/tools/r8/synthesis/SyntheticProgramClassReference.java
new file mode 100644
index 0000000..3472268
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticProgramClassReference.java
@@ -0,0 +1,70 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.synthesis;
+
+import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.GraphLens.NonIdentityGraphLens;
+import com.android.tools.r8.synthesis.SyntheticNaming.SyntheticKind;
+import java.util.function.Consumer;
+import java.util.function.Function;
+
+/**
+ * Reference to a synthetic class item.
+ *
+ * <p>This class is internal to the synthetic items collection, thus package-protected.
+ */
+class SyntheticProgramClassReference
+    extends SyntheticClassReference<
+        SyntheticProgramClassReference, SyntheticProgramClassDefinition, DexProgramClass>
+    implements SyntheticProgramReference {
+
+  SyntheticProgramClassReference(SyntheticKind kind, SynthesizingContext context, DexType type) {
+    super(kind, context, type);
+  }
+
+  @Override
+  SyntheticProgramClassDefinition lookupDefinition(Function<DexType, DexClass> definitions) {
+    DexClass clazz = definitions.apply(type);
+    if (clazz == null) {
+      return null;
+    }
+    assert clazz.isProgramClass();
+    return new SyntheticProgramClassDefinition(getKind(), getContext(), clazz.asProgramClass());
+  }
+
+  @Override
+  SyntheticProgramClassReference rewrite(NonIdentityGraphLens lens) {
+    DexType rewritten = lens.lookupType(type);
+    // If the reference has been non-trivially rewritten the compiler has changed it and it can no
+    // longer be considered a synthetic. The context may or may not have changed.
+    if (type != rewritten && !lens.isSimpleRenaming(type, rewritten)) {
+      // 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 SyntheticNaming.verifyNotInternalSynthetic(rewritten);
+      return null;
+    }
+    SynthesizingContext context = getContext().rewrite(lens);
+    if (context == getContext() && rewritten == type) {
+      return this;
+    }
+    // Ensure that if a synthetic moves its context moves consistently.
+    if (type != rewritten) {
+      context =
+          SynthesizingContext.fromSyntheticContextChange(rewritten, context, lens.dexItemFactory());
+      if (context == null) {
+        return null;
+      }
+    }
+    return new SyntheticProgramClassReference(getKind(), context, rewritten);
+  }
+
+  @Override
+  public void apply(
+      Consumer<SyntheticMethodReference> onMethod,
+      Consumer<SyntheticProgramClassReference> onClass) {
+    onClass.accept(this);
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/synthesis/SyntheticProgramDefinition.java b/src/main/java/com/android/tools/r8/synthesis/SyntheticProgramDefinition.java
new file mode 100644
index 0000000..dd8953a
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticProgramDefinition.java
@@ -0,0 +1,17 @@
+// 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.synthesis;
+
+import com.android.tools.r8.graph.DexProgramClass;
+import java.util.function.Consumer;
+
+public interface SyntheticProgramDefinition {
+
+  void apply(
+      Consumer<SyntheticMethodDefinition> onMethod,
+      Consumer<SyntheticProgramClassDefinition> onClass);
+
+  DexProgramClass getHolder();
+}
diff --git a/src/main/java/com/android/tools/r8/synthesis/SyntheticProgramReference.java b/src/main/java/com/android/tools/r8/synthesis/SyntheticProgramReference.java
new file mode 100644
index 0000000..4df1f46
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticProgramReference.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.synthesis;
+
+import java.util.function.Consumer;
+
+public interface SyntheticProgramReference {
+
+  void apply(
+      Consumer<SyntheticMethodReference> onMethod,
+      Consumer<SyntheticProgramClassReference> onClass);
+}
diff --git a/src/main/java/com/android/tools/r8/synthesis/SyntheticReference.java b/src/main/java/com/android/tools/r8/synthesis/SyntheticReference.java
index 6618378..1a2d2fb 100644
--- a/src/main/java/com/android/tools/r8/synthesis/SyntheticReference.java
+++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticReference.java
@@ -4,9 +4,9 @@
 package com.android.tools.r8.synthesis;
 
 import com.android.tools.r8.graph.DexClass;
-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.synthesis.SyntheticNaming.SyntheticKind;
 import java.util.function.Function;
 
 /**
@@ -14,16 +14,26 @@
  *
  * <p>This class is internal to the synthetic items collection, thus package-protected.
  */
-abstract class SyntheticReference {
+abstract class SyntheticReference<
+    R extends SyntheticReference<R, D, C>,
+    D extends SyntheticDefinition<R, D, C>,
+    C extends DexClass> {
+
+  private final SyntheticKind kind;
   private final SynthesizingContext context;
 
-  SyntheticReference(SynthesizingContext context) {
+  SyntheticReference(SyntheticKind kind, SynthesizingContext context) {
+    assert kind != null;
+    assert context != null;
+    this.kind = kind;
     this.context = context;
   }
 
-  abstract SyntheticDefinition lookupDefinition(Function<DexType, DexClass> definitions);
+  abstract D lookupDefinition(Function<DexType, DexClass> definitions);
 
-  abstract DexReference getReference();
+  final SyntheticKind getKind() {
+    return kind;
+  }
 
   final SynthesizingContext getContext() {
     return context;
@@ -31,5 +41,5 @@
 
   abstract DexType getHolder();
 
-  abstract SyntheticReference rewrite(NonIdentityGraphLens lens);
+  abstract R rewrite(NonIdentityGraphLens lens);
 }
diff --git a/src/main/java/com/android/tools/r8/utils/BooleanBox.java b/src/main/java/com/android/tools/r8/utils/BooleanBox.java
index f46ac1c..f6b790d 100644
--- a/src/main/java/com/android/tools/r8/utils/BooleanBox.java
+++ b/src/main/java/com/android/tools/r8/utils/BooleanBox.java
@@ -4,6 +4,8 @@
 
 package com.android.tools.r8.utils;
 
+import java.util.function.BooleanSupplier;
+
 public class BooleanBox {
 
   private boolean value;
@@ -14,10 +16,24 @@
     set(initialValue);
   }
 
+  public void computeIfNotSet(BooleanSupplier supplier) {
+    if (isFalse()) {
+      set(supplier.getAsBoolean());
+    }
+  }
+
   public boolean get() {
     return value;
   }
 
+  public boolean isFalse() {
+    return !get();
+  }
+
+  public boolean isTrue() {
+    return get();
+  }
+
   public void set() {
     set(true);
   }
diff --git a/src/main/java/com/android/tools/r8/utils/ClasspathClassCollection.java b/src/main/java/com/android/tools/r8/utils/ClasspathClassCollection.java
index 28eb17b..e14d8ed 100644
--- a/src/main/java/com/android/tools/r8/utils/ClasspathClassCollection.java
+++ b/src/main/java/com/android/tools/r8/utils/ClasspathClassCollection.java
@@ -10,10 +10,19 @@
 
 /** Represents a collection of classpath classes. */
 public class ClasspathClassCollection extends ClassMap<DexClasspathClass> {
+
+  private ClasspathClassCollection() {
+    this(null);
+  }
+
   public ClasspathClassCollection(ClassProvider<DexClasspathClass> classProvider) {
     super(null, classProvider);
   }
 
+  public static ClasspathClassCollection empty() {
+    return new ClasspathClassCollection();
+  }
+
   @Override
   DexClasspathClass resolveClassConflict(DexClasspathClass a, DexClasspathClass b) {
     throw new CompilationError("Classpath type already present: " + a.type.toSourceString());
diff --git a/src/main/java/com/android/tools/r8/utils/ExceptionUtils.java b/src/main/java/com/android/tools/r8/utils/ExceptionUtils.java
index 8c4da03..ef5c3ca 100644
--- a/src/main/java/com/android/tools/r8/utils/ExceptionUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/ExceptionUtils.java
@@ -22,9 +22,20 @@
 import java.util.function.BiFunction;
 import java.util.function.Consumer;
 import java.util.function.Supplier;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
 
 public abstract class ExceptionUtils {
 
+  public static String getMainStackTrace() {
+    return Thread.getAllStackTraces().entrySet().stream()
+        .filter(x -> x.getKey().getName().equals("main"))
+        .map(x -> x.getValue())
+        .flatMap(x -> Stream.of(x))
+        .map(x -> x.toString())
+        .collect(Collectors.joining(System.lineSeparator()));
+  }
+
   public static void withConsumeResourceHandler(
       Reporter reporter, StringConsumer consumer, String data) {
     withConsumeResourceHandler(reporter, handler -> consumer.accept(data, handler));
diff --git a/src/main/java/com/android/tools/r8/utils/FilteredArchiveClassFileProvider.java b/src/main/java/com/android/tools/r8/utils/FilteredArchiveClassFileProvider.java
index 43cbb4d..483315e 100644
--- a/src/main/java/com/android/tools/r8/utils/FilteredArchiveClassFileProvider.java
+++ b/src/main/java/com/android/tools/r8/utils/FilteredArchiveClassFileProvider.java
@@ -10,6 +10,6 @@
 class FilteredArchiveClassFileProvider extends InternalArchiveClassFileProvider {
 
   FilteredArchiveClassFileProvider(FilteredClassPath archive) throws IOException {
-    super(archive.getPath(), entry -> archive.matchesFile(entry));
+    super(archive.getPath(), archive::matchesFile);
   }
 }
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 db914a4..a8bec4a 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -3,7 +3,6 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.utils;
 
-import static com.google.common.base.Predicates.not;
 
 import com.android.tools.r8.ClassFileConsumer;
 import com.android.tools.r8.CompilationMode;
@@ -32,10 +31,11 @@
 import com.android.tools.r8.features.FeatureSplitConfiguration;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexClass;
-import com.android.tools.r8.graph.DexDefinition;
+import com.android.tools.r8.graph.DexClasspathClass;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexItem;
 import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexLibraryClass;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexType;
@@ -48,6 +48,7 @@
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.conversion.MethodProcessingId;
 import com.android.tools.r8.ir.desugar.DesugaredLibraryConfiguration;
+import com.android.tools.r8.ir.desugar.nest.Nest;
 import com.android.tools.r8.ir.optimize.Inliner;
 import com.android.tools.r8.ir.optimize.enums.EnumDataMap;
 import com.android.tools.r8.ir.optimize.lambda.kotlin.KotlinLambdaGroupIdFactory;
@@ -73,13 +74,7 @@
 import com.google.common.collect.Iterables;
 import com.google.common.collect.Lists;
 import com.google.common.collect.Sets;
-import java.io.IOException;
 import java.io.PrintStream;
-import java.lang.reflect.GenericSignatureFormatError;
-import java.nio.file.Files;
-import java.nio.file.InvalidPathException;
-import java.nio.file.Path;
-import java.nio.file.Paths;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.Comparator;
@@ -307,6 +302,7 @@
   // the actual catch handler allowed when inlining. Threshold found empirically by testing on
   // GMS Core.
   public int inliningControlFlowResolutionBlocksThreshold = 15;
+  public boolean enableSwitchRewriting = true;
   public boolean enableStringSwitchConversion = true;
   public int minimumStringSwitchSize = 3;
   public boolean enableEnumValueOptimization = true;
@@ -492,7 +488,7 @@
     if (testing.enableForceNestBasedAccessDesugaringForTest) {
       return true;
     }
-    return enableNestBasedAccessDesugaring && !canUseNestBasedAccess();
+    return !canUseNestBasedAccess();
   }
 
   public Set<String> extensiveLoggingFilter = getExtensiveLoggingFilter();
@@ -513,10 +509,6 @@
   public boolean enableLambdaMerging = false;
   // Flag to turn on/off desugaring in D8/R8.
   public DesugarState desugarState = DesugarState.ON;
-  // Flag to turn on/off desugaring of invoke-special to a virtual method on the current class.
-  public boolean enableInvokeSpecialToVirtualMethodDesugaring = true;
-  // Flag to turn on/off JDK11+ nest-access control
-  public boolean enableNestBasedAccessDesugaring = true;
   // Flag to turn on/off reduction of nest to improve class merging optimizations.
   public boolean enableNestReduction = true;
   // Defines interface method rewriter behavior.
@@ -611,6 +603,7 @@
   public boolean printCfg = false;
   public String printCfgFile;
   public boolean ignoreMissingClasses = false;
+
   // EXPERIMENTAL flag to get behaviour as close to Proguard as possible.
   public boolean forceProguardCompatibility = false;
   public AssertionConfigurationWithDefault assertionsConfiguration = null;
@@ -680,19 +673,6 @@
     return ImmutableSet.of();
   }
 
-  private static Set<String> getExtensiveFieldMinifierLoggingFilter() {
-    String property =
-        System.getProperty("com.android.tools.r8.extensiveFieldMinifierLoggingFilter");
-    if (property != null) {
-      ImmutableSet.Builder<String> builder = ImmutableSet.builder();
-      for (String method : property.split(";")) {
-        builder.add(method);
-      }
-      return builder.build();
-    }
-    return ImmutableSet.of();
-  }
-
   private static Set<String> getExtensiveInterfaceMethodMinifierLoggingFilter() {
     String property =
         System.getProperty("com.android.tools.r8.extensiveInterfaceMethodMinifierLoggingFilter");
@@ -706,40 +686,6 @@
     return ImmutableSet.of();
   }
 
-  private static Set<String> getNullableReceiverInliningFilter() {
-    String property = System.getProperty("com.android.tools.r8.nullableReceiverInliningFilter");
-    if (property != null) {
-      // The property is allowed to be either (1) a path to a file where each line is a method
-      // signature, or (2) a semicolon separated list of method signatures.
-      Path path = null;
-      try {
-        Path tmp = Paths.get(property);
-        if (Files.exists(tmp)) {
-          path = tmp;
-        }
-      } catch (InvalidPathException | NullPointerException e) {
-        // Ignore, treat as a semicolon separated list of method signatures.
-      }
-      ImmutableSet.Builder<String> builder = ImmutableSet.builder();
-      if (path != null) {
-        try {
-          Files.readAllLines(path).stream()
-              .map(String::trim)
-              .filter(not(String::isEmpty))
-              .forEach(builder::add);
-        } catch (IOException e) {
-          throw new RuntimeException(e);
-        }
-      } else {
-        for (String method : property.split(";")) {
-          builder.add(method);
-        }
-      }
-      return builder.build();
-    }
-    return ImmutableSet.of();
-  }
-
   public static class InvalidParameterAnnotationInfo {
 
     final DexMethod method;
@@ -845,121 +791,64 @@
 
   private final Set<DexItem> invalidLibraryClasses = Sets.newConcurrentHashSet();
 
-  public void errorMissingClassMissingNestHost(DexClass compiledClass) {
-    throw reporter.fatalError(messageErrorMissingNestHost(compiledClass));
-  }
-
-  public void warningMissingClassMissingNestHost(DexClass compiledClass) {
-    if (compiledClass.isLibraryClass()) {
-      errorMissingClassMissingNestHost(compiledClass);
-    }
-    reporter.warning(new StringDiagnostic(messageWarningMissingNestHost(compiledClass)));
-  }
-
-  public void nestDesugaringWarningMissingNestHost(DexClass compiledClass) {
-    if (compiledClass.isLibraryClass()) {
-      errorMissingClassMissingNestHost(compiledClass);
-    }
-    reporter.warning(
+  public RuntimeException errorMissingNestHost(DexClass clazz) {
+    throw reporter.fatalError(
         new MissingNestHostNestDesugarDiagnostic(
-            compiledClass.getOrigin(),
-            Position.UNKNOWN,
-            messageWarningMissingNestHost(compiledClass)));
+            clazz.getOrigin(), Position.UNKNOWN, messageErrorMissingNestHost(clazz)));
   }
 
-  public void errorMissingClassIncompleteNest(List<DexType> nest, AppView<?> appView) {
-    throw reporter.fatalError(messageErrorIncompleteNest(nest, appView));
-  }
-
-  public void warningMissingClassIncompleteNest(List<DexType> nest, AppView<?> appView) {
-    for (DexType type : nest) {
-      DexClass clazz = appView.definitionFor(type);
-      if (clazz != null && clazz.isLibraryClass()) {
-        errorMissingClassIncompleteNest(nest, appView);
-        return;
-      }
-    }
-    reporter.warning(new StringDiagnostic(messageWarningIncompleteNest(nest, appView)));
-  }
-
-  public void nestDesugaringWarningIncompleteNest(List<DexType> nest, AppView<?> appView) {
-    DexClass availableClass = null;
-    for (DexType type : nest) {
-      DexClass clazz = appView.definitionFor(type);
-      if (clazz != null && clazz.isProgramClass()) {
-        availableClass = clazz;
-      } else if (clazz != null && clazz.isLibraryClass()) {
-        errorMissingClassIncompleteNest(nest, appView);
-        return;
-      }
-    }
-    assert availableClass != null;
-    reporter.warning(
-        new IncompleteNestNestDesugarDiagnosic(
-            availableClass.getOrigin(),
-            Position.UNKNOWN,
-            messageWarningIncompleteNest(nest, appView)));
-  }
-
-  private String messageErrorMissingNestHost(DexClass compiledClass) {
+  private static String messageErrorMissingNestHost(DexClass compiledClass) {
     String nestHostName = compiledClass.getNestHost().getName();
     return "Class "
         + compiledClass.type.getName()
         + " requires its nest host "
         + nestHostName
-        + " to be on program or class path. ";
+        + " to be on program or class path.";
   }
 
-  private String messageWarningMissingNestHost(DexClass compiledClass) {
-    return messageErrorMissingNestHost(compiledClass)
-        + "Class "
-        + compiledClass.type.getName()
-        + " is considered as not being part of any nest.";
+  public RuntimeException errorMissingNestMember(Nest nest) {
+    throw reporter.fatalError(
+        new IncompleteNestNestDesugarDiagnosic(
+            nest.getHostClass().getOrigin(), Position.UNKNOWN, messageErrorIncompleteNest(nest)));
   }
 
-  private String messageErrorIncompleteNest(List<DexType> nest, AppView<?> appView) {
-    List<String> programClassesFromNest = new ArrayList<>();
-    List<String> unavailableClasses = new ArrayList<>();
-    List<String> classPathClasses = new ArrayList<>();
-    List<String> libraryClasses = new ArrayList<>();
-    for (DexType type : nest) {
-      DexClass clazz = appView.definitionFor(appView.graphLens().lookupType(type));
-      if (clazz == null) {
-        unavailableClasses.add(type.getName());
-      } else if (clazz.isLibraryClass()) {
-        libraryClasses.add(type.getName());
-      } else if (clazz.isProgramClass()) {
-        programClassesFromNest.add(type.getName());
-      } else {
-        assert clazz.isClasspathClass();
-        classPathClasses.add(type.getName());
-      }
+  private static String messageErrorIncompleteNest(Nest nest) {
+    List<DexProgramClass> programClassesFromNest = new ArrayList<>();
+    List<DexClasspathClass> classpathClassesFromNest = new ArrayList<>();
+    List<DexLibraryClass> libraryClassesFromNest = new ArrayList<>();
+    nest.getHostClass()
+        .accept(
+            programClassesFromNest::add,
+            classpathClassesFromNest::add,
+            libraryClassesFromNest::add);
+    for (DexClass memberClass : nest.getMembers()) {
+      memberClass.accept(
+          programClassesFromNest::add, classpathClassesFromNest::add, libraryClassesFromNest::add);
     }
     StringBuilder stringBuilder =
         new StringBuilder("Compilation of classes ")
-            .append(String.join(", ", programClassesFromNest))
+            .append(StringUtils.join(", ", programClassesFromNest, DexClass::getTypeName))
             .append(" requires its nest mates ");
-    if (!unavailableClasses.isEmpty()) {
-      stringBuilder.append(String.join(", ", unavailableClasses)).append(" (unavailable) ");
+    if (nest.hasMissingMembers()) {
+      stringBuilder
+          .append(StringUtils.join(", ", nest.getMissingMembers(), DexType::getTypeName))
+          .append(" (unavailable) ");
     }
-    if (!libraryClasses.isEmpty()) {
-      stringBuilder.append(String.join(", ", unavailableClasses)).append(" (on library path) ");
+    if (!libraryClassesFromNest.isEmpty()) {
+      stringBuilder
+          .append(StringUtils.join(", ", libraryClassesFromNest, DexClass::getTypeName))
+          .append(" (on library path) ");
     }
     stringBuilder.append("to be on program or class path.");
-    if (!classPathClasses.isEmpty()) {
+    if (!classpathClassesFromNest.isEmpty()) {
       stringBuilder
           .append("(Classes ")
-          .append(String.join(", ", classPathClasses))
+          .append(StringUtils.join(", ", classpathClassesFromNest, DexClass::getTypeName))
           .append(" from the same nest are on class path).");
     }
     return stringBuilder.toString();
   }
 
-  private String messageWarningIncompleteNest(List<DexType> nest, AppView<?> appView) {
-    return messageErrorIncompleteNest(nest, appView)
-        + " Unavailable classes are considered as not being part of the nest.";
-  }
-
   public void warningMissingTypeForDesugar(
       Origin origin, Position position, DexType missingType, DexMethod context) {
     if (reportedMissingForDesugaring.add(missingType)) {
@@ -1034,31 +923,6 @@
     }
   }
 
-  public void warningInvalidSignature(
-      DexDefinition item, Origin origin, String signature, GenericSignatureFormatError e) {
-    StringBuilder message = new StringBuilder("Invalid signature '");
-    message.append(signature);
-    message.append("' for ");
-    if (item.isDexClass()) {
-      message.append("class ");
-      message.append((item.asDexClass()).getType().toSourceString());
-    } else if (item.isDexEncodedField()) {
-      message.append("field ");
-      message.append(item.toSourceString());
-    } else {
-      assert item.isDexEncodedMethod();
-      message.append("method ");
-      message.append(item.toSourceString());
-    }
-    message.append(".");
-    message.append(System.lineSeparator());
-    message.append("Signature is ignored and will not be present in the output.");
-    message.append(System.lineSeparator());
-    message.append("Parser error: ");
-    message.append(e.getMessage());
-    reporter.warning(new StringDiagnostic(message.toString(), origin));
-  }
-
   private final Box<Boolean> reportedExperimentClassFileVersion = new Box<>(false);
 
   public void warningExperimentalClassFileVersion(Origin origin) {
@@ -1280,6 +1144,7 @@
 
     public boolean enable = true;
     public boolean enableConstructorMerging = true;
+    // TODO(b/174809311): Update or remove the option and its tests after new lambdas synthetics.
     public boolean enableJavaLambdaMerging = false;
     public boolean enableKotlinLambdaMerging = true;
 
@@ -1429,14 +1294,15 @@
     public boolean addCallEdgesForLibraryInvokes = false;
 
     public boolean allowCheckDiscardedErrors = false;
+    public boolean allowDexInputForTesting = false;
     public boolean allowInjectedAnnotationMethods = false;
     public boolean allowTypeErrors =
         !Version.isDevelopmentVersion()
             || System.getProperty("com.android.tools.r8.allowTypeErrors") != null;
     public boolean allowInvokeErrors = false;
+    public boolean allowUnnecessaryDontWarnWildcards = true;
+    public boolean allowUnusedDontWarnRules = true;
     public boolean disableL8AnnotationRemoval = false;
-    public boolean allowClassInlinerGracefulExit =
-        System.getProperty("com.android.tools.r8.disallowClassInlinerGracefulExit") == null;
     public boolean reportUnusedProguardConfigurationRules = false;
     public boolean alwaysUseExistingAccessInfoCollectionsInMemberRebinding = true;
     public boolean alwaysUsePessimisticRegisterAllocation = false;
@@ -1458,7 +1324,6 @@
     public Consumer<IRCode> irModifier = null;
     public int basicBlockMuncherIterationLimit = NO_LIMIT;
     public boolean dontReportFailingCheckDiscarded = false;
-    public boolean deterministicSortingBasedOnDexType = true;
     public PrintStream whyAreYouNotInliningConsumer = System.out;
     public boolean trackDesugaredAPIConversions =
         System.getProperty("com.android.tools.r8.trackDesugaredAPIConversions") != null;
@@ -1472,6 +1337,10 @@
     public boolean disableMappingToOriginalProgramVerification = false;
     public boolean allowInvalidCfAccessFlags =
         System.getProperty("com.android.tools.r8.allowInvalidCfAccessFlags") != null;
+    // TODO(b/177333791): Set to true
+    public boolean checkForNotExpandingMainDexTracingResult = false;
+
+    public boolean allowConflictingSyntheticTypes = false;
 
     // Flag to allow processing of resources in D8. A data resource consumer still needs to be
     // specified.
@@ -1603,6 +1472,16 @@
     return !isDesugaring() || hasMinApi(AndroidApiLevel.K);
   }
 
+  public boolean enableTryWithResourcesDesugaring() {
+    switch (tryWithResourcesDesugaring) {
+      case Off:
+        return false;
+      case Auto:
+        return !canUseSuppressedExceptions();
+    }
+    throw new Unreachable();
+  }
+
   public boolean canUsePrivateInterfaceMethods() {
     return !isDesugaring() || hasMinApi(AndroidApiLevel.N);
   }
@@ -1622,6 +1501,10 @@
         && !canUseDefaultAndStaticInterfaceMethods();
   }
 
+  public boolean isSwitchRewritingEnabled() {
+    return enableSwitchRewriting && !debug;
+  }
+
   public boolean isStringSwitchConversionEnabled() {
     return enableStringSwitchConversion && !debug;
   }
@@ -2009,4 +1892,11 @@
   public boolean canHaveDalvikIntUsedAsNonIntPrimitiveTypeBug() {
     return isGeneratingClassFiles() || minApiLevel < AndroidApiLevel.L.getLevel();
   }
+
+  // The standard library prior to API 19 did not contain a ZipFile that implemented Closable.
+  //
+  // See b/177532008.
+  public boolean canHaveZipFileWithMissingCloseableBug() {
+    return isGeneratingClassFiles() || minApiLevel < AndroidApiLevel.K.getLevel();
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/utils/IterableUtils.java b/src/main/java/com/android/tools/r8/utils/IterableUtils.java
index cd1c0a0..2b0267d 100644
--- a/src/main/java/com/android/tools/r8/utils/IterableUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/IterableUtils.java
@@ -59,10 +59,14 @@
     return -1;
   }
 
-  public static <T> Iterable<T> filter(Iterable<T> iterable, Predicate<T> predicate) {
+  public static <T> Iterable<T> filter(Iterable<T> iterable, Predicate<? super T> predicate) {
     return () -> IteratorUtils.filter(iterable.iterator(), predicate);
   }
 
+  public static <T> boolean hasNext(Iterable<T> iterable) {
+    return iterable.iterator().hasNext();
+  }
+
   public static <T> int size(Iterable<T> iterable) {
     int result = 0;
     for (T element : iterable) {
@@ -77,6 +81,10 @@
     return result;
   }
 
+  public static <S, T> Iterable<T> transform(Iterable<S> iterable, Function<S, T> fn) {
+    return Iterables.transform(iterable, fn::apply);
+  }
+
   public static <T> boolean isEmpty(Iterable<T> iterable) {
     return !iterable.iterator().hasNext();
   }
@@ -102,13 +110,13 @@
   }
 
   public static <F> int sumInt(Iterable<F> iterable, Function<? super F, Integer> fn) {
-    Iterable<Integer> integers = Iterables.transform(iterable, i -> fn.apply(i));
+    Iterable<Integer> integers = Iterables.transform(iterable, fn::apply);
     return sumInt(integers);
   }
 
   public static <T, U> Iterable<U> flatMap(
       Iterable<T> iterable, Function<? super T, Iterable<U>> map) {
-    return Iterables.concat(Iterables.transform(iterable, val -> map.apply(val)));
+    return Iterables.concat(Iterables.transform(iterable, map::apply));
   }
 
   public static <T> Iterable<T> empty() {
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 40ae9ce..e6b0085 100644
--- a/src/main/java/com/android/tools/r8/utils/ListUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/ListUtils.java
@@ -14,6 +14,36 @@
 
 public class ListUtils {
 
+  /**
+   * Maps each element in the list using on the given function. Returns the mapped list if any
+   * elements were rewritten, otherwise returns the original list.
+   *
+   * <p>If the given function {@param fn} returns null for an element {@code v}, this is interpreted
+   * 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) {
+    List<T> result = null;
+    for (int i = 0; i < list.size(); i++) {
+      T element = list.get(i);
+      List<T> replacement = fn.apply(element);
+      if (replacement == null) {
+        if (result != null) {
+          result.add(element);
+        }
+      } else {
+        if (result == null) {
+          result = new ArrayList<>(list.size() + replacement.size() - 1);
+          for (int j = 0; j < i; j++) {
+            result.add(list.get(j));
+          }
+        }
+        result.addAll(replacement);
+      }
+    }
+    return result != null ? result : defaultValue;
+  }
+
   public static <T> T first(List<T> list) {
     return list.get(0);
   }
@@ -40,6 +70,14 @@
     return -1;
   }
 
+  public static <S, T> List<T> map(Iterable<S> list, Function<S, T> fn) {
+    List<T> result = new ArrayList<>();
+    for (S element : list) {
+      result.add(fn.apply(element));
+    }
+    return result;
+  }
+
   public static <S, T> List<T> map(Collection<S> list, Function<S, T> fn) {
     List<T> result = new ArrayList<>(list.size());
     for (S element : list) {
diff --git a/src/main/java/com/android/tools/r8/utils/MapUtils.java b/src/main/java/com/android/tools/r8/utils/MapUtils.java
index ac79586..abae424 100644
--- a/src/main/java/com/android/tools/r8/utils/MapUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/MapUtils.java
@@ -45,6 +45,6 @@
 
   public static String toString(Map<?, ?> map) {
     return StringUtils.join(
-        map.entrySet(), ",", BraceType.TUBORG, entry -> entry.getKey() + ":" + entry.getValue());
+        ",", map.entrySet(), entry -> entry.getKey() + ":" + entry.getValue(), BraceType.TUBORG);
   }
 }
diff --git a/src/main/java/com/android/tools/r8/utils/Pair.java b/src/main/java/com/android/tools/r8/utils/Pair.java
index ac58f56..c2f3489 100644
--- a/src/main/java/com/android/tools/r8/utils/Pair.java
+++ b/src/main/java/com/android/tools/r8/utils/Pair.java
@@ -50,4 +50,9 @@
   public boolean equals(Object obj) {
     throw new Unreachable("Pair does not want to support equality!");
   }
+
+  @Override
+  public String toString() {
+    return "Pair{" + first + ", " + second + '}';
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/utils/StringUtils.java b/src/main/java/com/android/tools/r8/utils/StringUtils.java
index a90d2bc..8f398ac 100644
--- a/src/main/java/com/android/tools/r8/utils/StringUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/StringUtils.java
@@ -108,8 +108,8 @@
     return append(builder, collection, ", ", BraceType.PARENS);
   }
 
-  public static <T> StringBuilder append(StringBuilder builder, Collection<T> collection,
-      String seperator, BraceType brace) {
+  public static <T> StringBuilder append(
+      StringBuilder builder, Iterable<T> collection, String seperator, BraceType brace) {
     builder.append(brace.left());
     boolean first = true;
     for (T element : collection) {
@@ -128,18 +128,22 @@
     return join(collection, separator, BraceType.NONE);
   }
 
+  public static <T> String join(String separator, Iterable<T> iterable, Function<T, String> fn) {
+    return join(separator, iterable, fn, BraceType.NONE);
+  }
+
   public static String join(String separator, String... strings) {
     return join(Arrays.asList(strings), separator, BraceType.NONE);
   }
 
   public static <T> String join(Collection<T> collection, String separator, BraceType brace) {
-    return join(collection, separator, brace, Object::toString);
+    return join(separator, collection, Object::toString, brace);
   }
 
-  public static <T> String join(Collection<T> collection, String separator, BraceType brace,
-      Function<T, String> fn) {
+  public static <T> String join(
+      String separator, Iterable<T> iterable, Function<T, String> fn, BraceType brace) {
     StringBuilder builder = new StringBuilder();
-    append(builder, ListUtils.map(collection, fn), separator, brace);
+    append(builder, IterableUtils.transform(iterable, fn), separator, brace);
     return builder.toString();
   }
 
diff --git a/src/main/java/com/android/tools/r8/utils/ThreadUtils.java b/src/main/java/com/android/tools/r8/utils/ThreadUtils.java
index 2a16dbf..a8fa794 100644
--- a/src/main/java/com/android/tools/r8/utils/ThreadUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/ThreadUtils.java
@@ -8,6 +8,7 @@
 import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
+import java.util.concurrent.Callable;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Executors;
@@ -18,6 +19,11 @@
 
   public static final int NOT_SPECIFIED = -1;
 
+  public static <T> Future<T> processAsynchronously(
+      Callable<T> callable, ExecutorService executorService) {
+    return executorService.submit(callable);
+  }
+
   public static <T, R, E extends Exception> Collection<R> processItemsWithResults(
       Iterable<T> items, ThrowingFunction<T, R, E> consumer, ExecutorService executorService)
       throws ExecutionException {
diff --git a/src/main/java/com/android/tools/r8/utils/structural/CompareToVisitor.java b/src/main/java/com/android/tools/r8/utils/structural/CompareToVisitor.java
index a0b2104..90e3b16 100644
--- a/src/main/java/com/android/tools/r8/utils/structural/CompareToVisitor.java
+++ b/src/main/java/com/android/tools/r8/utils/structural/CompareToVisitor.java
@@ -37,10 +37,7 @@
 
   public final <S extends StructuralItem<S>> int visitItemCollection(
       Collection<S> items1, Collection<S> items2) {
-    return visitItemIterator(
-        items1.iterator(),
-        items2.iterator(),
-        (s, other, visitor) -> s.acceptCompareTo(other, visitor));
+    return visitItemIterator(items1.iterator(), items2.iterator(), StructuralItem::acceptCompareTo);
   }
 
   public abstract int visitDexString(DexString string1, DexString string2);
diff --git a/src/main/java/com/android/tools/r8/utils/structural/CompareToVisitorBase.java b/src/main/java/com/android/tools/r8/utils/structural/CompareToVisitorBase.java
index 234732c..c721599 100644
--- a/src/main/java/com/android/tools/r8/utils/structural/CompareToVisitorBase.java
+++ b/src/main/java/com/android/tools/r8/utils/structural/CompareToVisitorBase.java
@@ -18,29 +18,39 @@
 /** Base class to share most visiting methods */
 public abstract class CompareToVisitorBase extends CompareToVisitor {
 
+  private static boolean DEBUG = false;
+
+  // Helper to debug insert a breakpoint on order values.
+  public static int debug(int order) {
+    if (DEBUG && order != 0) {
+      return order;
+    }
+    return order;
+  }
+
   @Override
   public final int visitBool(boolean value1, boolean value2) {
-    return Boolean.compare(value1, value2);
+    return debug(Boolean.compare(value1, value2));
   }
 
   @Override
   public final int visitInt(int value1, int value2) {
-    return Integer.compare(value1, value2);
+    return debug(Integer.compare(value1, value2));
   }
 
   @Override
   public int visitLong(long value1, long value2) {
-    return Long.compare(value1, value2);
+    return debug(Long.compare(value1, value2));
   }
 
   @Override
   public int visitFloat(float value1, float value2) {
-    return Float.compare(value1, value2);
+    return debug(Float.compare(value1, value2));
   }
 
   @Override
   public int visitDouble(double value1, double value2) {
-    return Double.compare(value1, value2);
+    return debug(Double.compare(value1, value2));
   }
 
   @Override
@@ -53,12 +63,12 @@
     if (order == 0) {
       order = visitBool(it1.hasNext(), it2.hasNext());
     }
-    return order;
+    return debug(order);
   }
 
   @Override
   public int visitDexString(DexString string1, DexString string2) {
-    return string1.compareTo(string2);
+    return debug(string1.compareTo(string2));
   }
 
   @Override
@@ -74,19 +84,19 @@
         order = visitDexMethod(reference1.asDexMethod(), reference2.asDexMethod());
       }
     }
-    return order;
+    return debug(order);
   }
 
   @Override
   public final <S> int visit(S item1, S item2, Comparator<S> comparator) {
-    return comparator.compare(item1, item2);
+    return debug(comparator.compare(item1, item2));
   }
 
   @Override
   public final <S> int visit(S item1, S item2, StructuralMapping<S> accept) {
     ItemSpecification<S> itemVisitor = new ItemSpecification<>(item1, item2, this);
     accept.apply(itemVisitor);
-    return itemVisitor.order;
+    return debug(itemVisitor.order);
   }
 
   private static class ItemSpecification<T>
@@ -198,7 +208,7 @@
     }
 
     @Override
-    protected <S> ItemSpecification<T> withItemIterator(
+    protected <S> ItemSpecification<T> withCustomItemIterator(
         Function<T, Iterator<S>> getter, CompareToAccept<S> compare, HashingAccept<S> hasher) {
       if (order == 0) {
         order = parent.visitItemIterator(getter.apply(item1), getter.apply(item2), compare);
diff --git a/src/main/java/com/android/tools/r8/utils/structural/CompareToVisitorWithNamingLens.java b/src/main/java/com/android/tools/r8/utils/structural/CompareToVisitorWithNamingLens.java
index f581065..7569929 100644
--- a/src/main/java/com/android/tools/r8/utils/structural/CompareToVisitorWithNamingLens.java
+++ b/src/main/java/com/android/tools/r8/utils/structural/CompareToVisitorWithNamingLens.java
@@ -29,34 +29,35 @@
 
   @Override
   public int visitDexType(DexType type1, DexType type2) {
-    return namingLens
-        .lookupDescriptor(type1)
-        .acceptCompareTo(namingLens.lookupDescriptor(type2), this);
+    return debug(
+        namingLens
+            .lookupDescriptor(type1)
+            .acceptCompareTo(namingLens.lookupDescriptor(type2), this));
   }
 
   @Override
   public int visitDexField(DexField field1, DexField field2) {
     int order = field1.holder.acceptCompareTo(field2.holder, this);
     if (order != 0) {
-      return order;
+      return debug(order);
     }
     order = namingLens.lookupName(field1).acceptCompareTo(namingLens.lookupName(field2), this);
     if (order != 0) {
-      return order;
+      return debug(order);
     }
-    return field1.type.acceptCompareTo(field2.type, this);
+    return debug(field1.type.acceptCompareTo(field2.type, this));
   }
 
   @Override
   public int visitDexMethod(DexMethod method1, DexMethod method2) {
     int order = method1.holder.acceptCompareTo(method2.holder, this);
     if (order != 0) {
-      return order;
+      return debug(order);
     }
     order = namingLens.lookupName(method1).acceptCompareTo(namingLens.lookupName(method2), this);
     if (order != 0) {
-      return order;
+      return debug(order);
     }
-    return method1.proto.acceptCompareTo(method2.proto, this);
+    return debug(method1.proto.acceptCompareTo(method2.proto, this));
   }
 }
diff --git a/src/main/java/com/android/tools/r8/utils/structural/CompareToVisitorWithTypeEquivalence.java b/src/main/java/com/android/tools/r8/utils/structural/CompareToVisitorWithTypeEquivalence.java
index 89bd02b..ccb5c9e 100644
--- a/src/main/java/com/android/tools/r8/utils/structural/CompareToVisitorWithTypeEquivalence.java
+++ b/src/main/java/com/android/tools/r8/utils/structural/CompareToVisitorWithTypeEquivalence.java
@@ -28,6 +28,6 @@
   public int visitDexType(DexType type1, DexType type2) {
     DexType repr1 = representatives.getRepresentative(type1);
     DexType repr2 = representatives.getRepresentative(type2);
-    return repr1.getDescriptor().acceptCompareTo(repr2.getDescriptor(), this);
+    return debug(repr1.getDescriptor().acceptCompareTo(repr2.getDescriptor(), this));
   }
 }
diff --git a/src/main/java/com/android/tools/r8/utils/structural/HashCodeVisitor.java b/src/main/java/com/android/tools/r8/utils/structural/HashCodeVisitor.java
index 892183d..244d404 100644
--- a/src/main/java/com/android/tools/r8/utils/structural/HashCodeVisitor.java
+++ b/src/main/java/com/android/tools/r8/utils/structural/HashCodeVisitor.java
@@ -105,7 +105,7 @@
   }
 
   @Override
-  protected <S> HashCodeVisitor<T> withItemIterator(
+  protected <S> HashCodeVisitor<T> withCustomItemIterator(
       Function<T, Iterator<S>> getter, CompareToAccept<S> compare, HashingAccept<S> hasher) {
     Iterator<S> it = getter.apply(item);
     while (it.hasNext()) {
diff --git a/src/main/java/com/android/tools/r8/utils/structural/HashingVisitorWithTypeEquivalence.java b/src/main/java/com/android/tools/r8/utils/structural/HashingVisitorWithTypeEquivalence.java
index e63ac6c..6c388fb 100644
--- a/src/main/java/com/android/tools/r8/utils/structural/HashingVisitorWithTypeEquivalence.java
+++ b/src/main/java/com/android/tools/r8/utils/structural/HashingVisitorWithTypeEquivalence.java
@@ -170,7 +170,7 @@
     }
 
     @Override
-    protected <S> ItemSpecification<T> withItemIterator(
+    protected <S> ItemSpecification<T> withCustomItemIterator(
         Function<T, Iterator<S>> getter, CompareToAccept<S> compare, HashingAccept<S> hasher) {
       parent.visitItemIterator(getter.apply(item), hasher);
       return this;
diff --git a/src/main/java/com/android/tools/r8/utils/structural/StructuralSpecification.java b/src/main/java/com/android/tools/r8/utils/structural/StructuralSpecification.java
index a81fc18..69d8796 100644
--- a/src/main/java/com/android/tools/r8/utils/structural/StructuralSpecification.java
+++ b/src/main/java/com/android/tools/r8/utils/structural/StructuralSpecification.java
@@ -51,12 +51,12 @@
       HashingAccept<S> hasher);
 
   /** Base implementation for visiting an enumeration of items. */
-  protected abstract <S> V withItemIterator(
+  protected abstract <S> V withCustomItemIterator(
       Function<T, Iterator<S>> getter, CompareToAccept<S> compare, HashingAccept<S> hasher);
 
   public final <S> V withCustomItemCollection(
       Function<T, Collection<S>> getter, StructuralAcceptor<S> acceptor) {
-    return withItemIterator(getter.andThen(Collection::iterator), acceptor, acceptor);
+    return withCustomItemIterator(getter.andThen(Collection::iterator), acceptor, acceptor);
   }
 
   /**
@@ -79,24 +79,23 @@
         predicate, getter, StructuralItem::acceptCompareTo, StructuralItem::acceptHashing);
   }
 
+  public final <S extends StructuralItem<S>> V withItemIterator(Function<T, Iterator<S>> getter) {
+    return withCustomItemIterator(
+        getter, StructuralItem::acceptCompareTo, StructuralItem::acceptHashing);
+  }
+
   public final <S extends StructuralItem<S>> V withItemCollection(
       Function<T, Collection<S>> getter) {
-    return withItemIterator(
-        getter.andThen(Collection::iterator),
-        StructuralItem::acceptCompareTo,
-        StructuralItem::acceptHashing);
+    return withItemIterator(getter.andThen(Collection::iterator));
   }
 
   public final <S extends StructuralItem<S>> V withItemArray(Function<T, S[]> getter) {
-    return withItemIterator(
-        getter.andThen(a -> Arrays.asList(a).iterator()),
-        StructuralItem::acceptCompareTo,
-        StructuralItem::acceptHashing);
+    return withItemIterator(getter.andThen(a -> Arrays.asList(a).iterator()));
   }
 
   public final <S extends StructuralItem<S>> V withItemArrayAllowingNullMembers(
       Function<T, S[]> getter) {
-    return withItemIterator(
+    return withCustomItemIterator(
         getter.andThen(a -> Arrays.asList(a).iterator()),
         (a, b, visitor) -> {
           if (a == null || b == null) {
diff --git a/src/main/keep.txt b/src/main/keep.txt
index 82f6965..24d0fbc 100644
--- a/src/main/keep.txt
+++ b/src/main/keep.txt
@@ -26,3 +26,8 @@
 
 # Compatibility command line program used by the Android Platform build.
 -keep public class com.android.tools.r8.compatproguard.CompatProguard { public static void main(java.lang.String[]); }
+
+# TODO(b/176783536): Avoid need to use -dontwarn.
+-dontwarn com.google.errorprone.annotations.**
+-dontwarn com.google.j2objc.annotations.*
+-dontwarn javax.annotation.Nullable
diff --git a/src/test/examplesAndroidO/multidex004/ref-list-1.txt b/src/test/examplesAndroidO/multidex004/ref-list-1.txt
index ac6e888..c817c33 100644
--- a/src/test/examplesAndroidO/multidex004/ref-list-1.txt
+++ b/src/test/examplesAndroidO/multidex004/ref-list-1.txt
@@ -1,4 +1,4 @@
-Lmultidex004/-$$Lambda$MainActivity$g120D_43GXrTOaB3kfYt6wSIJh4;
+Lmultidex004/MainActivity-$$ExternalSyntheticLambda0;
 Lmultidex004/MainActivity;
 Lmultidex004/VersionInterface;
 Lmultidex004/VersionStatic;
diff --git a/src/test/java/com/android/tools/r8/D8IncrementalRunExamplesAndroidOTest.java b/src/test/java/com/android/tools/r8/D8IncrementalRunExamplesAndroidOTest.java
index d25c465..82cfebf 100644
--- a/src/test/java/com/android/tools/r8/D8IncrementalRunExamplesAndroidOTest.java
+++ b/src/test/java/com/android/tools/r8/D8IncrementalRunExamplesAndroidOTest.java
@@ -13,9 +13,9 @@
 import com.android.tools.r8.errors.Unimplemented;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.ir.desugar.InterfaceMethodRewriter;
-import com.android.tools.r8.ir.desugar.LambdaRewriter;
-import com.android.tools.r8.ir.desugar.TwrCloseResourceRewriter;
-import com.android.tools.r8.synthesis.SyntheticItems;
+import com.android.tools.r8.references.ClassReference;
+import com.android.tools.r8.references.Reference;
+import com.android.tools.r8.synthesis.SyntheticItemsTestUtils;
 import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.AndroidApp;
 import com.android.tools.r8.utils.DescriptorUtils;
@@ -93,11 +93,12 @@
         for (String descriptor : descriptors) {
           // classes are either lambda classes used by the main class, companion classes of the main
           // interface, the main class/interface, or for JDK9, desugaring of try-with-resources.
+          ClassReference reference = Reference.classFromDescriptor(descriptor);
           Assert.assertTrue(
-              descriptor.contains(LambdaRewriter.LAMBDA_CLASS_NAME_PREFIX)
-                  || descriptor.endsWith(InterfaceMethodRewriter.COMPANION_CLASS_NAME_SUFFIX + ";")
-                  || descriptor.equals(TwrCloseResourceRewriter.UTILITY_CLASS_DESCRIPTOR)
-                  || descriptor.contains(SyntheticItems.EXTERNAL_SYNTHETIC_CLASS_SEPARATOR)
+              descriptor.endsWith(InterfaceMethodRewriter.COMPANION_CLASS_NAME_SUFFIX + ";")
+                  || SyntheticItemsTestUtils.isExternalTwrCloseMethod(reference)
+                  || SyntheticItemsTestUtils.isExternalLambda(reference)
+                  || SyntheticItemsTestUtils.isExternalStaticInterfaceCall(reference)
                   || descriptor.equals(mainClassDescriptor));
         }
         String classDescriptor =
diff --git a/src/test/java/com/android/tools/r8/D8LazyRunExamplesAndroidOTest.java b/src/test/java/com/android/tools/r8/D8LazyRunExamplesAndroidOTest.java
index 1d9ec50..78bbcf3 100644
--- a/src/test/java/com/android/tools/r8/D8LazyRunExamplesAndroidOTest.java
+++ b/src/test/java/com/android/tools/r8/D8LazyRunExamplesAndroidOTest.java
@@ -92,9 +92,7 @@
     // Build each class individually using tmpClassesDir as classpath for desugaring.
     List<ProgramResource> individalDexes = new ArrayList<>();
     List<Path> individualClassFiles =
-        Files.walk(tmpClassesDir)
-        .filter(classFile -> FileUtils.isClassFile(classFile))
-        .collect(Collectors.toList());
+        Files.walk(tmpClassesDir).filter(FileUtils::isClassFile).collect(Collectors.toList());
     for (Path classFile : individualClassFiles) {
       D8Command.Builder builder =
           D8Command.builder()
diff --git a/src/test/java/com/android/tools/r8/D8TestCompileResult.java b/src/test/java/com/android/tools/r8/D8TestCompileResult.java
index 1fcd0e7..b5fccb8 100644
--- a/src/test/java/com/android/tools/r8/D8TestCompileResult.java
+++ b/src/test/java/com/android/tools/r8/D8TestCompileResult.java
@@ -4,7 +4,9 @@
 package com.android.tools.r8;
 
 import com.android.tools.r8.ToolHelper.ProcessResult;
+import com.android.tools.r8.errors.Unimplemented;
 import com.android.tools.r8.utils.AndroidApp;
+import java.util.Set;
 
 public class D8TestCompileResult extends TestCompileResult<D8TestCompileResult, D8TestRunResult> {
   D8TestCompileResult(TestState state, AndroidApp app, int minApiLevel, OutputMode outputMode) {
@@ -22,6 +24,11 @@
   }
 
   @Override
+  public Set<String> getMainDexClasses() {
+    throw new Unimplemented();
+  }
+
+  @Override
   public String getStdout() {
     return state.getStdout();
   }
diff --git a/src/test/java/com/android/tools/r8/DXTestCompileResult.java b/src/test/java/com/android/tools/r8/DXTestCompileResult.java
index 4a0c42f..a3cf644 100644
--- a/src/test/java/com/android/tools/r8/DXTestCompileResult.java
+++ b/src/test/java/com/android/tools/r8/DXTestCompileResult.java
@@ -6,6 +6,7 @@
 import com.android.tools.r8.ToolHelper.ProcessResult;
 import com.android.tools.r8.errors.Unimplemented;
 import com.android.tools.r8.utils.AndroidApp;
+import java.util.Set;
 
 public class DXTestCompileResult extends TestCompileResult<DXTestCompileResult, DXTestRunResult> {
 
@@ -24,6 +25,11 @@
   }
 
   @Override
+  public Set<String> getMainDexClasses() {
+    throw new Unimplemented();
+  }
+
+  @Override
   public String getStdout() {
     throw new Unimplemented("Unexpected attempt to access stdout from dx");
   }
diff --git a/src/test/java/com/android/tools/r8/ExternalR8TestCompileResult.java b/src/test/java/com/android/tools/r8/ExternalR8TestCompileResult.java
index a7c0baf..cef49f7 100644
--- a/src/test/java/com/android/tools/r8/ExternalR8TestCompileResult.java
+++ b/src/test/java/com/android/tools/r8/ExternalR8TestCompileResult.java
@@ -5,10 +5,12 @@
 package com.android.tools.r8;
 
 import com.android.tools.r8.ToolHelper.ProcessResult;
+import com.android.tools.r8.errors.Unimplemented;
 import com.android.tools.r8.utils.AndroidApp;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import java.io.IOException;
 import java.nio.file.Path;
+import java.util.Set;
 import java.util.concurrent.ExecutionException;
 
 public class ExternalR8TestCompileResult
@@ -51,6 +53,11 @@
   }
 
   @Override
+  public Set<String> getMainDexClasses() {
+    throw new Unimplemented();
+  }
+
+  @Override
   public String getStdout() {
     return processResult.stdout;
   }
diff --git a/src/test/java/com/android/tools/r8/Keep.java b/src/test/java/com/android/tools/r8/Keep.java
new file mode 100644
index 0000000..ed8275b
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/Keep.java
@@ -0,0 +1,10 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+@Retention(RetentionPolicy.CLASS)
+public @interface Keep {}
diff --git a/src/test/java/com/android/tools/r8/ProguardTestBuilder.java b/src/test/java/com/android/tools/r8/ProguardTestBuilder.java
index 0e65de1..7aacf12 100644
--- a/src/test/java/com/android/tools/r8/ProguardTestBuilder.java
+++ b/src/test/java/com/android/tools/r8/ProguardTestBuilder.java
@@ -53,6 +53,11 @@
   }
 
   @Override
+  public boolean isProguardTestBuilder() {
+    return true;
+  }
+
+  @Override
   ProguardTestBuilder self() {
     return this;
   }
diff --git a/src/test/java/com/android/tools/r8/ProguardTestCompileResult.java b/src/test/java/com/android/tools/r8/ProguardTestCompileResult.java
index c352417..ca1a0a7 100644
--- a/src/test/java/com/android/tools/r8/ProguardTestCompileResult.java
+++ b/src/test/java/com/android/tools/r8/ProguardTestCompileResult.java
@@ -9,6 +9,7 @@
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import java.io.IOException;
 import java.nio.file.Path;
+import java.util.Set;
 import java.util.concurrent.ExecutionException;
 
 public class ProguardTestCompileResult
@@ -42,6 +43,11 @@
   }
 
   @Override
+  public Set<String> getMainDexClasses() {
+    throw new Unimplemented();
+  }
+
+  @Override
   public String getStdout() {
     throw new Unimplemented("Unexpected attempt to access stdout from Proguard");
   }
diff --git a/src/test/java/com/android/tools/r8/R8RunArtTestsTest.java b/src/test/java/com/android/tools/r8/R8RunArtTestsTest.java
index 4dc03b1..f22a173 100644
--- a/src/test/java/com/android/tools/r8/R8RunArtTestsTest.java
+++ b/src/test/java/com/android/tools/r8/R8RunArtTestsTest.java
@@ -741,8 +741,20 @@
           .put(
               "095-switch-MAX_INT",
               TestCondition.match(
+                  TestCondition.tools(DexTool.NONE),
+                  TestCondition.compilers(CompilerUnderTest.D8_AFTER_R8CF),
+                  TestCondition.runtimes(DexVm.Version.V4_0_4)))
+          .put(
+              "095-switch-MAX_INT",
+              TestCondition.match(
+                  TestCondition.tools(DexTool.NONE),
+                  TestCondition.compilers(CompilerUnderTest.D8),
+                  TestCondition.runtimes(DexVm.Version.V4_0_4)))
+          .put(
+              "095-switch-MAX_INT",
+              TestCondition.match(
                   TestCondition.tools(DexTool.DX),
-                  TestCondition.D8_COMPILER,
+                  TestCondition.compilers(CompilerUnderTest.D8),
                   TestCondition.runtimes(DexVm.Version.V4_0_4)))
           .build();
 
diff --git a/src/test/java/com/android/tools/r8/R8RunExamplesAndroidOTest.java b/src/test/java/com/android/tools/r8/R8RunExamplesAndroidOTest.java
index 7157943..389b0fa 100644
--- a/src/test/java/com/android/tools/r8/R8RunExamplesAndroidOTest.java
+++ b/src/test/java/com/android/tools/r8/R8RunExamplesAndroidOTest.java
@@ -107,14 +107,14 @@
         .withOptionConsumer(opts -> opts.enableClassInlining = false)
         .withBuilderTransformation(
             b -> b.addProguardConfiguration(PROGUARD_OPTIONS, Origin.unknown()))
-        .withDexCheck(inspector -> checkLambdaCount(inspector, 101, "lambdadesugaring"))
+        .withDexCheck(inspector -> checkLambdaCount(inspector, 102, "lambdadesugaring"))
         .run();
 
     test("lambdadesugaring", "lambdadesugaring", "LambdaDesugaring")
         .withMinApiLevel(ToolHelper.getMinApiLevelForDexVmNoHigherThan(AndroidApiLevel.K))
         .withBuilderTransformation(
             b -> b.addProguardConfiguration(PROGUARD_OPTIONS, Origin.unknown()))
-        .withDexCheck(inspector -> checkLambdaCount(inspector, 6, "lambdadesugaring"))
+        .withDexCheck(inspector -> checkLambdaCount(inspector, 7, "lambdadesugaring"))
         .run();
   }
 
@@ -146,14 +146,14 @@
         .withOptionConsumer(opts -> opts.enableClassInlining = false)
         .withBuilderTransformation(
             b -> b.addProguardConfiguration(PROGUARD_OPTIONS, Origin.unknown()))
-        .withDexCheck(inspector -> checkLambdaCount(inspector, 101, "lambdadesugaring"))
+        .withDexCheck(inspector -> checkLambdaCount(inspector, 102, "lambdadesugaring"))
         .run();
 
     test("lambdadesugaring", "lambdadesugaring", "LambdaDesugaring")
         .withMinApiLevel(AndroidApiLevel.N)
         .withBuilderTransformation(
             b -> b.addProguardConfiguration(PROGUARD_OPTIONS, Origin.unknown()))
-        .withDexCheck(inspector -> checkLambdaCount(inspector, 6, "lambdadesugaring"))
+        .withDexCheck(inspector -> checkLambdaCount(inspector, 7, "lambdadesugaring"))
         .run();
   }
 
diff --git a/src/test/java/com/android/tools/r8/R8RunExamplesCommon.java b/src/test/java/com/android/tools/r8/R8RunExamplesCommon.java
index 23ec0c9..8a004a9 100644
--- a/src/test/java/com/android/tools/r8/R8RunExamplesCommon.java
+++ b/src/test/java/com/android/tools/r8/R8RunExamplesCommon.java
@@ -169,6 +169,7 @@
 
   protected void configure(InternalOptions options) {
     options.lineNumberOptimization = LineNumberOptimization.OFF;
+    options.testing.allowDexInputForTesting = input == Input.DX;
   }
 
   private boolean shouldCompileFail() {
diff --git a/src/test/java/com/android/tools/r8/R8TestBuilder.java b/src/test/java/com/android/tools/r8/R8TestBuilder.java
index e831d72..909d45e 100644
--- a/src/test/java/com/android/tools/r8/R8TestBuilder.java
+++ b/src/test/java/com/android/tools/r8/R8TestBuilder.java
@@ -244,10 +244,6 @@
     return self();
   }
 
-  public T allowClassInlinerGracefulExit() {
-    return addOptionsModification(options -> options.testing.allowClassInlinerGracefulExit = true);
-  }
-
   /**
    * Allow info, warning, and error diagnostics.
    *
@@ -314,6 +310,10 @@
     return self();
   }
 
+  public T allowUnusedDontWarnPatterns() {
+    return addOptionsModification(options -> options.testing.allowUnusedDontWarnRules = true);
+  }
+
   public T allowUnusedProguardConfigurationRules() {
     return allowUnusedProguardConfigurationRules(true);
   }
diff --git a/src/test/java/com/android/tools/r8/R8TestCompileResult.java b/src/test/java/com/android/tools/r8/R8TestCompileResult.java
index 9b63b86..c9834f8 100644
--- a/src/test/java/com/android/tools/r8/R8TestCompileResult.java
+++ b/src/test/java/com/android/tools/r8/R8TestCompileResult.java
@@ -21,6 +21,7 @@
 import java.io.IOException;
 import java.nio.file.Path;
 import java.util.List;
+import java.util.Set;
 import java.util.function.Consumer;
 
 public class R8TestCompileResult extends TestCompileResult<R8TestCompileResult, R8TestRunResult> {
@@ -74,6 +75,11 @@
   }
 
   @Override
+  public Set<String> getMainDexClasses() {
+    return state.getMainDexClasses();
+  }
+
+  @Override
   public String getStdout() {
     return state.getStdout();
   }
diff --git a/src/test/java/com/android/tools/r8/RunExamplesAndroidOTest.java b/src/test/java/com/android/tools/r8/RunExamplesAndroidOTest.java
index c72a3ea..b7bb5d2 100644
--- a/src/test/java/com/android/tools/r8/RunExamplesAndroidOTest.java
+++ b/src/test/java/com/android/tools/r8/RunExamplesAndroidOTest.java
@@ -611,14 +611,15 @@
     Assume.assumeTrue(ToolHelper.artSupported() || ToolHelper.compareAgaintsGoldenFiles());
     boolean expectedToFail = expectedToFail(testName);
     try {
-      String output = ToolHelper.runArtNoVerificationErrors(
-          Arrays.stream(dexes).map(path -> path.toString()).collect(Collectors.toList()),
-          qualifiedMainClass,
-          builder -> {
-            for (String arg : args) {
-              builder.appendProgramArgument(arg);
-            }
-          });
+      String output =
+          ToolHelper.runArtNoVerificationErrors(
+              Arrays.stream(dexes).map(Path::toString).collect(Collectors.toList()),
+              qualifiedMainClass,
+              builder -> {
+                for (String arg : args) {
+                  builder.appendProgramArgument(arg);
+                }
+              });
       if (!expectedToFail
           && !skipRunningOnJvm(testName)
           && !ToolHelper.compareAgaintsGoldenFiles()) {
diff --git a/src/test/java/com/android/tools/r8/RunExamplesAndroidPTest.java b/src/test/java/com/android/tools/r8/RunExamplesAndroidPTest.java
index d06d976..ed69ecf 100644
--- a/src/test/java/com/android/tools/r8/RunExamplesAndroidPTest.java
+++ b/src/test/java/com/android/tools/r8/RunExamplesAndroidPTest.java
@@ -248,10 +248,11 @@
     if (expectedToFail) {
       thrown.expect(Throwable.class);
     }
-    String output = ToolHelper.runArtNoVerificationErrors(
-        Arrays.stream(dexes).map(path -> path.toString()).collect(Collectors.toList()),
-        qualifiedMainClass,
-        null);
+    String output =
+        ToolHelper.runArtNoVerificationErrors(
+            Arrays.stream(dexes).map(Path::toString).collect(Collectors.toList()),
+            qualifiedMainClass,
+            null);
     if (!expectedToFail) {
       ToolHelper.ProcessResult javaResult =
           ToolHelper.runJava(ImmutableList.copyOf(jars), qualifiedMainClass);
diff --git a/src/test/java/com/android/tools/r8/TestBase.java b/src/test/java/com/android/tools/r8/TestBase.java
index e1d4c9e..f6846b4 100644
--- a/src/test/java/com/android/tools/r8/TestBase.java
+++ b/src/test/java/com/android/tools/r8/TestBase.java
@@ -49,7 +49,6 @@
 import com.android.tools.r8.shaking.MainDexClasses;
 import com.android.tools.r8.shaking.NoStaticClassMergingRule;
 import com.android.tools.r8.shaking.NoVerticalClassMergingRule;
-import com.android.tools.r8.shaking.ProguardClassFilter;
 import com.android.tools.r8.shaking.ProguardClassNameList;
 import com.android.tools.r8.shaking.ProguardConfiguration;
 import com.android.tools.r8.shaking.ProguardConfigurationRule;
@@ -720,9 +719,9 @@
         MainDexClasses.createEmptyMainDexClasses());
   }
 
-  protected static AppView<AppInfoWithClassHierarchy> computeAppViewWithSubtyping(AndroidApp app)
-      throws Exception {
-    return computeAppViewWithSubtyping(
+  protected static AppView<AppInfoWithClassHierarchy> computeAppViewWithClassHierachy(
+      AndroidApp app) throws Exception {
+    return computeAppViewWithClassHierachy(
         app,
         factory ->
             buildConfigForRules(
@@ -730,7 +729,7 @@
                 Collections.singletonList(ProguardKeepRule.defaultKeepAllRule(unused -> {}))));
   }
 
-  private static AppView<AppInfoWithClassHierarchy> computeAppViewWithSubtyping(
+  private static AppView<AppInfoWithClassHierarchy> computeAppViewWithClassHierachy(
       AndroidApp app, Function<DexItemFactory, ProguardConfiguration> keepConfig) throws Exception {
     DexItemFactory dexItemFactory = new DexItemFactory();
     InternalOptions options = new InternalOptions(keepConfig.apply(dexItemFactory), new Reporter());
@@ -759,7 +758,7 @@
 
   protected static AppView<AppInfoWithLiveness> computeAppViewWithLiveness(
       AndroidApp app, Function<DexItemFactory, ProguardConfiguration> keepConfig) throws Exception {
-    AppView<AppInfoWithClassHierarchy> appView = computeAppViewWithSubtyping(app, keepConfig);
+    AppView<AppInfoWithClassHierarchy> appView = computeAppViewWithClassHierachy(app, keepConfig);
     // Run the tree shaker to compute an instance of AppInfoWithLiveness.
     ExecutorService executor = Executors.newSingleThreadExecutor();
     SubtypingInfo subtypingInfo = new SubtypingInfo(appView);
@@ -769,8 +768,8 @@
             .run(executor);
     appView.setRootSet(rootSet);
     AppInfoWithLiveness appInfoWithLiveness =
-        EnqueuerFactory.createForInitialTreeShaking(appView, subtypingInfo)
-            .traceApplication(rootSet, ProguardClassFilter.empty(), executor, Timing.empty());
+        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);
@@ -1702,6 +1701,10 @@
     return AndroidApiLevel.L;
   }
 
+  public static AndroidApiLevel apiLevelWithTwrCloseResourceSupport() {
+    return AndroidApiLevel.K;
+  }
+
   public static boolean canUseJavaUtilObjects(TestParameters parameters) {
     return parameters.isCfRuntime()
         || parameters.getApiLevel().isGreaterThanOrEqualTo(AndroidApiLevel.K);
diff --git a/src/test/java/com/android/tools/r8/TestBuilderMinAndroidJarTest.java b/src/test/java/com/android/tools/r8/TestBuilderMinAndroidJarTest.java
index 06cba56..d917ab6 100644
--- a/src/test/java/com/android/tools/r8/TestBuilderMinAndroidJarTest.java
+++ b/src/test/java/com/android/tools/r8/TestBuilderMinAndroidJarTest.java
@@ -28,7 +28,10 @@
 
   @Parameters(name = "{0}")
   public static TestParametersCollection data() {
-    return getTestParameters().withAllRuntimes().withAllApiLevels().build();
+    return getTestParameters()
+        .withAllRuntimes()
+        .withApiLevelsStartingAtIncluding(AndroidApiLevel.N)
+        .build();
   }
 
   public TestBuilderMinAndroidJarTest(TestParameters parameters) {
diff --git a/src/test/java/com/android/tools/r8/TestCompileResult.java b/src/test/java/com/android/tools/r8/TestCompileResult.java
index e37e48e..64c7680 100644
--- a/src/test/java/com/android/tools/r8/TestCompileResult.java
+++ b/src/test/java/com/android/tools/r8/TestCompileResult.java
@@ -38,6 +38,7 @@
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.List;
+import java.util.Set;
 import java.util.concurrent.ExecutionException;
 import java.util.function.Consumer;
 import java.util.function.Function;
@@ -90,6 +91,13 @@
     return self();
   }
 
+  public abstract Set<String> getMainDexClasses();
+
+  public final CR inspectMainDexClasses(Consumer<Set<String>> consumer) {
+    consumer.accept(getMainDexClasses());
+    return self();
+  }
+
   public abstract String getStdout();
 
   public abstract String getStderr();
diff --git a/src/test/java/com/android/tools/r8/TestCompilerBuilder.java b/src/test/java/com/android/tools/r8/TestCompilerBuilder.java
index f196151..79f00e2 100644
--- a/src/test/java/com/android/tools/r8/TestCompilerBuilder.java
+++ b/src/test/java/com/android/tools/r8/TestCompilerBuilder.java
@@ -14,6 +14,7 @@
 import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.AndroidApp;
 import com.android.tools.r8.utils.AndroidAppConsumers;
+import com.android.tools.r8.utils.DescriptorUtils;
 import com.android.tools.r8.utils.ForwardingOutputStream;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.ThrowingOutputStream;
@@ -30,10 +31,13 @@
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.List;
+import java.util.Set;
 import java.util.concurrent.ExecutionException;
 import java.util.function.Consumer;
 import java.util.function.Function;
 import java.util.function.Supplier;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
 
 public abstract class TestCompilerBuilder<
         C extends BaseCompilerCommand,
@@ -45,7 +49,9 @@
 
   public static final Consumer<InternalOptions> DEFAULT_OPTIONS =
       options -> {
-        options.testing.allowClassInlinerGracefulExit = false;
+        options.testing.allowUnusedDontWarnRules = false;
+        options.testing.allowUnnecessaryDontWarnWildcards = false;
+        options.testing.enableExperimentalMissingClassesReporting = true;
         options.testing.reportUnusedProguardConfigurationRules = true;
         options.horizontalClassMergerOptions().enable();
       };
@@ -58,6 +64,7 @@
   private boolean useDefaultRuntimeLibrary = true;
   private final List<Path> additionalRunClassPath = new ArrayList<>();
   private ProgramConsumer programConsumer;
+  private MainDexClassesCollector mainDexClassesCollector;
   private StringConsumer mainDexListConsumer;
   protected int minApiLevel = ToolHelper.getMinApiLevelForDexVm().getLevel();
   private Consumer<InternalOptions> optionsConsumer = DEFAULT_OPTIONS;
@@ -172,7 +179,13 @@
   public CR compile() throws CompilationFailedException {
     AndroidAppConsumers sink = new AndroidAppConsumers();
     builder.setProgramConsumer(sink.wrapProgramConsumer(programConsumer));
-    builder.setMainDexListConsumer(mainDexListConsumer);
+    if (mainDexClassesCollector != null || mainDexListConsumer != null) {
+      builder.setMainDexListConsumer(
+          ChainedStringConsumer.builder()
+              .addIfNotNull(mainDexClassesCollector)
+              .addIfNotNull(mainDexListConsumer)
+              .build());
+    }
     if (backend.isDex() || !isTestShrinkerBuilder()) {
       assert !builder.isMinApiLevelSet()
           : "Don't set the API level directly through BaseCompilerCommand.Builder in tests";
@@ -188,6 +201,7 @@
         builder.addLibraryFiles(TestBase.runtimeJar(backend));
       }
     }
+    List<String> mainDexClasses = null;
     assertNull(oldStdout);
     oldStdout = System.out;
     assertNull(oldStderr);
@@ -220,6 +234,9 @@
       }
       return cr;
     } finally {
+      if (mainDexClassesCollector != null) {
+        getState().setMainDexClasses(mainDexClassesCollector.getMainDexClasses());
+      }
       if (stdout != null) {
         getState().setStdout(stdout.toString());
       }
@@ -372,6 +389,12 @@
     return self();
   }
 
+  public T collectMainDexClasses() {
+    assert mainDexClassesCollector == null;
+    mainDexClassesCollector = new MainDexClassesCollector();
+    return self();
+  }
+
   public T setMainDexListConsumer(StringConsumer consumer) {
     assert consumer != null;
     this.mainDexListConsumer = consumer;
@@ -467,4 +490,76 @@
     builder.addAssertionsConfiguration(assertionsConfigurationGenerator);
     return self();
   }
+
+  private static class ChainedStringConsumer implements StringConsumer {
+
+    private final List<StringConsumer> consumers;
+
+    ChainedStringConsumer(List<StringConsumer> consumers) {
+      this.consumers = consumers;
+    }
+
+    static Builder builder() {
+      return new Builder();
+    }
+
+    @Override
+    public void accept(String string, DiagnosticsHandler handler) {
+      consumers.forEach(consumer -> consumer.accept(string, handler));
+    }
+
+    @Override
+    public void finished(DiagnosticsHandler handler) {
+      consumers.forEach(consumer -> consumer.finished(handler));
+    }
+
+    static class Builder {
+
+      private final List<StringConsumer> consumers = new ArrayList<>();
+
+      Builder add(StringConsumer consumer) {
+        assert consumer != null;
+        consumers.add(consumer);
+        return this;
+      }
+
+      Builder addIfNotNull(StringConsumer consumer) {
+        return consumer != null ? add(consumer) : this;
+      }
+
+      ChainedStringConsumer build() {
+        return new ChainedStringConsumer(consumers);
+      }
+    }
+  }
+
+  private static class MainDexClassesCollector implements StringConsumer {
+
+    private StringBuilder builder = new StringBuilder();
+    private Set<String> mainDexClasses;
+
+    public Set<String> getMainDexClasses() {
+      assert mainDexClasses != null;
+      return mainDexClasses;
+    }
+
+    @Override
+    public void accept(String string, DiagnosticsHandler handler) {
+      builder.append(string);
+    }
+
+    @Override
+    public void finished(DiagnosticsHandler handler) {
+      mainDexClasses =
+          Stream.of(builder.toString().split(System.lineSeparator()))
+              .map(
+                  line -> {
+                    assert line.endsWith(".class");
+                    return line.substring(0, line.length() - ".class".length());
+                  })
+              .map(DescriptorUtils::getJavaTypeFromBinaryName)
+              .collect(Collectors.toSet());
+      builder = null;
+    }
+  }
 }
diff --git a/src/test/java/com/android/tools/r8/TestDiagnosticMessages.java b/src/test/java/com/android/tools/r8/TestDiagnosticMessages.java
index 72a22e6..4f8713f 100644
--- a/src/test/java/com/android/tools/r8/TestDiagnosticMessages.java
+++ b/src/test/java/com/android/tools/r8/TestDiagnosticMessages.java
@@ -7,6 +7,7 @@
 import static com.android.tools.r8.DiagnosticsMatcher.diagnosticMessage;
 import static org.hamcrest.CoreMatchers.not;
 
+import java.util.Arrays;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.List;
@@ -64,6 +65,11 @@
     return assertWarningsMatch(Collections.singletonList(matcher));
   }
 
+  @SuppressWarnings("unchecked")
+  default TestDiagnosticMessages assertWarningsMatch(Matcher<Diagnostic>... matchers) {
+    return assertWarningsMatch(Arrays.asList(matchers));
+  }
+
   TestDiagnosticMessages assertWarningsMatch(Collection<Matcher<Diagnostic>> matchers);
 
   default TestDiagnosticMessages assertErrorsMatch(Matcher<Diagnostic> matcher) {
diff --git a/src/test/java/com/android/tools/r8/TestDiagnosticMessagesImpl.java b/src/test/java/com/android/tools/r8/TestDiagnosticMessagesImpl.java
index 3935988..f1b6e73 100644
--- a/src/test/java/com/android/tools/r8/TestDiagnosticMessagesImpl.java
+++ b/src/test/java/com/android/tools/r8/TestDiagnosticMessagesImpl.java
@@ -92,7 +92,7 @@
         "Expected no "
             + type
             + " messages, got:\n"
-            + String.join("\n", ListUtils.map(messages, m -> m.getDiagnosticMessage())),
+            + String.join("\n", ListUtils.map(messages, Diagnostic::getDiagnosticMessage)),
         0,
         messages.size());
   }
diff --git a/src/test/java/com/android/tools/r8/TestParametersBuilder.java b/src/test/java/com/android/tools/r8/TestParametersBuilder.java
index 9b84425..c5811f9 100644
--- a/src/test/java/com/android/tools/r8/TestParametersBuilder.java
+++ b/src/test/java/com/android/tools/r8/TestParametersBuilder.java
@@ -75,12 +75,12 @@
 
   /** Add all available CF runtimes starting from and including {@param startInclusive}. */
   public TestParametersBuilder withCfRuntimesStartingFromIncluding(CfVm startInclusive) {
-    return withCfRuntimeFilter(vm -> startInclusive.lessThanOrEqual(vm));
+    return withCfRuntimeFilter(startInclusive::lessThanOrEqual);
   }
 
   /** Add all available CF runtimes starting from and excluding {@param startExcluding}. */
   public TestParametersBuilder withCfRuntimesStartingFromExcluding(CfVm startExcluding) {
-    return withCfRuntimeFilter(vm -> startExcluding.lessThan(vm));
+    return withCfRuntimeFilter(startExcluding::lessThan);
   }
 
   /** Add all available CF runtimes ending at and including {@param endInclusive}. */
@@ -115,9 +115,14 @@
     return withDexRuntimesStartingFromIncluding(DexVm.Version.V5_1_1);
   }
 
+  /** Add all available DEX runtimes that do not support native multidex. */
+  public TestParametersBuilder withMainDexRuntimes() {
+    return withDexRuntimesEndingAtExcluding(DexVm.Version.V5_1_1);
+  }
+
   /** Add all available DEX runtimes starting from and including {@param startInclusive}. */
   public TestParametersBuilder withDexRuntimesStartingFromIncluding(DexVm.Version startInclusive) {
-    return withDexRuntimeFilter(vm -> startInclusive.isOlderThanOrEqual(vm));
+    return withDexRuntimeFilter(startInclusive::isOlderThanOrEqual);
   }
 
   /** Add all available DEX runtimes starting from and excluding {@param startExcluding}. */
diff --git a/src/test/java/com/android/tools/r8/TestParametersCollection.java b/src/test/java/com/android/tools/r8/TestParametersCollection.java
index 52bc8d5..8351a9c 100644
--- a/src/test/java/com/android/tools/r8/TestParametersCollection.java
+++ b/src/test/java/com/android/tools/r8/TestParametersCollection.java
@@ -6,7 +6,6 @@
 import java.util.Collection;
 import java.util.Iterator;
 import java.util.stream.Stream;
-import org.jetbrains.annotations.NotNull;
 
 public class TestParametersCollection implements Iterable<TestParameters> {
 
@@ -17,7 +16,6 @@
     this.parameters = parameters;
   }
 
-  @NotNull
   @Override
   public Iterator<TestParameters> iterator() {
     return parameters.iterator();
diff --git a/src/test/java/com/android/tools/r8/TestRunResult.java b/src/test/java/com/android/tools/r8/TestRunResult.java
index c775215..4a7d5c7 100644
--- a/src/test/java/com/android/tools/r8/TestRunResult.java
+++ b/src/test/java/com/android/tools/r8/TestRunResult.java
@@ -66,8 +66,12 @@
   }
 
   public RR assertSuccessWithOutputLinesIf(boolean condition, String... expected) {
+    return assertSuccessWithOutputLinesIf(condition, Arrays.asList(expected));
+  }
+
+  public RR assertSuccessWithOutputLinesIf(boolean condition, List<String> expected) {
     if (condition) {
-      return assertSuccessWithOutputLines(Arrays.asList(expected));
+      return assertSuccessWithOutputLines(expected);
     }
     return self();
   }
diff --git a/src/test/java/com/android/tools/r8/TestShrinkerBuilder.java b/src/test/java/com/android/tools/r8/TestShrinkerBuilder.java
index 1bcbc55..b28a51f 100644
--- a/src/test/java/com/android/tools/r8/TestShrinkerBuilder.java
+++ b/src/test/java/com/android/tools/r8/TestShrinkerBuilder.java
@@ -4,6 +4,8 @@
 
 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;
 import com.android.tools.r8.references.MethodReference;
@@ -40,6 +42,10 @@
     super(state, builder, backend);
   }
 
+  public boolean isProguardTestBuilder() {
+    return false;
+  }
+
   @Override
   public boolean isTestShrinkerBuilder() {
     return true;
@@ -90,7 +96,7 @@
 
   public abstract T addKeepRuleFiles(List<Path> files);
 
-  public T addKeepRuleFiles(Path... files) throws IOException {
+  public T addKeepRuleFiles(Path... files) {
     return addKeepRuleFiles(Arrays.asList(files));
   }
 
@@ -101,7 +107,150 @@
   }
 
   public T addDontWarn(Class<?> clazz) {
-    return addKeepRules("-dontwarn " + clazz.getTypeName());
+    return addDontWarn(clazz.getTypeName());
+  }
+
+  public T addDontWarn(String className) {
+    return addKeepRules("-dontwarn " + className);
+  }
+
+  public T addDontWarn(String... classes) {
+    for (String clazz : classes) {
+      addKeepRules("-dontwarn " + clazz);
+    }
+    return self();
+  }
+
+  @Deprecated
+  public T addDontWarnCompanionClass(Class<?> clazz) {
+    return addDontWarn(clazz.getTypeName() + COMPANION_CLASS_NAME_SUFFIX);
+  }
+
+  @Deprecated
+  public T addDontWarnCompanionClasses() {
+    return addDontWarn("**" + COMPANION_CLASS_NAME_SUFFIX);
+  }
+
+  @Deprecated
+  public T addDontWarnCompilerSynthesizedAnnotations() {
+    return addDontWarnCompilerSynthesizedClassAnnotation()
+        .addDontWarnCompilerSynthesizedClassMapAnnotation();
+  }
+
+  @Deprecated
+  public T addDontWarnCompilerSynthesizedClassAnnotation() {
+    return addDontWarn("com.android.tools.r8.annotations.SynthesizedClass");
+  }
+
+  @Deprecated
+  public T addDontWarnCompilerSynthesizedClassMapAnnotation() {
+    return addDontWarn("com.android.tools.r8.annotations.SynthesizedClassMap");
+  }
+
+  // TODO(b/176143558): Should not report missing classes for compiler synthesized classes.
+  @Deprecated
+  public T addDontWarnEmulatedLibraryClass(Class<?> clazz) {
+    return addDontWarn(clazz.getTypeName() + "$-EL");
+  }
+
+  // TODO(b/176143558): Should not report missing classes for compiler synthesized classes.
+  @Deprecated
+  public T addDontWarnEmulatedLibraryClasses() {
+    return addDontWarn("**$-EL");
+  }
+
+  public T addDontWarnGoogle() {
+    return addDontWarn("com.google.**");
+  }
+
+  public T addDontWarnJavax() {
+    return addDontWarn("javax.**");
+  }
+
+  public T addDontWarnJavaxNullableAnnotation() {
+    return addDontWarn("javax.annotation.Nullable");
+  }
+
+  public T addDontWarnJavaLangInvoke() {
+    return addDontWarn("java.lang.invoke.*");
+  }
+
+  public T addDontWarnJavaLangInvoke(String className) {
+    return addDontWarn("java.lang.invoke." + className);
+  }
+
+  public T addDontWarnJavaNioFile() {
+    return addDontWarn("java.nio.file.**");
+  }
+
+  // TODO(b/176133676): Investigate why there are missing class references to org.jetbrains
+  @Deprecated
+  public T addDontWarnJetBrains() {
+    return addDontWarn("org.jetbrains.**");
+  }
+
+  public T addDontWarnJetBrainsAnnotations() {
+    return addDontWarnJetBrainsNotNullAnnotation().addDontWarnJetBrainsNullableAnnotation();
+  }
+
+  public T addDontWarnJetBrainsNotNullAnnotation() {
+    return addDontWarn("org.jetbrains.annotations.NotNull");
+  }
+
+  public T addDontWarnJetBrainsNullableAnnotation() {
+    return addDontWarn("org.jetbrains.annotations.Nullable");
+  }
+
+  // TODO(b/176133676): Should not report missing classes for Kotlin classes.
+  @Deprecated
+  public T addDontWarnKotlin() {
+    return addDontWarn("kotlin.**");
+  }
+
+  // TODO(b/176133676): Should not report missing classes for Kotlin metadata.
+  @Deprecated
+  public T addDontWarnKotlinMetadata() {
+    return addDontWarn("kotlin.Metadata");
+  }
+
+  // TODO(b/176133676): Investigate kotlinx missing class references.
+  @Deprecated
+  public T addDontWarnKotlinx() {
+    return addDontWarn("kotlinx.**");
+  }
+
+  // TODO(b/176144018): Should not report compiler synthesized references as missing.
+  @Deprecated
+  public T addDontWarnRetargetLibraryMembers() {
+    return addDontWarn("j$.retarget.$r8$retargetLibraryMember**");
+  }
+
+  @Deprecated
+  public T addDontWarnRetargetLibraryMember(String suffix) {
+    return addDontWarn("j$.retarget.$r8$retargetLibraryMember$" + suffix);
+  }
+
+  // TODO(b/154849103): Should not warn about SerializedLambda.
+  @Deprecated
+  public T addDontWarnSerializedLambda() {
+    return addDontWarn("java.lang.invoke.SerializedLambda");
+  }
+
+  // TODO(b/176781593): Should not be reported missing.
+  @Deprecated
+  public T addDontWarnTimeConversions() {
+    return addDontWarn("java.time.TimeConversions");
+  }
+
+  // TODO(b/176144018): Should not report compiler synthesized references as missing.
+  @Deprecated
+  public T addDontWarnVivifiedClass(Class<?> clazz) {
+    return addDontWarn("$-vivified-$." + clazz.getTypeName());
+  }
+
+  @Deprecated
+  public T addDontWarnVivifiedClasses() {
+    return addDontWarn("$-vivified-$.**");
   }
 
   public T addKeepKotlinMetadata() {
@@ -327,6 +476,10 @@
     return addTestingAnnotation(NeverInline.class);
   }
 
+  public final T addKeepAnnotation() {
+    return addTestingAnnotation(Keep.class);
+  }
+
   public final T addMemberValuePropagationAnnotations() {
     return addTestingAnnotation(NeverPropagateValue.class);
   }
diff --git a/src/test/java/com/android/tools/r8/TestState.java b/src/test/java/com/android/tools/r8/TestState.java
index 3e28699..a30033b 100644
--- a/src/test/java/com/android/tools/r8/TestState.java
+++ b/src/test/java/com/android/tools/r8/TestState.java
@@ -5,6 +5,7 @@
 
 import java.io.IOException;
 import java.nio.file.Path;
+import java.util.Set;
 import java.util.function.BiFunction;
 import org.junit.rules.TemporaryFolder;
 
@@ -13,6 +14,8 @@
   private final TemporaryFolder temp;
   private final TestDiagnosticMessagesImpl messages = new TestDiagnosticMessagesImpl();
 
+  private Set<String> mainDexClasses;
+
   private String stdout;
   private String stderr;
 
@@ -40,6 +43,14 @@
     return messages;
   }
 
+  public Set<String> getMainDexClasses() {
+    return mainDexClasses;
+  }
+
+  void setMainDexClasses(Set<String> mainDexClasses) {
+    this.mainDexClasses = mainDexClasses;
+  }
+
   public String getStdout() {
     return stdout;
   }
diff --git a/src/test/java/com/android/tools/r8/ToolHelper.java b/src/test/java/com/android/tools/r8/ToolHelper.java
index a85ae6a..08664a8 100644
--- a/src/test/java/com/android/tools/r8/ToolHelper.java
+++ b/src/test/java/com/android/tools/r8/ToolHelper.java
@@ -510,7 +510,7 @@
   }
 
   private static List<File> toFileList(List<String> filePathList) {
-    return filePathList.stream().map(path -> new File(path)).collect(Collectors.toList());
+    return filePathList.stream().map(File::new).collect(Collectors.toList());
   }
 
   public static class DXCommandBuilder extends CommandBuilder {
@@ -1541,7 +1541,7 @@
       assert goldenFileDir.isDirectory();
       processResult =
           compareAgainstGoldenFiles(
-              files.stream().map(f -> new File(f)).collect(Collectors.toList()), goldenFileDir);
+              files.stream().map(File::new).collect(Collectors.toList()), goldenFileDir);
       if (processResult.exitCode == 0) {
         processResult = readProcessResult(goldenFileDir);
       }
@@ -1553,8 +1553,7 @@
     if (goldenFilesDirToProp != null) {
       File goldenFileDir = new File(goldenFilesDirToProp);
       assert goldenFileDir.isDirectory();
-      storeAsGoldenFiles(
-          files.stream().map(f -> new File(f)).collect(Collectors.toList()), goldenFileDir);
+      storeAsGoldenFiles(files.stream().map(File::new).collect(Collectors.toList()), goldenFileDir);
       storeProcessResult(processResult, goldenFileDir);
     }
 
diff --git a/src/test/java/com/android/tools/r8/accessrelaxation/AccessRelaxationTestBase.java b/src/test/java/com/android/tools/r8/accessrelaxation/AccessRelaxationTestBase.java
index ce40a93..1127d82 100644
--- a/src/test/java/com/android/tools/r8/accessrelaxation/AccessRelaxationTestBase.java
+++ b/src/test/java/com/android/tools/r8/accessrelaxation/AccessRelaxationTestBase.java
@@ -23,7 +23,7 @@
     this.parameters = parameters;
   }
 
-  static void assertPublic(CodeInspector codeInspector, Class clazz, MethodSignature signature) {
+  static void assertPublic(CodeInspector codeInspector, Class<?> clazz, MethodSignature signature) {
     ClassSubject classSubject = codeInspector.clazz(clazz);
     assertThat(classSubject, isPresent());
     MethodSubject methodSubject = classSubject.method(signature);
@@ -31,12 +31,12 @@
     assertThat(methodSubject, isPublic());
   }
 
-  static void assertNotPublic(CodeInspector codeInspector, Class clazz, MethodSignature signature) {
+  static void assertNotPublic(
+      CodeInspector codeInspector, Class<?> clazz, MethodSignature signature) {
     ClassSubject classSubject = codeInspector.clazz(clazz);
     assertThat(classSubject, isPresent());
     MethodSubject methodSubject = classSubject.method(signature);
     assertThat(methodSubject, isPresent());
     assertThat(methodSubject, not(isPublic()));
   }
-
 }
diff --git a/src/test/java/com/android/tools/r8/accessrelaxation/NoRelaxationForSerializableTest.java b/src/test/java/com/android/tools/r8/accessrelaxation/NoRelaxationForSerializableTest.java
index 83a0155..339361b 100644
--- a/src/test/java/com/android/tools/r8/accessrelaxation/NoRelaxationForSerializableTest.java
+++ b/src/test/java/com/android/tools/r8/accessrelaxation/NoRelaxationForSerializableTest.java
@@ -13,6 +13,7 @@
 import com.android.tools.r8.R8TestCompileResult;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.naming.MemberNaming.MethodSignature;
+import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.BooleanUtils;
 import com.android.tools.r8.utils.FileUtils;
 import com.android.tools.r8.utils.StringUtils;
@@ -103,7 +104,11 @@
   @Parameterized.Parameters(name = "{0}, access-modification: {1}")
   public static List<Object[]> data() {
     return buildParameters(
-        getTestParameters().withAllRuntimesAndApiLevels().build(), BooleanUtils.values());
+        getTestParameters()
+            .withAllRuntimes()
+            .withApiLevelsStartingAtIncluding(AndroidApiLevel.K)
+            .build(),
+        BooleanUtils.values());
   }
 
   public NoRelaxationForSerializableTest(TestParameters parameters, boolean accessModification) {
@@ -141,6 +146,8 @@
     R8TestCompileResult result =
         testForR8(parameters.getBackend())
             .addProgramClasses(CLASSES)
+            .addMemberValuePropagationAnnotations()
+            .addNoVerticalClassMergingAnnotations()
             .enableInliningAnnotations()
             .addKeepRuleFiles(configuration)
             .addKeepRules(KEEPMEMBER_RULES)
@@ -174,6 +181,7 @@
     R8TestCompileResult result =
         testForR8(parameters.getBackend())
             .addProgramClasses(CLASSES)
+            .addNoVerticalClassMergingAnnotations()
             .enableInliningAnnotations()
             .enableMemberValuePropagationAnnotations()
             .addKeepRuleFiles(configuration)
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 607f96d..98421ae 100644
--- a/src/test/java/com/android/tools/r8/annotations/SourceDebugExtensionTest.java
+++ b/src/test/java/com/android/tools/r8/annotations/SourceDebugExtensionTest.java
@@ -66,6 +66,7 @@
         .addProgramFiles(kotlinSources)
         .addKeepAttributes(ProguardKeepAttributes.SOURCE_DEBUG_EXTENSION)
         .addKeepAllClassesRule()
+        .addDontWarnJetBrainsNotNullAnnotation()
         .setMode(CompilationMode.RELEASE)
         .setMinApi(parameters.getApiLevel())
         .compile()
diff --git a/src/test/java/com/android/tools/r8/cf/KeepDeserializeLambdaMethodTestRunner.java b/src/test/java/com/android/tools/r8/cf/KeepDeserializeLambdaMethodTestRunner.java
index 8bb8e36..902d503 100644
--- a/src/test/java/com/android/tools/r8/cf/KeepDeserializeLambdaMethodTestRunner.java
+++ b/src/test/java/com/android/tools/r8/cf/KeepDeserializeLambdaMethodTestRunner.java
@@ -11,6 +11,7 @@
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.TestShrinkerBuilder;
 import com.android.tools.r8.utils.StringUtils;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.android.tools.r8.utils.codeinspector.MethodSubject;
@@ -26,9 +27,9 @@
 @RunWith(Parameterized.class)
 public class KeepDeserializeLambdaMethodTestRunner extends TestBase {
 
-  private static final Class TEST_CLASS_CF =
+  private static final Class<?> TEST_CLASS_CF =
       com.android.tools.r8.cf.KeepDeserializeLambdaMethodTestCf.class;
-  private static final Class TEST_CLASS_DEX =
+  private static final Class<?> TEST_CLASS_DEX =
       com.android.tools.r8.cf.KeepDeserializeLambdaMethodTestDex.class;
 
   private static final String EXPECTED =
@@ -89,8 +90,10 @@
           // TODO(b/148836254): Support deserialized lambdas without the need of additional rules.
           "-keep class * { private static synthetic void lambda$*(); }");
     } else {
-      builder.noMinification();
-      builder.noTreeShaking();
+      builder
+          .applyIf(parameters.isDexRuntime(), TestShrinkerBuilder::addDontWarnSerializedLambda)
+          .noMinification()
+          .noTreeShaking();
     }
     builder
         .run(parameters.getRuntime(), getMainClass())
diff --git a/src/test/java/com/android/tools/r8/cf/MissingClassJoinsToObjectTest.java b/src/test/java/com/android/tools/r8/cf/MissingClassJoinsToObjectTest.java
index 81f1d09..43d02cb 100644
--- a/src/test/java/com/android/tools/r8/cf/MissingClassJoinsToObjectTest.java
+++ b/src/test/java/com/android/tools/r8/cf/MissingClassJoinsToObjectTest.java
@@ -51,6 +51,7 @@
             .enableInliningAnnotations()
             .addProgramClasses(TestClass.class, A.class)
             .addKeepMainRule(TestClass.class)
+            .addDontWarn(B.class)
             .setMinApi(parameters.getApiLevel())
             .compile()
             .addRunClasspathFiles(getRuntimeClasspath())
diff --git a/src/test/java/com/android/tools/r8/cf/PrintSeedsWithDeserializeLambdaMethodTest.java b/src/test/java/com/android/tools/r8/cf/PrintSeedsWithDeserializeLambdaMethodTest.java
index 497a1bf..ee7ff0f 100644
--- a/src/test/java/com/android/tools/r8/cf/PrintSeedsWithDeserializeLambdaMethodTest.java
+++ b/src/test/java/com/android/tools/r8/cf/PrintSeedsWithDeserializeLambdaMethodTest.java
@@ -9,6 +9,7 @@
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.TestShrinkerBuilder;
 import com.android.tools.r8.utils.StringUtils;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.android.tools.r8.utils.codeinspector.MethodSubject;
@@ -53,6 +54,7 @@
         .addProgramClasses(getClasses())
         .setMinApi(parameters.getApiLevel())
         .addKeepMainRule(getMainClass())
+        .applyIf(parameters.isDexRuntime(), TestShrinkerBuilder::addDontWarnSerializedLambda)
         .addPrintSeeds()
         .allowStdoutMessages()
         .noMinification()
diff --git a/src/test/java/com/android/tools/r8/cf/SillyBlockOrderTest.java b/src/test/java/com/android/tools/r8/cf/SillyBlockOrderTest.java
index 4484519..d1e4ef7 100644
--- a/src/test/java/com/android/tools/r8/cf/SillyBlockOrderTest.java
+++ b/src/test/java/com/android/tools/r8/cf/SillyBlockOrderTest.java
@@ -10,6 +10,7 @@
 import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.utils.StringUtils;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.InstructionSubject;
 import com.android.tools.r8.utils.codeinspector.MethodSubject;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -53,7 +54,8 @@
     try {
       MethodSubject method =
           inspector.method(TestClass.class.getMethod("doubleConditional", boolean.class));
-      assertEquals(expected, method.streamInstructions().filter(i -> i.isGoto()).count());
+      assertEquals(
+          expected, method.streamInstructions().filter(InstructionSubject::isGoto).count());
     } catch (NoSuchMethodException e) {
       throw new RuntimeException(e);
     }
diff --git a/src/test/java/com/android/tools/r8/cf/UnusedMultiNewArrayTest.java b/src/test/java/com/android/tools/r8/cf/UnusedMultiNewArrayTest.java
index 5b9ad45..0820b25 100644
--- a/src/test/java/com/android/tools/r8/cf/UnusedMultiNewArrayTest.java
+++ b/src/test/java/com/android/tools/r8/cf/UnusedMultiNewArrayTest.java
@@ -20,7 +20,7 @@
 
   @Parameters(name = "{0}")
   public static TestParametersCollection data() {
-    return getTestParameters().withAllRuntimes().build();
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
   }
 
   public UnusedMultiNewArrayTest(TestParameters parameters) {
@@ -32,7 +32,8 @@
     testForR8(parameters.getBackend())
         .addProgramClasses(TestClass.class)
         .addKeepMainRule(TestClass.class)
-        .setMinApi(parameters.getRuntime())
+        .addDontWarn(A.class)
+        .setMinApi(parameters.getApiLevel())
         .compile();
   }
 
diff --git a/src/test/java/com/android/tools/r8/checkdiscarded/CheckDiscardModifyDiagnosticsLevelTest.java b/src/test/java/com/android/tools/r8/checkdiscarded/CheckDiscardModifyDiagnosticsLevelTest.java
index bac9468..ffe8230 100644
--- a/src/test/java/com/android/tools/r8/checkdiscarded/CheckDiscardModifyDiagnosticsLevelTest.java
+++ b/src/test/java/com/android/tools/r8/checkdiscarded/CheckDiscardModifyDiagnosticsLevelTest.java
@@ -18,6 +18,8 @@
 import com.android.tools.r8.checkdiscarded.testclasses.Main;
 import com.android.tools.r8.checkdiscarded.testclasses.UnusedClass;
 import com.android.tools.r8.checkdiscarded.testclasses.UsedClass;
+import com.android.tools.r8.checkdiscarded.testclasses.WillBeGone;
+import com.android.tools.r8.checkdiscarded.testclasses.WillStay;
 import com.android.tools.r8.errors.CheckDiscardDiagnostic;
 import com.android.tools.r8.utils.InternalOptions;
 import com.google.common.collect.ImmutableList;
@@ -79,7 +81,8 @@
   public void dontFailCompilationOnCheckDiscardedFailure() {
     try {
       testForR8(Backend.DEX)
-          .addProgramClasses(UnusedClass.class, UsedClass.class, Main.class)
+          .addProgramClasses(
+              UnusedClass.class, UsedClass.class, Main.class, WillBeGone.class, WillStay.class)
           .addKeepMainRule(Main.class)
           .addKeepRules("-checkdiscard class " + UsedClass.class.getTypeName())
           .addOptionsModification(this::noInlining)
diff --git a/src/test/java/com/android/tools/r8/checkdiscarded/CheckDiscardedTest.java b/src/test/java/com/android/tools/r8/checkdiscarded/CheckDiscardedTest.java
index 61f8917..da2e4cb 100644
--- a/src/test/java/com/android/tools/r8/checkdiscarded/CheckDiscardedTest.java
+++ b/src/test/java/com/android/tools/r8/checkdiscarded/CheckDiscardedTest.java
@@ -46,7 +46,7 @@
   }
 
   private void compile(
-      Class annotation,
+      Class<?> annotation,
       boolean checkMembers,
       Consumer<TestDiagnosticMessages> onCompilationFailure) {
     R8FullTestBuilder builder = testForR8(Backend.DEX);
@@ -54,7 +54,8 @@
     try {
       R8TestCompileResult result =
           builder
-              .addProgramClasses(UnusedClass.class, UsedClass.class, Main.class)
+              .addProgramClasses(
+                  UnusedClass.class, UsedClass.class, Main.class, WillBeGone.class, WillStay.class)
               .addKeepMainRule(Main.class)
               .addKeepRules(checkDiscardRule(checkMembers, annotation))
               .minification(minify)
@@ -122,5 +123,4 @@
                             containsString("is invoked from"))));
     compile(WillStay.class, true, check);
   }
-
 }
diff --git a/src/test/java/com/android/tools/r8/classlookup/LibraryClassExtendsProgramClassTest.java b/src/test/java/com/android/tools/r8/classlookup/LibraryClassExtendsProgramClassTest.java
index 363008d..1b68052 100644
--- a/src/test/java/com/android/tools/r8/classlookup/LibraryClassExtendsProgramClassTest.java
+++ b/src/test/java/com/android/tools/r8/classlookup/LibraryClassExtendsProgramClassTest.java
@@ -195,9 +195,11 @@
   public void testWithDontWarn() throws Exception {
     testForR8(parameters.getBackend())
         .setMinApi(parameters.getApiLevel())
+        .addProgramClasses(TestClass.class)
         .addProgramClassFileData(junitClasses)
         .addKeepAllClassesRule()
-        .addKeepRules("-dontwarn android.test.**")
+        .applyIf(
+            libraryContainsJUnit(), builder -> builder.addKeepRules("-dontwarn android.test.**"))
         .addOptionsModification(options -> options.lookupLibraryBeforeProgram = false)
         .compile()
         .assertNoMessages();
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/AdaptVerticallyMergedResourceFileContentsTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/AdaptVerticallyMergedResourceFileContentsTest.java
index 0ebbb9d..090cb9e 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/AdaptVerticallyMergedResourceFileContentsTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/AdaptVerticallyMergedResourceFileContentsTest.java
@@ -12,7 +12,6 @@
 
 import com.android.tools.r8.DataEntryResource;
 import com.android.tools.r8.NeverClassInline;
-import com.android.tools.r8.NeverInline;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.utils.DataResourceConsumerForTesting;
@@ -51,9 +50,11 @@
             .addHorizontallyMergedClassesInspectorIf(
                 enableHorizontalClassMerging,
                 inspector -> inspector.assertMergedInto(B.class, A.class))
+            .addVerticallyMergedClassesInspector(
+                inspector -> inspector.assertMergedIntoSubtype(Parent.class))
             .compile()
             .run(parameters.getRuntime(), Main.class)
-            .assertSuccessWithOutputLines("a", "foo parent", "b")
+            .assertSuccessWithOutputLines("a", "b")
             .inspector();
 
     assertThat(codeInspector.clazz(Parent.class), not(isPresent()));
@@ -72,13 +73,7 @@
         ImmutableList.of(aClassSubject.getFinalName(), aClassSubject.getFinalName(), newClassName));
   }
 
-  @NeverClassInline
-  public static class Parent {
-    @NeverInline
-    public void foo() {
-      System.out.println("foo parent");
-    }
-  }
+  public static class Parent {}
 
   @NeverClassInline
   public static class A extends Parent {
@@ -95,13 +90,8 @@
   }
 
   public static class Main {
-    @NeverInline
-    public static void parent(Parent p) {
-      p.foo();
-    }
-
     public static void main(String[] args) {
-      parent(new A());
+      new A();
       new B();
     }
   }
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/ClassesWithNativeMethodsTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/ClassesWithNativeMethodsTest.java
index 1f0e3e6..04bb9d7 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/ClassesWithNativeMethodsTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/ClassesWithNativeMethodsTest.java
@@ -12,6 +12,7 @@
 import com.android.tools.r8.NeverClassInline;
 import com.android.tools.r8.NeverInline;
 import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.utils.codeinspector.HorizontallyMergedClassesInspector;
 import org.junit.Test;
 
 public class ClassesWithNativeMethodsTest extends HorizontalClassMergingTestBase {
@@ -31,7 +32,8 @@
         .enableInliningAnnotations()
         .enableNeverClassInliningAnnotations()
         .setMinApi(parameters.getApiLevel())
-        .addHorizontallyMergedClassesInspector(inspector -> inspector.assertNoClassesMerged())
+        .addHorizontallyMergedClassesInspector(
+            HorizontallyMergedClassesInspector::assertNoClassesMerged)
         .run(parameters.getRuntime(), Main.class)
         .assertFailureWithErrorThatMatches(
             allOf(
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/ConstructorCantInlineTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/ConstructorCantInlineTest.java
index 935e75e..369ebfd 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/ConstructorCantInlineTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/ConstructorCantInlineTest.java
@@ -50,7 +50,6 @@
     }
   }
 
-  @NoHorizontalClassMerging
   @NeverClassInline
   public static class B extends A {}
 
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/JavaLambdaMergingTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/JavaLambdaMergingTest.java
index adabb3e..63c1f18 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/JavaLambdaMergingTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/JavaLambdaMergingTest.java
@@ -4,16 +4,18 @@
 
 package com.android.tools.r8.classmerging.horizontal;
 
-import static com.android.tools.r8.ir.desugar.LambdaRewriter.LAMBDA_CLASS_NAME_PREFIX;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.references.Reference;
+import com.android.tools.r8.synthesis.SyntheticItemsTestUtils;
 import com.android.tools.r8.utils.codeinspector.VerticallyMergedClassesInspector;
 import java.util.Set;
 import java.util.stream.Collectors;
+import org.junit.Ignore;
 import org.junit.Test;
 
 public class JavaLambdaMergingTest extends HorizontalClassMergingTestBase {
@@ -23,6 +25,7 @@
   }
 
   @Test
+  @Ignore("b/174809311): Test does not work with hygienic lambdas. Rewrite or remove")
   public void test() throws Exception {
     testForR8(parameters.getBackend())
         .addInnerClasses(getClass())
@@ -38,16 +41,12 @@
             inspector -> {
               Set<DexType> lambdaSources =
                   inspector.getSources().stream()
-                      .filter(x -> x.toSourceString().contains(LAMBDA_CLASS_NAME_PREFIX))
+                      .filter(JavaLambdaMergingTest::isLambda)
                       .collect(Collectors.toSet());
               assertEquals(3, lambdaSources.size());
               DexType firstTarget = inspector.getTarget(lambdaSources.iterator().next());
               for (DexType lambdaSource : lambdaSources) {
-                assertTrue(
-                    inspector
-                        .getTarget(lambdaSource)
-                        .toSourceString()
-                        .contains(LAMBDA_CLASS_NAME_PREFIX));
+                assertTrue(isLambda(inspector.getTarget(lambdaSource)));
                 assertEquals(firstTarget, inspector.getTarget(lambdaSource));
               }
             })
@@ -59,6 +58,11 @@
         .assertSuccessWithOutputLines("Hello world!");
   }
 
+  private static boolean isLambda(DexType type) {
+    return SyntheticItemsTestUtils.isExternalLambda(
+        Reference.classFromDescriptor(type.toDescriptorString()));
+  }
+
   public static class Main {
 
     public static void main(String[] args) {
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/MergingProducesFieldCollisionTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/MergingProducesFieldCollisionTest.java
index 34972f6..13cc54f 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/MergingProducesFieldCollisionTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/MergingProducesFieldCollisionTest.java
@@ -59,7 +59,6 @@
             });
   }
 
-  @NoVerticalClassMerging
   public static class Parent {
     @NeverInline
     public void foo() {
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/NonReboundFieldAccessOnMergedClassTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/NonReboundFieldAccessOnMergedClassTest.java
index 671f740..ce0425e 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/NonReboundFieldAccessOnMergedClassTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/NonReboundFieldAccessOnMergedClassTest.java
@@ -35,6 +35,7 @@
               }
             })
         .enableNeverClassInliningAnnotations()
+        .enableNoVerticalClassMergingAnnotations()
         .setMinApi(parameters.getApiLevel())
         .compile()
         .run(parameters.getRuntime(), Main.class)
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/VerticallyMergedClassDistinguishedByCheckCastTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/VerticallyMergedClassDistinguishedByCheckCastTest.java
index 366800a..eba6de8 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/VerticallyMergedClassDistinguishedByCheckCastTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/VerticallyMergedClassDistinguishedByCheckCastTest.java
@@ -25,6 +25,7 @@
         .addOptionsModification(
             options ->
                 options.horizontalClassMergerOptions().enableIf(enableHorizontalClassMerging))
+        .enableInliningAnnotations()
         .enableNeverClassInliningAnnotations()
         .setMinApi(parameters.getApiLevel())
         .run(parameters.getRuntime(), Main.class)
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/VerticallyMergedClassDistinguishedByInstanceOfTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/VerticallyMergedClassDistinguishedByInstanceOfTest.java
index c8ad21d..47994ef 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/VerticallyMergedClassDistinguishedByInstanceOfTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/VerticallyMergedClassDistinguishedByInstanceOfTest.java
@@ -25,6 +25,7 @@
         .addOptionsModification(
             options ->
                 options.horizontalClassMergerOptions().enableIf(enableHorizontalClassMerging))
+        .enableInliningAnnotations()
         .enableNeverClassInliningAnnotations()
         .setMinApi(parameters.getApiLevel())
         .run(parameters.getRuntime(), Main.class)
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/dispatch/OverrideAbstractMethodWithDefaultTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/dispatch/OverrideAbstractMethodWithDefaultTest.java
index e456600..635ec43 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/dispatch/OverrideAbstractMethodWithDefaultTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/dispatch/OverrideAbstractMethodWithDefaultTest.java
@@ -11,6 +11,7 @@
 import com.android.tools.r8.NoVerticalClassMerging;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.classmerging.horizontal.HorizontalClassMergingTestBase;
+import com.android.tools.r8.utils.codeinspector.HorizontallyMergedClassesInspector;
 import org.junit.Test;
 
 public class OverrideAbstractMethodWithDefaultTest extends HorizontalClassMergingTestBase {
@@ -31,7 +32,7 @@
         .enableNoVerticalClassMergingAnnotations()
         .setMinApi(parameters.getApiLevel())
         .addHorizontallyMergedClassesInspectorIf(
-            enableHorizontalClassMerging, inspector -> inspector.assertNoClassesMerged())
+            enableHorizontalClassMerging, HorizontallyMergedClassesInspector::assertNoClassesMerged)
         .run(parameters.getRuntime(), Main.class)
         .assertSuccessWithOutputLines("J", "B2")
         .inspect(
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/dispatch/OverrideMergeAbsentTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/dispatch/OverrideMergeAbsentTest.java
index 526aa00..9a727f5 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/dispatch/OverrideMergeAbsentTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/dispatch/OverrideMergeAbsentTest.java
@@ -12,6 +12,7 @@
 import com.android.tools.r8.NoVerticalClassMerging;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.classmerging.horizontal.HorizontalClassMergingTestBase;
+import com.android.tools.r8.utils.codeinspector.HorizontallyMergedClassesInspector;
 import org.junit.Test;
 
 public class OverrideMergeAbsentTest extends HorizontalClassMergingTestBase {
@@ -32,7 +33,7 @@
         .enableNoVerticalClassMergingAnnotations()
         .setMinApi(parameters.getApiLevel())
         .addHorizontallyMergedClassesInspectorIf(
-            enableHorizontalClassMerging, inspector -> inspector.assertNoClassesMerged())
+            enableHorizontalClassMerging, HorizontallyMergedClassesInspector::assertNoClassesMerged)
         .run(parameters.getRuntime(), Main.class)
         .assertSuccessWithOutputLines("A", "B", "A", "J")
         .inspect(
diff --git a/src/test/java/com/android/tools/r8/classmerging/vertical/NoIllegalClassAccessWithAccessModificationsTest.java b/src/test/java/com/android/tools/r8/classmerging/vertical/NoIllegalClassAccessWithAccessModificationsTest.java
index 35e27eb..f2edbce 100644
--- a/src/test/java/com/android/tools/r8/classmerging/vertical/NoIllegalClassAccessWithAccessModificationsTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/vertical/NoIllegalClassAccessWithAccessModificationsTest.java
@@ -42,6 +42,7 @@
         .addKeepMainRule(TestClass.class)
         .addVerticallyMergedClassesInspector(this::inspectVerticallyMergedClasses)
         .allowAccessModification()
+        .enableInliningAnnotations()
         .enableNoHorizontalClassMergingAnnotations()
         .setMinApi(parameters.getApiLevel())
         .compile()
diff --git a/src/test/java/com/android/tools/r8/classmerging/vertical/NonReboundFieldAccessOnMergedClassTest.java b/src/test/java/com/android/tools/r8/classmerging/vertical/NonReboundFieldAccessOnMergedClassTest.java
index 0f28809..c65809a 100644
--- a/src/test/java/com/android/tools/r8/classmerging/vertical/NonReboundFieldAccessOnMergedClassTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/vertical/NonReboundFieldAccessOnMergedClassTest.java
@@ -37,6 +37,7 @@
         .addVerticallyMergedClassesInspector(
             inspector -> inspector.assertMergedIntoSubtype(C.class))
         .enableNeverClassInliningAnnotations()
+        .enableNoVerticalClassMergingAnnotations()
         .setMinApi(parameters.getApiLevel())
         .compile()
         .run(parameters.getRuntime(), Main.class)
diff --git a/src/test/java/com/android/tools/r8/classmerging/vertical/VerticalClassMergerTest.java b/src/test/java/com/android/tools/r8/classmerging/vertical/VerticalClassMergerTest.java
index 8dc4e01..5468cae 100644
--- a/src/test/java/com/android/tools/r8/classmerging/vertical/VerticalClassMergerTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/vertical/VerticalClassMergerTest.java
@@ -39,7 +39,6 @@
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.android.tools.r8.utils.codeinspector.FoundClassSubject;
 import com.android.tools.r8.utils.codeinspector.MethodSubject;
-import com.google.common.base.Predicates;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableSet;
 import java.io.File;
@@ -336,7 +335,8 @@
           CF_DIR.resolve("ConflictingInterfaceSignaturesTest.class"),
           CF_DIR.resolve("ConflictingInterfaceSignaturesTest$A.class"),
           CF_DIR.resolve("ConflictingInterfaceSignaturesTest$B.class"),
-          CF_DIR.resolve("ConflictingInterfaceSignaturesTest$InterfaceImpl.class")
+          CF_DIR.resolve("ConflictingInterfaceSignaturesTest$InterfaceImpl.class"),
+          CF_DIR.resolve("NeverInline.class")
         };
     Set<String> preservedClassNames =
         ImmutableSet.of(
@@ -361,7 +361,8 @@
           JAVA8_CF_DIR.resolve("NestedDefaultInterfaceMethodsTest.class"),
           JAVA8_CF_DIR.resolve("NestedDefaultInterfaceMethodsTest$A.class"),
           JAVA8_CF_DIR.resolve("NestedDefaultInterfaceMethodsTest$B.class"),
-          JAVA8_CF_DIR.resolve("NestedDefaultInterfaceMethodsTest$C.class")
+          JAVA8_CF_DIR.resolve("NestedDefaultInterfaceMethodsTest$C.class"),
+          JAVA8_CF_DIR.resolve("NeverInline.class")
         };
     Set<String> preservedClassNames =
         ImmutableSet.of(
@@ -384,7 +385,8 @@
         new Path[] {
           JAVA8_CF_DIR.resolve("NestedDefaultInterfaceMethodsTest.class"),
           JAVA8_CF_DIR.resolve("NestedDefaultInterfaceMethodsTest$A.class"),
-          JAVA8_CF_DIR.resolve("NestedDefaultInterfaceMethodsTest$B.class")
+          JAVA8_CF_DIR.resolve("NestedDefaultInterfaceMethodsTest$B.class"),
+          JAVA8_CF_DIR.resolve("NeverInline.class")
         };
     Set<String> preservedClassNames =
         ImmutableSet.of(
@@ -461,8 +463,14 @@
         new Path[] {
           CF_DIR.resolve("ProguardFieldMapTest.class"),
           CF_DIR.resolve("ProguardFieldMapTest$A.class"),
-          CF_DIR.resolve("ProguardFieldMapTest$B.class")
+          CF_DIR.resolve("ProguardFieldMapTest$B.class"),
+          CF_DIR.resolve("NeverClassInline.class")
         };
+    Set<String> preservedClassNamesWithoutClassMerging =
+        ImmutableSet.of(
+            "classmerging.ProguardFieldMapTest",
+            "classmerging.ProguardFieldMapTest$A",
+            "classmerging.ProguardFieldMapTest$B");
 
     // Try without vertical class merging, to check that output is as expected.
     R8TestCompileResult compileResultWithoutClassMerging =
@@ -478,7 +486,7 @@
                 .allowUnusedProguardConfigurationRules(),
             main,
             readProgramFiles(programFiles),
-            Predicates.alwaysTrue());
+            preservedClassNamesWithoutClassMerging::contains);
 
     ClassNameMapper mappingWithoutClassMerging =
         ClassNameMapper.mapperFromString(compileResultWithoutClassMerging.getProguardMap());
@@ -528,8 +536,14 @@
         new Path[] {
           CF_DIR.resolve("ProguardMethodMapTest.class"),
           CF_DIR.resolve("ProguardMethodMapTest$A.class"),
-          CF_DIR.resolve("ProguardMethodMapTest$B.class")
+          CF_DIR.resolve("ProguardMethodMapTest$B.class"),
+          CF_DIR.resolve("NeverClassInline.class")
         };
+    Set<String> preservedClassNamesWithoutClassMerging =
+        ImmutableSet.of(
+            "classmerging.ProguardMethodMapTest",
+            "classmerging.ProguardMethodMapTest$A",
+            "classmerging.ProguardMethodMapTest$B");
 
     // Try without vertical class merging, to check that output is as expected.
     R8TestCompileResult compileResultWithoutClassMerging =
@@ -545,7 +559,7 @@
                 .allowUnusedProguardConfigurationRules(),
             main,
             readProgramFiles(programFiles),
-            Predicates.alwaysTrue());
+            preservedClassNamesWithoutClassMerging::contains);
 
     ClassNameMapper mappingWithoutClassMerging =
         ClassNameMapper.mapperFromString(compileResultWithoutClassMerging.getProguardMap());
@@ -603,8 +617,14 @@
         new Path[] {
           CF_DIR.resolve("ProguardMethodMapTest.class"),
           CF_DIR.resolve("ProguardMethodMapTest$A.class"),
-          CF_DIR.resolve("ProguardMethodMapTest$B.class")
+          CF_DIR.resolve("ProguardMethodMapTest$B.class"),
+          CF_DIR.resolve("NeverClassInline.class")
         };
+    Set<String> preservedClassNamesWithoutClassMerging =
+        ImmutableSet.of(
+            "classmerging.ProguardMethodMapTest",
+            "classmerging.ProguardMethodMapTest$A",
+            "classmerging.ProguardMethodMapTest$B");
 
     // Try without vertical class merging, to check that output is as expected.
     R8TestCompileResult compileResultWithoutClassMerging =
@@ -624,7 +644,7 @@
                 .allowUnusedProguardConfigurationRules(),
             main,
             readProgramFiles(programFiles),
-            Predicates.alwaysTrue());
+            preservedClassNamesWithoutClassMerging::contains);
 
     ClassNameMapper mappingWithoutClassMerging =
         ClassNameMapper.mapperFromString(compileResultWithoutClassMerging.getProguardMap());
@@ -690,7 +710,8 @@
         new Path[] {
           CF_DIR.resolve("SubClassThatReferencesSuperMethod.class"),
           CF_DIR.resolve("SuperClassWithReferencedMethod.class"),
-          CF_DIR.resolve("SuperCallRewritingTest.class")
+          CF_DIR.resolve("SuperCallRewritingTest.class"),
+          CF_DIR.resolve("NeverInline.class")
         };
     Set<String> preservedClassNames =
         ImmutableSet.of(
@@ -721,7 +742,8 @@
     Path[] programFiles =
         new Path[] {
           CF_DIR.resolve("SuperClassWithReferencedMethod.class"),
-          CF_DIR.resolve("SuperCallRewritingTest.class")
+          CF_DIR.resolve("SuperCallRewritingTest.class"),
+          CF_DIR.resolve("NeverInline.class")
         };
 
     // Build SubClassThatReferencesMethod.
@@ -936,7 +958,8 @@
           CF_DIR.resolve("ConflictingInterfaceSignaturesTest.class"),
           CF_DIR.resolve("ConflictingInterfaceSignaturesTest$A.class"),
           CF_DIR.resolve("ConflictingInterfaceSignaturesTest$B.class"),
-          CF_DIR.resolve("ConflictingInterfaceSignaturesTest$InterfaceImpl.class")
+          CF_DIR.resolve("ConflictingInterfaceSignaturesTest$InterfaceImpl.class"),
+          CF_DIR.resolve("NeverInline.class")
         };
     Set<String> preservedClassNames =
         ImmutableSet.of(
@@ -958,7 +981,8 @@
         new Path[] {
           JAVA8_CF_DIR.resolve("MergeDefaultMethodIntoClassTest.class"),
           JAVA8_CF_DIR.resolve("MergeDefaultMethodIntoClassTest$A.class"),
-          JAVA8_CF_DIR.resolve("MergeDefaultMethodIntoClassTest$B.class")
+          JAVA8_CF_DIR.resolve("MergeDefaultMethodIntoClassTest$B.class"),
+          JAVA8_CF_DIR.resolve("NeverInline.class")
         };
     ImmutableSet<String> preservedClassNames =
         ImmutableSet.of(
@@ -1021,17 +1045,21 @@
     Path[] programFiles =
         new Path[] {
           CF_DIR.resolve("SimpleInterfaceAccessTest.class"),
+          CF_DIR.resolve("SimpleInterfaceAccessTest$1.class"),
           CF_DIR.resolve("SimpleInterfaceAccessTest$SimpleInterface.class"),
           CF_DIR.resolve("SimpleInterfaceAccessTest$OtherSimpleInterface.class"),
           CF_DIR.resolve("SimpleInterfaceAccessTest$OtherSimpleInterfaceImpl.class"),
           CF_DIR.resolve("pkg/SimpleInterfaceImplRetriever.class"),
-          CF_DIR.resolve("pkg/SimpleInterfaceImplRetriever$SimpleInterfaceImpl.class")
+          CF_DIR.resolve("pkg/SimpleInterfaceImplRetriever$SimpleInterfaceImpl.class"),
+          CF_DIR.resolve("pkg/SimpleInterfaceImplRetriever$1.class"),
+          CF_DIR.resolve("NeverInline.class")
         };
     // SimpleInterface cannot be merged into SimpleInterfaceImpl because SimpleInterfaceImpl
     // is in a different package and is not public.
     ImmutableSet<String> preservedClassNames =
         ImmutableSet.of(
             "classmerging.SimpleInterfaceAccessTest",
+            "classmerging.SimpleInterfaceAccessTest$1",
             "classmerging.SimpleInterfaceAccessTest$SimpleInterface",
             "classmerging.SimpleInterfaceAccessTest$OtherSimpleInterface",
             "classmerging.SimpleInterfaceAccessTest$OtherSimpleInterfaceImpl",
@@ -1081,8 +1109,10 @@
     Path[] programFiles =
         new Path[] {
           CF_DIR.resolve("TemplateMethodTest.class"),
+          CF_DIR.resolve("TemplateMethodTest$1.class"),
           CF_DIR.resolve("TemplateMethodTest$AbstractClass.class"),
-          CF_DIR.resolve("TemplateMethodTest$AbstractClassImpl.class")
+          CF_DIR.resolve("TemplateMethodTest$AbstractClassImpl.class"),
+          CF_DIR.resolve("NeverInline.class")
         };
     Set<String> preservedClassNames =
         ImmutableSet.of(
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 13d59dc..a5a1a8a 100644
--- a/src/test/java/com/android/tools/r8/debug/DebugTestBase.java
+++ b/src/test/java/com/android/tools/r8/debug/DebugTestBase.java
@@ -9,6 +9,7 @@
 import com.android.tools.r8.ToolHelper.DexVm;
 import com.android.tools.r8.ToolHelper.DexVm.Version;
 import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.graph.ClassAccessFlags;
 import com.android.tools.r8.naming.ClassNameMapper;
 import com.android.tools.r8.naming.ClassNamingForNameMapper;
 import com.android.tools.r8.naming.ClassNamingForNameMapper.MappedRange;
@@ -2161,8 +2162,28 @@
       }
 
       private static boolean isInLambdaClass(VmMirror mirror, Location location) {
-        String classSig = mirror.getClassSignature(location.classID);
-        return classSig.contains("$$Lambda$");
+        // TODO(b/174809573): These "lambda" specific methods are highly questionable.
+        //   Determine the exact filtering behavior of intellij (which is very likely not name
+        //   based) and update this filter accordingly.
+        CommandPacket cmd =
+            new CommandPacket(
+                ReferenceTypeCommandSet.CommandSetID, ReferenceTypeCommandSet.ModifiersCommand);
+        cmd.setNextValueAsReferenceTypeID(location.classID);
+        ReplyPacket reply = mirror.performCommand(cmd);
+        mirror.checkReply(reply);
+        int modifiers = reply.getNextValueAsInt();
+        ClassAccessFlags flags = ClassAccessFlags.fromCfAccessFlags(modifiers);
+        if (!flags.isSynthetic()) {
+          return false;
+        }
+        String signature = mirror.getClassSignature(location.classID);
+        if (signature.contains("-CC")) {
+          // TODO(b/174809573): The need to return false here indicates a questionable test
+          //  expectation. Either the test is incorrect or there is a bug in our generation of
+          //  -CC classes marked as synthetic as that would lead to unwanted debugger filtering.
+          return false;
+        }
+        return true;
       }
 
       private static boolean isLambdaMethod(VmMirror mirror, Location location) {
diff --git a/src/test/java/com/android/tools/r8/debug/KotlinStdLibCompilationTest.java b/src/test/java/com/android/tools/r8/debug/KotlinStdLibCompilationTest.java
index deb97d8..ff841d7 100644
--- a/src/test/java/com/android/tools/r8/debug/KotlinStdLibCompilationTest.java
+++ b/src/test/java/com/android/tools/r8/debug/KotlinStdLibCompilationTest.java
@@ -54,6 +54,7 @@
         .noMinification()
         .noTreeShaking()
         .addKeepAllAttributes()
+        .addDontWarnJetBrainsAnnotations()
         .setMode(CompilationMode.DEBUG)
         .setMinApi(parameters.getApiLevel())
         .compileWithExpectedDiagnostics(TestDiagnosticMessages::assertNoMessages);
diff --git a/src/test/java/com/android/tools/r8/debug/LocalsTest.java b/src/test/java/com/android/tools/r8/debug/LocalsTest.java
index 7eeed7d..68b67a9 100644
--- a/src/test/java/com/android/tools/r8/debug/LocalsTest.java
+++ b/src/test/java/com/android/tools/r8/debug/LocalsTest.java
@@ -285,41 +285,46 @@
         "Locals",
         breakpoint("Locals", "breakpoint"),
         run(),
-        inspect(state -> {
-          // 1st breakpoint: all lengthOfArray[N] are set to 0
-          FrameInspector outerFrame = state.getFrame(1);
+        inspect(
+            state -> {
+              // 1st breakpoint: all lengthOfArray[N] are set to 0
+              FrameInspector outerFrame = state.getFrame(1);
 
-          Map<String, Value> localValues = outerFrame.getLocalValues();
+              Map<String, Value> localValues = outerFrame.getLocalValues();
 
-          for (int i = minIndex; i <= maxIndex; ++i) {
-            String varName = "lengthOfArray" + i;
-            Assert.assertTrue(localValues.containsKey(varName));
-            Assert.assertEquals(Value.createInt(0), localValues.get(varName));
-          }
+              for (int i = minIndex; i <= maxIndex; ++i) {
+                String varName = "lengthOfArray" + i;
+                Assert.assertTrue(localValues.containsKey(varName));
+                Assert.assertEquals(Value.createInt(0), localValues.get(varName));
+              }
 
-          // Capture IDs of arrays.
-          for (int i = minIndex; i <= maxIndex; ++i) {
-            String varName = "array" + i;
-            Assert.assertTrue(localValues.containsKey(varName));
-            arrayLocals.put(varName, localValues.get(varName));
-          }
-        }),
+              // Capture IDs of arrays.
+              for (int i = minIndex; i <= maxIndex; ++i) {
+                String varName = "array" + i;
+                Assert.assertTrue(localValues.containsKey(varName));
+                arrayLocals.put(varName, localValues.get(varName));
+              }
+            }),
         // Step out to reach next instructions in the tested method
         stepOut(),
-        inspect(state -> {
-          Assert.assertEquals("Locals.java", state.getSourceFile());
-          Assert.assertEquals(107, state.getLineNumber());
-          // Verify that all arrays have the same value.
-          arrayLocals.forEach((name, value) -> state.checkLocal(name, value));
-        }),
+        inspect(
+            state -> {
+              Assert.assertEquals("Locals.java", state.getSourceFile());
+              Assert.assertEquals(107, state.getLineNumber());
+              // Verify that all arrays have the same value.
+              arrayLocals.forEach(state::checkLocal);
+            }),
         // Step instruction by instruction to ensure all locals previously declared are safe.
-        stepUntil(StepKind.OVER, StepLevel.INSTRUCTION, state -> {
-          final String sourceFile = state.getSourceFile();
-          final int lineNumber = state.getLineNumber();
-          arrayLocals.forEach((name, value) -> state.checkLocal(name, value));
-          // Stop when we reach the expected line.
-          return lineNumber == 125 && sourceFile.equals("Locals.java");
-        }),
+        stepUntil(
+            StepKind.OVER,
+            StepLevel.INSTRUCTION,
+            state -> {
+              final String sourceFile = state.getSourceFile();
+              final int lineNumber = state.getLineNumber();
+              arrayLocals.forEach(state::checkLocal);
+              // Stop when we reach the expected line.
+              return lineNumber == 125 && sourceFile.equals("Locals.java");
+            }),
         run());
   }
 
diff --git a/src/test/java/com/android/tools/r8/debug/SynchronizedBlockTest.java b/src/test/java/com/android/tools/r8/debug/SynchronizedBlockTest.java
index 0c9c4b1..208c2cb 100644
--- a/src/test/java/com/android/tools/r8/debug/SynchronizedBlockTest.java
+++ b/src/test/java/com/android/tools/r8/debug/SynchronizedBlockTest.java
@@ -28,8 +28,8 @@
   public static Collection<Object[]> setup() {
     DelayedDebugTestConfig cf =
         temp -> new CfDebugTestConfig().addPaths(DebugTestBase.DEBUGGEE_JAR);
-    DelayedDebugTestConfig r8cf = temp -> new R8CfDebugTestResourcesConfig(temp);
-    DelayedDebugTestConfig d8 = temp -> new D8DebugTestResourcesConfig(temp);
+    DelayedDebugTestConfig r8cf = R8CfDebugTestResourcesConfig::new;
+    DelayedDebugTestConfig d8 = D8DebugTestResourcesConfig::new;
     return ImmutableList.of(
         new Object[] {"CF", cf},
         new Object[] {"D8", d8},
diff --git a/src/test/java/com/android/tools/r8/desugar/DesugarMissingTypeLambdaTest.java b/src/test/java/com/android/tools/r8/desugar/DesugarMissingTypeLambdaTest.java
index 8f304c6..875ead2 100644
--- a/src/test/java/com/android/tools/r8/desugar/DesugarMissingTypeLambdaTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/DesugarMissingTypeLambdaTest.java
@@ -3,8 +3,6 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.desugar;
 
-import static org.hamcrest.CoreMatchers.containsString;
-import static org.hamcrest.MatcherAssert.assertThat;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
 
@@ -19,6 +17,7 @@
 import com.android.tools.r8.errors.InterfaceDesugarMissingTypeDiagnostic;
 import com.android.tools.r8.position.Position;
 import com.android.tools.r8.references.Reference;
+import com.android.tools.r8.synthesis.SyntheticItemsTestUtils;
 import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.StringUtils;
 import org.junit.Test;
@@ -79,7 +78,7 @@
         assertEquals(
             Reference.classFromClass(MissingInterface.class), desugarWarning.getMissingType());
         // TODO(b/132671303): The context class should not be the synthesized lambda class.
-        assertThat(desugarWarning.getContextType().getDescriptor(), containsString("$$Lambda"));
+        assertTrue(SyntheticItemsTestUtils.isInternalLambda(desugarWarning.getContextType()));
         // TODO(b/132671303): The position info should be the method context.
         assertEquals(Position.UNKNOWN, desugarWarning.getPosition());
       }
diff --git a/src/test/java/com/android/tools/r8/desugar/DesugarToClassFile.java b/src/test/java/com/android/tools/r8/desugar/DesugarToClassFile.java
index c9377fe..a0b196f 100644
--- a/src/test/java/com/android/tools/r8/desugar/DesugarToClassFile.java
+++ b/src/test/java/com/android/tools/r8/desugar/DesugarToClassFile.java
@@ -10,6 +10,7 @@
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.FoundClassSubject;
 import java.nio.file.Path;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -47,8 +48,7 @@
 
   private void checkHasLambdaClass(CodeInspector inspector) {
     assertTrue(
-        inspector.allClasses().stream()
-            .anyMatch(subject -> subject.getOriginalName().contains("-$$Lambda$")));
+        inspector.allClasses().stream().anyMatch(FoundClassSubject::isSynthesizedJavaLambdaClass));
   }
 
   @Test
diff --git a/src/test/java/com/android/tools/r8/desugar/InvokeSuperToRewrittenDefaultMethodTest.java b/src/test/java/com/android/tools/r8/desugar/InvokeSuperToRewrittenDefaultMethodTest.java
index f63c4d9..93e46d0 100644
--- a/src/test/java/com/android/tools/r8/desugar/InvokeSuperToRewrittenDefaultMethodTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/InvokeSuperToRewrittenDefaultMethodTest.java
@@ -106,7 +106,6 @@
     }
   }
 
-  @FunctionalInterface
   public interface CharConsumer extends Consumer<Character>, IntConsumer {
 
     void accept(char c);
diff --git a/src/test/java/com/android/tools/r8/desugar/backports/BackportDuplicationTest.java b/src/test/java/com/android/tools/r8/desugar/backports/BackportDuplicationTest.java
index d094c78..acfc021 100644
--- a/src/test/java/com/android/tools/r8/desugar/backports/BackportDuplicationTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/backports/BackportDuplicationTest.java
@@ -3,9 +3,6 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.desugar.backports;
 
-import static org.hamcrest.CoreMatchers.containsString;
-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;
@@ -16,10 +13,10 @@
 import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.desugar.backports.AbstractBackportTest.MiniAssert;
 import com.android.tools.r8.references.MethodReference;
-import com.android.tools.r8.synthesis.SyntheticItems;
+import com.android.tools.r8.synthesis.SyntheticItemsTestUtils;
+import com.android.tools.r8.synthesis.SyntheticNaming;
 import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.StringUtils;
-import com.android.tools.r8.utils.SyntheticItemsTestUtils;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.android.tools.r8.utils.codeinspector.InstructionSubject;
 import com.google.common.collect.ImmutableList;
@@ -200,9 +197,7 @@
   private void checkNoOriginalsAndNoInternalSynthetics(CodeInspector inspector) {
     inspector.forAllClasses(
         clazz -> {
-          assertThat(
-              clazz.getFinalName(),
-              not(containsString(SyntheticItems.INTERNAL_SYNTHETIC_CLASS_SEPARATOR)));
+          SyntheticNaming.verifyNotInternalSynthetic(clazz.getFinalReference());
           if (!clazz.getOriginalName().equals(MiniAssert.class.getTypeName())) {
             clazz.forAllMethods(
                 method ->
@@ -233,11 +228,11 @@
     // of intermediates.
     Set<MethodReference> expectedSynthetics =
         ImmutableSet.of(
-            SyntheticItemsTestUtils.syntheticMethod(
+            SyntheticItemsTestUtils.syntheticBackportMethod(
                 User1.class, 0, Character.class.getMethod("compare", char.class, char.class)),
-            SyntheticItemsTestUtils.syntheticMethod(
+            SyntheticItemsTestUtils.syntheticBackportMethod(
                 User1.class, 1, Boolean.class.getMethod("compare", boolean.class, boolean.class)),
-            SyntheticItemsTestUtils.syntheticMethod(
+            SyntheticItemsTestUtils.syntheticBackportMethod(
                 User2.class, 0, Integer.class.getMethod("compare", int.class, int.class)));
     assertEquals(expectedSynthetics, getSyntheticMethods(inspector));
   }
diff --git a/src/test/java/com/android/tools/r8/desugar/backports/BackportMainDexTest.java b/src/test/java/com/android/tools/r8/desugar/backports/BackportMainDexTest.java
index 45f8957..05f84af 100644
--- a/src/test/java/com/android/tools/r8/desugar/backports/BackportMainDexTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/backports/BackportMainDexTest.java
@@ -3,7 +3,6 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.desugar.backports;
 
-import static com.android.tools.r8.synthesis.SyntheticItems.EXTERNAL_SYNTHETIC_CLASS_SEPARATOR;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.junit.Assert.assertEquals;
@@ -25,11 +24,11 @@
 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.synthesis.SyntheticItemsTestUtils;
 import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.AndroidApp;
 import com.android.tools.r8.utils.ListUtils;
 import com.android.tools.r8.utils.StringUtils;
-import com.android.tools.r8.utils.SyntheticItemsTestUtils;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableSet;
@@ -57,11 +56,6 @@
   static final List<Class<?>> MAIN_DEX_LIST_CLASSES =
       ImmutableList.of(MiniAssert.class, TestClass.class, User2.class);
 
-  static final String SyntheticUnderUser1 =
-      User1.class.getTypeName() + EXTERNAL_SYNTHETIC_CLASS_SEPARATOR;
-  static final String SyntheticUnderUser2 =
-      User2.class.getTypeName() + EXTERNAL_SYNTHETIC_CLASS_SEPARATOR;
-
   private final TestParameters parameters;
 
   @Parameterized.Parameters(name = "{0}")
@@ -341,16 +335,16 @@
   private ImmutableSet<MethodReference> getNonMainDexExpectedSynthetics()
       throws NoSuchMethodException {
     return ImmutableSet.of(
-        SyntheticItemsTestUtils.syntheticMethod(
+        SyntheticItemsTestUtils.syntheticBackportMethod(
             User1.class, 1, Boolean.class.getMethod("compare", boolean.class, boolean.class)));
   }
 
   private ImmutableSet<MethodReference> getMainDexExpectedSynthetics()
       throws NoSuchMethodException {
     return ImmutableSet.of(
-        SyntheticItemsTestUtils.syntheticMethod(
+        SyntheticItemsTestUtils.syntheticBackportMethod(
             User1.class, 0, Character.class.getMethod("compare", char.class, char.class)),
-        SyntheticItemsTestUtils.syntheticMethod(
+        SyntheticItemsTestUtils.syntheticBackportMethod(
             User2.class, 0, Integer.class.getMethod("compare", int.class, int.class)));
   }
 
diff --git a/src/test/java/com/android/tools/r8/desugar/bridge/LambdaReturnTypeBridgeTest.java b/src/test/java/com/android/tools/r8/desugar/bridge/LambdaReturnTypeBridgeTest.java
index 2dbcc30..fdb1044 100644
--- a/src/test/java/com/android/tools/r8/desugar/bridge/LambdaReturnTypeBridgeTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/bridge/LambdaReturnTypeBridgeTest.java
@@ -62,12 +62,7 @@
             codeInspector -> {
               boolean foundBridge = false;
               for (FoundClassSubject clazz : codeInspector.allClasses()) {
-                if (clazz
-                    .getOriginalName()
-                    .contains(
-                        "-$$Lambda$"
-                            + LambdaWithMultipleImplementingInterfaces.class.getSimpleName()
-                            + "$")) {
+                if (clazz.isSynthesizedJavaLambdaClass()) {
                   // Find bridge method and check whether or not it has a cast.
                   for (FoundMethodSubject bridge : clazz.allMethods(FoundMethodSubject::isBridge)) {
                     foundBridge = true;
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/BufferedReaderTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/BufferedReaderTest.java
index f71c916..2494871 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/BufferedReaderTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/BufferedReaderTest.java
@@ -174,6 +174,11 @@
                     configurationAlternative3(options, false, parameters))
         .addInnerClasses(BufferedReaderTest.class)
         .addKeepMainRule(TestClass.class)
+        .applyIf(
+            parameters.getApiLevel().isLessThan(AndroidApiLevel.N),
+            builder ->
+                builder.addDontWarnRetargetLibraryMember(
+                    "virtualDispatch$BufferedReader$lines$dispatchHolder"))
         .setMinApi(parameters.getApiLevel())
         .enableCoreLibraryDesugaring(parameters.getApiLevel(), keepRuleConsumer)
         .enableInliningAnnotations()
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/CustomCollectionForwardingTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/CustomCollectionForwardingTest.java
index cf0d8ef..33d3f35 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/CustomCollectionForwardingTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/CustomCollectionForwardingTest.java
@@ -5,6 +5,7 @@
 package com.android.tools.r8.desugar.desugaredlibrary;
 
 import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestShrinkerBuilder;
 import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.BooleanUtils;
 import com.android.tools.r8.utils.StringUtils;
@@ -15,7 +16,6 @@
 import java.util.List;
 import java.util.ListIterator;
 import java.util.Spliterator;
-import org.jetbrains.annotations.NotNull;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
@@ -64,6 +64,9 @@
         .addKeepMainRule(Executor.class)
         .setMinApi(parameters.getApiLevel())
         .enableCoreLibraryDesugaring(parameters.getApiLevel(), keepRuleConsumer)
+        .applyIf(
+            parameters.getApiLevel().isLessThan(AndroidApiLevel.N),
+            TestShrinkerBuilder::addDontWarnEmulatedLibraryClasses)
         .compile()
         .addDesugaredCoreLibraryRunClassPath(
             this::buildDesugaredLibrary,
@@ -115,21 +118,18 @@
       return false;
     }
 
-    @NotNull
     @Override
     public Iterator<E> iterator() {
       return null;
     }
 
-    @NotNull
     @Override
     public Object[] toArray() {
       return new Object[0];
     }
 
-    @NotNull
     @Override
-    public <T> T[] toArray(@NotNull T[] a) {
+    public <T> T[] toArray(T[] a) {
       return null;
     }
 
@@ -144,27 +144,27 @@
     }
 
     @Override
-    public boolean containsAll(@NotNull Collection<?> c) {
+    public boolean containsAll(Collection<?> c) {
       return false;
     }
 
     @Override
-    public boolean addAll(@NotNull Collection<? extends E> c) {
+    public boolean addAll(Collection<? extends E> c) {
       return false;
     }
 
     @Override
-    public boolean addAll(int index, @NotNull Collection<? extends E> c) {
+    public boolean addAll(int index, Collection<? extends E> c) {
       return false;
     }
 
     @Override
-    public boolean removeAll(@NotNull Collection<?> c) {
+    public boolean removeAll(Collection<?> c) {
       return false;
     }
 
     @Override
-    public boolean retainAll(@NotNull Collection<?> c) {
+    public boolean retainAll(Collection<?> c) {
       return false;
     }
 
@@ -199,19 +199,16 @@
       return 0;
     }
 
-    @NotNull
     @Override
     public ListIterator<E> listIterator() {
       return null;
     }
 
-    @NotNull
     @Override
     public ListIterator<E> listIterator(int index) {
       return null;
     }
 
-    @NotNull
     @Override
     public List<E> subList(int fromIndex, int toIndex) {
       return null;
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/CustomCollectionInterfaceSuperTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/CustomCollectionInterfaceSuperTest.java
index 4bd591e..7206b75 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/CustomCollectionInterfaceSuperTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/CustomCollectionInterfaceSuperTest.java
@@ -15,7 +15,6 @@
 import java.util.Iterator;
 import java.util.List;
 import java.util.function.Predicate;
-import org.jetbrains.annotations.NotNull;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
@@ -82,6 +81,12 @@
     testForR8(parameters.getBackend())
         .addInnerClasses(CustomCollectionInterfaceSuperTest.class)
         .addKeepMainRule(Main.class)
+        .applyIf(
+            parameters.getApiLevel().isLessThan(AndroidApiLevel.N),
+            builder ->
+                builder
+                    .addDontWarnEmulatedLibraryClass(Collection.class)
+                    .addDontWarnVivifiedClass(Predicate.class))
         .setMinApi(parameters.getApiLevel())
         .enableCoreLibraryDesugaring(parameters.getApiLevel(), keepRuleConsumer)
         .compile()
@@ -153,21 +158,18 @@
       return false;
     }
 
-    @NotNull
     @Override
     public Iterator<E> iterator() {
       return Collections.emptyIterator();
     }
 
-    @NotNull
     @Override
     public Object[] toArray() {
       return new Object[0];
     }
 
-    @NotNull
     @Override
-    public <T> T[] toArray(@NotNull T[] a) {
+    public <T> T[] toArray(T[] a) {
       return a;
     }
 
@@ -182,22 +184,22 @@
     }
 
     @Override
-    public boolean containsAll(@NotNull Collection<?> c) {
+    public boolean containsAll(Collection<?> c) {
       return false;
     }
 
     @Override
-    public boolean addAll(@NotNull Collection<? extends E> c) {
+    public boolean addAll(Collection<? extends E> c) {
       return false;
     }
 
     @Override
-    public boolean removeAll(@NotNull Collection<?> c) {
+    public boolean removeAll(Collection<?> c) {
       return false;
     }
 
     @Override
-    public boolean retainAll(@NotNull Collection<?> c) {
+    public boolean retainAll(Collection<?> c) {
       return false;
     }
 
@@ -226,21 +228,18 @@
       return false;
     }
 
-    @NotNull
     @Override
     public Iterator<E> iterator() {
       return Collections.emptyIterator();
     }
 
-    @NotNull
     @Override
     public Object[] toArray() {
       return new Object[0];
     }
 
-    @NotNull
     @Override
-    public <T> T[] toArray(@NotNull T[] a) {
+    public <T> T[] toArray(T[] a) {
       return a;
     }
 
@@ -255,22 +254,22 @@
     }
 
     @Override
-    public boolean containsAll(@NotNull Collection<?> c) {
+    public boolean containsAll(Collection<?> c) {
       return false;
     }
 
     @Override
-    public boolean addAll(@NotNull Collection<? extends E> c) {
+    public boolean addAll(Collection<? extends E> c) {
       return false;
     }
 
     @Override
-    public boolean removeAll(@NotNull Collection<?> c) {
+    public boolean removeAll(Collection<?> c) {
       return false;
     }
 
     @Override
-    public boolean retainAll(@NotNull Collection<?> c) {
+    public boolean retainAll(Collection<?> c) {
       return false;
     }
 
@@ -299,21 +298,18 @@
       return false;
     }
 
-    @NotNull
     @Override
     public Iterator<E> iterator() {
       return Collections.emptyIterator();
     }
 
-    @NotNull
     @Override
     public Object[] toArray() {
       return new Object[0];
     }
 
-    @NotNull
     @Override
-    public <T> T[] toArray(@NotNull T[] a) {
+    public <T> T[] toArray(T[] a) {
       return a;
     }
 
@@ -328,22 +324,22 @@
     }
 
     @Override
-    public boolean containsAll(@NotNull Collection<?> c) {
+    public boolean containsAll(Collection<?> c) {
       return false;
     }
 
     @Override
-    public boolean addAll(@NotNull Collection<? extends E> c) {
+    public boolean addAll(Collection<? extends E> c) {
       return false;
     }
 
     @Override
-    public boolean removeAll(@NotNull Collection<?> c) {
+    public boolean removeAll(Collection<?> c) {
       return false;
     }
 
     @Override
-    public boolean retainAll(@NotNull Collection<?> c) {
+    public boolean retainAll(Collection<?> c) {
       return false;
     }
 
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/CustomCollectionSuperCallsTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/CustomCollectionSuperCallsTest.java
index 0707ab9..ce35ef0 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/CustomCollectionSuperCallsTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/CustomCollectionSuperCallsTest.java
@@ -9,6 +9,8 @@
 import com.android.tools.r8.D8TestRunResult;
 import com.android.tools.r8.R8TestRunResult;
 import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestShrinkerBuilder;
+import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.BooleanUtils;
 import java.nio.file.Path;
 import java.util.ArrayList;
@@ -106,6 +108,9 @@
         testForR8(parameters.getBackend())
             .addInnerClasses(CustomCollectionSuperCallsTest.class)
             .addKeepMainRule(Executor.class)
+            .applyIf(
+                parameters.getApiLevel().isLessThan(AndroidApiLevel.N),
+                TestShrinkerBuilder::addDontWarnVivifiedClasses)
             .setMinApi(parameters.getApiLevel())
             .enableCoreLibraryDesugaring(parameters.getApiLevel(), keepRuleConsumer)
             .compile()
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/CustomCollectionTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/CustomCollectionTest.java
index 0947a55..9212761 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/CustomCollectionTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/CustomCollectionTest.java
@@ -24,8 +24,6 @@
 import java.util.List;
 import java.util.SortedSet;
 import java.util.stream.Stream;
-import org.jetbrains.annotations.NotNull;
-import org.jetbrains.annotations.Nullable;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
@@ -97,10 +95,7 @@
               .disableDesugaring()
               .compile()
               .assertNoMessages()
-              .inspect(
-                  inspector -> {
-                    this.assertCustomCollectionCallsCorrect(inspector);
-                  })
+              .inspect(this::assertCustomCollectionCallsCorrect)
               .addDesugaredCoreLibraryRunClassPath(
                   this::buildDesugaredLibrary,
                   parameters.getApiLevel(),
@@ -138,6 +133,9 @@
             .addInnerClasses(CustomCollectionTest.class)
             .setMinApi(parameters.getApiLevel())
             .addKeepClassAndMembersRules(Executor.class)
+            .applyIf(
+                requiresEmulatedInterfaceCoreLibDesugaring(parameters),
+                builder -> builder.addDontWarnEmulatedLibraryClasses().addDontWarnVivifiedClasses())
             .enableCoreLibraryDesugaring(parameters.getApiLevel(), keepRuleConsumer)
             .compile()
             .inspect(this::assertCustomCollectionCallsCorrect)
@@ -161,7 +159,8 @@
               .allMatch(
                   instr ->
                       instr.toString().contains("$-EL")
-                          || instr.toString().contains("Comparator$-CC")));
+                          || instr.toString().contains("Comparator$-CC")
+                          || instr.toString().contains("Stream$-CC")));
     } else {
       assertTrue(direct.streamInstructions().noneMatch(instr -> instr.toString().contains("$-EL")));
     }
@@ -178,7 +177,8 @@
             .allMatch(
                 instr ->
                     instr.toString().contains("$-EL")
-                        || instr.toString().contains("Comparator$-CC")));
+                        || instr.toString().contains("Comparator$-CC")
+                        || instr.toString().contains("Stream$-CC")));
     inherited.streamInstructions().forEach(this::assertInheritedDispatchCorrect);
   }
 
@@ -311,21 +311,18 @@
       return false;
     }
 
-    @NotNull
     @Override
     public Iterator<E> iterator() {
       return Collections.emptyIterator();
     }
 
-    @NotNull
     @Override
     public Object[] toArray() {
       return new Object[0];
     }
 
-    @NotNull
     @Override
-    public <T> T[] toArray(@NotNull T[] a) {
+    public <T> T[] toArray(T[] a) {
       return a;
     }
 
@@ -340,22 +337,22 @@
     }
 
     @Override
-    public boolean containsAll(@NotNull Collection<?> c) {
+    public boolean containsAll(Collection<?> c) {
       return false;
     }
 
     @Override
-    public boolean addAll(@NotNull Collection<? extends E> c) {
+    public boolean addAll(Collection<? extends E> c) {
       return false;
     }
 
     @Override
-    public boolean removeAll(@NotNull Collection<?> c) {
+    public boolean removeAll(Collection<?> c) {
       return false;
     }
 
     @Override
-    public boolean retainAll(@NotNull Collection<?> c) {
+    public boolean retainAll(Collection<?> c) {
       return false;
     }
 
@@ -369,25 +366,21 @@
   // Implements directly a core library interface which implements other library interfaces.
   static class CustomSortedSet<E> implements SortedSet<E> {
 
-    @Nullable
     @Override
     public Comparator<? super E> comparator() {
       return null;
     }
 
-    @NotNull
     @Override
     public SortedSet<E> subSet(E fromElement, E toElement) {
       return new CustomSortedSet<>();
     }
 
-    @NotNull
     @Override
     public SortedSet<E> headSet(E toElement) {
       return new CustomSortedSet<>();
     }
 
-    @NotNull
     @Override
     public SortedSet<E> tailSet(E fromElement) {
       return new CustomSortedSet<>();
@@ -418,21 +411,18 @@
       return false;
     }
 
-    @NotNull
     @Override
     public Iterator<E> iterator() {
       return Collections.emptyIterator();
     }
 
-    @NotNull
     @Override
     public Object[] toArray() {
       return new Object[0];
     }
 
-    @NotNull
     @Override
-    public <T> T[] toArray(@NotNull T[] a) {
+    public <T> T[] toArray(T[] a) {
       return a;
     }
 
@@ -447,7 +437,7 @@
     }
 
     @Override
-    public boolean addAll(@NotNull Collection c) {
+    public boolean addAll(Collection c) {
       return false;
     }
 
@@ -455,17 +445,17 @@
     public void clear() {}
 
     @Override
-    public boolean removeAll(@NotNull Collection c) {
+    public boolean removeAll(Collection c) {
       return false;
     }
 
     @Override
-    public boolean retainAll(@NotNull Collection c) {
+    public boolean retainAll(Collection c) {
       return false;
     }
 
     @Override
-    public boolean containsAll(@NotNull Collection c) {
+    public boolean containsAll(Collection c) {
       return false;
     }
   }
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DesugaredLibraryDeterminismTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DesugaredLibraryDeterminismTest.java
index f7b5e5d..9623e7c 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DesugaredLibraryDeterminismTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DesugaredLibraryDeterminismTest.java
@@ -17,11 +17,16 @@
 import com.android.tools.r8.utils.codeinspector.FoundClassSubject;
 import com.android.tools.r8.utils.codeinspector.FoundMethodSubject;
 import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import com.google.common.collect.Sets;
+import com.google.common.collect.Sets.SetView;
 import java.io.IOException;
 import java.nio.file.Files;
 import java.nio.file.Path;
+import java.util.Collections;
 import java.util.IdentityHashMap;
 import java.util.Map;
+import java.util.Set;
+import java.util.stream.Collectors;
 import org.junit.Assume;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -53,12 +58,33 @@
   private void assertIdenticalInspectors(Path libDexFile1, Path libDexFile2) throws IOException {
     CodeInspector i1 = new CodeInspector(libDexFile1.resolve("classes.dex"));
     CodeInspector i2 = new CodeInspector(libDexFile2.resolve("classes.dex"));
+    assertIdenticalInspectors(i1, i2);
+  }
+
+  public static void assertIdenticalInspectors(CodeInspector i1, CodeInspector i2) {
     assertEquals(i1.allClasses().size(), i2.allClasses().size());
     Map<DexEncodedMethod, DexEncodedMethod> diffs = new IdentityHashMap<>();
     for (FoundClassSubject clazz1 : i1.allClasses()) {
       ClassSubject clazz = i2.clazz(clazz1.getOriginalName());
       assertTrue(clazz.isPresent());
       FoundClassSubject clazz2 = clazz.asFoundClassSubject();
+      Set<String> methods1 =
+          clazz1.allMethods().stream()
+              .map(FoundMethodSubject::toString)
+              .collect(Collectors.toSet());
+      Set<String> methods2 =
+          clazz2.allMethods().stream()
+              .map(FoundMethodSubject::toString)
+              .collect(Collectors.toSet());
+      SetView<String> union = Sets.union(methods1, methods2);
+      assertEquals(
+          "Inspector 1 contains more methods",
+          Collections.emptySet(),
+          Sets.difference(union, methods1));
+      assertEquals(
+          "Inspector 2 contains more methods",
+          Collections.emptySet(),
+          Sets.difference(union, methods2));
       assertEquals(clazz1.allMethods().size(), clazz2.allMethods().size());
       for (FoundMethodSubject method1 : clazz1.allMethods()) {
         MethodSubject method = clazz2.method(method1.asMethodReference());
@@ -79,7 +105,7 @@
     assertTrue(printDiffs(diffs), diffs.isEmpty());
   }
 
-  private String printDiffs(Map<DexEncodedMethod, DexEncodedMethod> diffs) {
+  private static String printDiffs(Map<DexEncodedMethod, DexEncodedMethod> diffs) {
     StringBuilder sb = new StringBuilder();
     sb.append("The following methods had differences from one dex file to the other (")
         .append(diffs.size())
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DesugaredLibraryTestBase.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DesugaredLibraryTestBase.java
index 5b87397..8a9fc12 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DesugaredLibraryTestBase.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DesugaredLibraryTestBase.java
@@ -24,7 +24,7 @@
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.ir.desugar.DesugaredLibraryConfiguration;
 import com.android.tools.r8.ir.desugar.DesugaredLibraryConfigurationParser;
-import com.android.tools.r8.ir.desugar.NestBasedAccessDesugaring;
+import com.android.tools.r8.ir.desugar.nest.NestBasedAccessDesugaring;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.tracereferences.TraceReferences;
 import com.android.tools.r8.utils.AndroidApiLevel;
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/FeatureSplitTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/FeatureSplitTest.java
index d4f08fc..9447057 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/FeatureSplitTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/FeatureSplitTest.java
@@ -11,6 +11,7 @@
 import com.android.tools.r8.CompilationFailedException;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestRuntime;
+import com.android.tools.r8.TestShrinkerBuilder;
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.ToolHelper.ArtCommandBuilder;
 import com.android.tools.r8.ToolHelper.ProcessResult;
@@ -247,6 +248,9 @@
                   SplitterTestBase.simpleSplitProvider(
                       builder, feature2Path, temp, FeatureClass2.class))
           .addKeepAllClassesRule()
+          .applyIf(
+              parameters.getApiLevel().isLessThan(AndroidApiLevel.N),
+              TestShrinkerBuilder::addDontWarnEmulatedLibraryClasses)
           .enableCoreLibraryDesugaring(parameters.getApiLevel(), keepRuleConsumer)
           .compile()
           .writeToZip(basePath);
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/IterableTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/IterableTest.java
index f998b89..0c16701 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/IterableTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/IterableTest.java
@@ -19,7 +19,6 @@
 import java.util.function.Consumer;
 import java.util.stream.Stream;
 import java.util.stream.StreamSupport;
-import org.jetbrains.annotations.NotNull;
 import org.junit.Assume;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -138,7 +137,6 @@
       this.collection = collection;
     }
 
-    @NotNull
     @Override
     public Iterator<E> iterator() {
       return collection.iterator();
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/JavaTimeTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/JavaTimeTest.java
index 31bbcbd..ff7577f 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/JavaTimeTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/JavaTimeTest.java
@@ -14,6 +14,7 @@
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestCompileResult;
 import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestShrinkerBuilder;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.BooleanUtils;
@@ -264,6 +265,9 @@
         testForR8(parameters.getBackend())
             .addInnerClasses(JavaTimeTest.class)
             .addKeepMainRule(TestClass.class)
+            .applyIf(
+                parameters.getApiLevel().isLessThan(AndroidApiLevel.O),
+                TestShrinkerBuilder::addDontWarnRetargetLibraryMembers)
             .enableNoVerticalClassMergingAnnotations()
             .setMinApi(parameters.getApiLevel())
             .enableCoreLibraryDesugaring(parameters.getApiLevel(), keepRuleConsumer)
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/MinimalInterfaceSuperTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/MinimalInterfaceSuperTest.java
index 616ec2c..6c443af 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/MinimalInterfaceSuperTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/MinimalInterfaceSuperTest.java
@@ -6,13 +6,13 @@
 
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.StringUtils;
 import java.util.AbstractCollection;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.Iterator;
 import java.util.function.Predicate;
-import org.jetbrains.annotations.NotNull;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
@@ -46,6 +46,9 @@
     testForR8(parameters.getBackend())
         .addInnerClasses(MinimalInterfaceSuperTest.class)
         .addKeepMainRule(Main.class)
+        .applyIf(
+            parameters.getApiLevel().isLessThan(AndroidApiLevel.N),
+            builder -> builder.addDontWarnVivifiedClass(Predicate.class))
         .setMinApi(parameters.getApiLevel())
         .enableCoreLibraryDesugaring(parameters.getApiLevel())
         .compile()
@@ -71,7 +74,6 @@
   }
 
   static class Col1<E> extends AbstractCollection<E> implements Col1Itf<E> {
-    @NotNull
     @Override
     public Iterator<E> iterator() {
       return Collections.emptyIterator();
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/NoDesugaredLibraryDexFileTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/NoDesugaredLibraryDexFileTest.java
index a6ea994..f0e55c0 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/NoDesugaredLibraryDexFileTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/NoDesugaredLibraryDexFileTest.java
@@ -16,8 +16,6 @@
 import java.util.Iterator;
 import java.util.List;
 import java.util.SortedSet;
-import org.jetbrains.annotations.NotNull;
-import org.jetbrains.annotations.Nullable;
 import org.junit.Assume;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -97,25 +95,21 @@
   // Implements directly a core library interface which implements other library interfaces.
   static class CustomSortedSet<E> implements SortedSet<E> {
 
-    @Nullable
     @Override
     public Comparator<? super E> comparator() {
       return null;
     }
 
-    @NotNull
     @Override
     public SortedSet<E> subSet(E fromElement, E toElement) {
       return new CustomSortedSet<>();
     }
 
-    @NotNull
     @Override
     public SortedSet<E> headSet(E toElement) {
       return new CustomSortedSet<>();
     }
 
-    @NotNull
     @Override
     public SortedSet<E> tailSet(E fromElement) {
       return new CustomSortedSet<>();
@@ -146,21 +140,18 @@
       return false;
     }
 
-    @NotNull
     @Override
     public Iterator<E> iterator() {
       return Collections.emptyIterator();
     }
 
-    @NotNull
     @Override
     public Object[] toArray() {
       return new Object[0];
     }
 
-    @NotNull
     @Override
-    public <T> T[] toArray(@NotNull T[] a) {
+    public <T> T[] toArray(T[] a) {
       return a;
     }
 
@@ -175,7 +166,7 @@
     }
 
     @Override
-    public boolean addAll(@NotNull Collection c) {
+    public boolean addAll(Collection c) {
       return false;
     }
 
@@ -183,17 +174,17 @@
     public void clear() {}
 
     @Override
-    public boolean removeAll(@NotNull Collection c) {
+    public boolean removeAll(Collection c) {
       return false;
     }
 
     @Override
-    public boolean retainAll(@NotNull Collection c) {
+    public boolean retainAll(Collection c) {
       return false;
     }
 
     @Override
-    public boolean containsAll(@NotNull Collection c) {
+    public boolean containsAll(Collection c) {
       return false;
     }
   }
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/NoForwardingMethodsTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/NoForwardingMethodsTest.java
index 98ad7dc..da41454 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/NoForwardingMethodsTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/NoForwardingMethodsTest.java
@@ -7,6 +7,7 @@
 import static org.junit.Assert.assertTrue;
 
 import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.BooleanUtils;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import java.util.ArrayList;
@@ -17,8 +18,6 @@
 import java.util.List;
 import java.util.Objects;
 import java.util.SortedSet;
-import org.jetbrains.annotations.NotNull;
-import org.jetbrains.annotations.Nullable;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
@@ -67,6 +66,9 @@
         .setMinApi(parameters.getApiLevel())
         .addKeepClassAndMembersRules(Executor.class)
         .enableCoreLibraryDesugaring(parameters.getApiLevel(), keepRuleConsumer)
+        .applyIf(
+            parameters.getApiLevel().isLessThan(AndroidApiLevel.N),
+            builder -> builder.addDontWarnEmulatedLibraryClass(Collection.class))
         .compile()
         .inspect(this::assertNoForwardingStreamMethod)
         .addDesugaredCoreLibraryRunClassPath(
@@ -103,25 +105,21 @@
   // Implements directly a core library interface which implements other library interfaces.
   static class CustomSortedSet<E> implements SortedSet<E> {
 
-    @Nullable
     @Override
     public Comparator<? super E> comparator() {
       return null;
     }
 
-    @NotNull
     @Override
     public SortedSet<E> subSet(E fromElement, E toElement) {
       return new CustomSortedSet<>();
     }
 
-    @NotNull
     @Override
     public SortedSet<E> headSet(E toElement) {
       return new CustomSortedSet<>();
     }
 
-    @NotNull
     @Override
     public SortedSet<E> tailSet(E fromElement) {
       return new CustomSortedSet<>();
@@ -152,21 +150,18 @@
       return false;
     }
 
-    @NotNull
     @Override
     public Iterator<E> iterator() {
       return Collections.emptyIterator();
     }
 
-    @NotNull
     @Override
     public Object[] toArray() {
       return new Object[0];
     }
 
-    @NotNull
     @Override
-    public <T> T[] toArray(@NotNull T[] a) {
+    public <T> T[] toArray(T[] a) {
       return a;
     }
 
@@ -181,7 +176,7 @@
     }
 
     @Override
-    public boolean addAll(@NotNull Collection c) {
+    public boolean addAll(Collection c) {
       return false;
     }
 
@@ -189,17 +184,17 @@
     public void clear() {}
 
     @Override
-    public boolean removeAll(@NotNull Collection c) {
+    public boolean removeAll(Collection c) {
       return false;
     }
 
     @Override
-    public boolean retainAll(@NotNull Collection c) {
+    public boolean retainAll(Collection c) {
       return false;
     }
 
     @Override
-    public boolean containsAll(@NotNull Collection c) {
+    public boolean containsAll(Collection c) {
       return false;
     }
   }
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/ObjectsTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/ObjectsTest.java
index 8031f43..f62067c 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/ObjectsTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/ObjectsTest.java
@@ -421,8 +421,8 @@
   private String expectedOutput(boolean objectsRequireNonNullWithSupplierSupported) {
     return StringUtils.lines(
         "1",
-        "true",
-        "true",
+        "false",
+        "false",
         Objects.toString(Objects.hash(1, 2)),
         "4",
         "NPE",
@@ -525,8 +525,8 @@
       boolean objectsRequireNonNullWithSupplierSupported = Boolean.parseBoolean(args[0]);
       // Android K methods.
       objectsCompare("b", "a");
-      objectsDeepEquals(args, args);
-      objectsEquals(args, args);
+      objectsDeepEquals(args, new Object());
+      objectsEquals(args, new Object());
       objectsHash(1, 2);
       objectsHashCode(4);
       objectsRequireNonNull(System.currentTimeMillis() >= 0 ? null : new Object());
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/ProgramRewritingTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/ProgramRewritingTest.java
index 69f366f..07f4415 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/ProgramRewritingTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/ProgramRewritingTest.java
@@ -14,6 +14,7 @@
 import com.android.tools.r8.D8TestRunResult;
 import com.android.tools.r8.R8TestRunResult;
 import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestShrinkerBuilder;
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.ToolHelper.DexVm;
 import com.android.tools.r8.utils.AndroidApiLevel;
@@ -120,6 +121,9 @@
           testForR8(parameters.getBackend())
               .minification(minifying)
               .addKeepMainRule(TEST_CLASS)
+              .applyIf(
+                  parameters.getApiLevel().isLessThan(AndroidApiLevel.N),
+                  TestShrinkerBuilder::addDontWarnEmulatedLibraryClasses)
               .addProgramFiles(Paths.get(ToolHelper.EXAMPLES_JAVA9_BUILD_DIR + "stream.jar"))
               .setMinApi(parameters.getApiLevel())
               .addOptionsModification(
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/RetargetOverrideTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/RetargetOverrideTest.java
index 59dbd1b..3eb81ed 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/RetargetOverrideTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/RetargetOverrideTest.java
@@ -5,7 +5,6 @@
 package com.android.tools.r8.desugar.desugaredlibrary;
 
 import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.ToolHelper.DexVm.Version;
 import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.BooleanUtils;
 import java.time.Instant;
@@ -64,6 +63,13 @@
         testForR8(Backend.DEX)
             .addKeepMainRule(Executor.class)
             .addInnerClasses(RetargetOverrideTest.class)
+            .applyIf(
+                parameters.getApiLevel().isLessThan(AndroidApiLevel.O),
+                builder ->
+                    builder
+                        .addDontWarnRetargetLibraryMembers()
+                        .addDontWarnTimeConversions()
+                        .addDontWarnVivifiedClasses())
             .enableCoreLibraryDesugaring(parameters.getApiLevel(), keepRuleConsumer)
             .setMinApi(parameters.getApiLevel())
             .compile()
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/SynchronizedCollectionTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/SynchronizedCollectionTest.java
index 2ad9b4f..9496b9c4 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/SynchronizedCollectionTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/SynchronizedCollectionTest.java
@@ -6,7 +6,9 @@
 
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestRuntime.CfVm;
+import com.android.tools.r8.TestShrinkerBuilder;
 import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.BooleanUtils;
 import com.android.tools.r8.utils.StringUtils;
 import java.nio.file.Path;
@@ -79,6 +81,9 @@
     testForR8(parameters.getBackend())
         .addProgramFiles(INPUT_JAR)
         .addKeepMainRule(MAIN_CLASS)
+        .applyIf(
+            parameters.getApiLevel().isLessThan(AndroidApiLevel.N),
+            TestShrinkerBuilder::addDontWarnEmulatedLibraryClasses)
         .setMinApi(parameters.getApiLevel())
         .enableCoreLibraryDesugaring(parameters.getApiLevel(), keepRuleConsumer)
         .compile()
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/CallBackConversionTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/CallBackConversionTest.java
index cb8c24f..42997cb 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/CallBackConversionTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/CallBackConversionTest.java
@@ -172,6 +172,9 @@
         .setMinApi(parameters.getApiLevel())
         .addProgramClasses(Impl.class)
         .addLibraryClasses(CustomLibClass.class)
+        .applyIf(
+            parameters.getApiLevel().isLessThan(AndroidApiLevel.N),
+            builder -> builder.addDontWarnVivifiedClass(Consumer.class))
         .enableCoreLibraryDesugaring(parameters.getApiLevel(), keepRuleConsumer)
         .compile()
         .inspect(this::assertLibraryOverridesThere)
@@ -189,6 +192,7 @@
         .setMinApi(parameters.getApiLevel())
         .addProgramClasses(Impl.class)
         .addLibraryClasses(CustomLibClass.class)
+        .addDontWarnVivifiedClass(Consumer.class)
         .enableCoreLibraryDesugaring(parameters.getApiLevel(), keepRuleConsumer)
         .compile()
         .inspect(this::assertLibraryOverridesThere)
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/ConversionIntroduceInterfaceMethodTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/ConversionIntroduceInterfaceMethodTest.java
index 5c01507..1298096 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/ConversionIntroduceInterfaceMethodTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/ConversionIntroduceInterfaceMethodTest.java
@@ -23,7 +23,6 @@
 import java.util.List;
 import java.util.function.Consumer;
 import java.util.stream.Collectors;
-import org.jetbrains.annotations.NotNull;
 import org.junit.BeforeClass;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -155,6 +154,10 @@
                 opt.desugaredLibraryConfiguration =
                     configurationWithSupportAllCallbacksFromLibrary(
                         opt, false, parameters, supportAllCallbacksFromLibrary))
+        .applyIf(
+            parameters.getApiLevel().isLessThan(AndroidApiLevel.N)
+                && supportAllCallbacksFromLibrary,
+            builder -> builder.addDontWarnVivifiedClass(Consumer.class))
         .compile()
         .inspect(this::assertDoubleForEach)
         .inspect(this::assertWrapperMethodsPresent)
@@ -224,15 +227,13 @@
       return Collections.<E>singletonList(null).iterator();
     }
 
-    @NotNull
     @Override
     public Object[] toArray() {
       return new Object[0];
     }
 
-    @NotNull
     @Override
-    public <T> T[] toArray(@NotNull T[] a) {
+    public <T> T[] toArray(T[] a) {
       return null;
     }
 
@@ -247,22 +248,22 @@
     }
 
     @Override
-    public boolean containsAll(@NotNull Collection<?> c) {
+    public boolean containsAll(Collection<?> c) {
       return false;
     }
 
     @Override
-    public boolean addAll(@NotNull Collection<? extends E> c) {
+    public boolean addAll(Collection<? extends E> c) {
       return false;
     }
 
     @Override
-    public boolean removeAll(@NotNull Collection<?> c) {
+    public boolean removeAll(Collection<?> c) {
       return false;
     }
 
     @Override
-    public boolean retainAll(@NotNull Collection<?> c) {
+    public boolean retainAll(Collection<?> c) {
       return false;
     }
 
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/DuplicateAPIProgramTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/DuplicateAPIProgramTest.java
index b62045b..8fa7780 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/DuplicateAPIProgramTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/DuplicateAPIProgramTest.java
@@ -84,6 +84,9 @@
             .addKeepMainRule(Executor.class)
             .addProgramClasses(Executor.class, MyMap.class)
             .addLibraryClasses(CustomLibClass.class)
+            .applyIf(
+                parameters.getApiLevel().isLessThan(AndroidApiLevel.N),
+                builder -> builder.addDontWarnVivifiedClass(BiConsumer.class))
             .enableCoreLibraryDesugaring(parameters.getApiLevel(), keepRuleConsumer)
             .compile()
             .inspect(this::assertDupMethod)
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/FunctionConversionTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/FunctionConversionTest.java
index 6afc3a5..24588c6 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/FunctionConversionTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/FunctionConversionTest.java
@@ -174,7 +174,7 @@
 
       private Object1 field;
 
-      private Object2(Object1 o) {
+      Object2(Object1 o) {
         this.field = o;
       }
     }
@@ -183,7 +183,7 @@
 
       private Object2 field;
 
-      private Object3(Object2 o) {
+      Object3(Object2 o) {
         this.field = o;
       }
 
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/gson/GetGenericInterfaceTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/gson/GetGenericInterfaceTest.java
index 249f26f..0e66723 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/gson/GetGenericInterfaceTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/gson/GetGenericInterfaceTest.java
@@ -9,6 +9,7 @@
 
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.desugar.desugaredlibrary.DesugaredLibraryTestBase;
+import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.BooleanUtils;
 import dalvik.system.PathClassLoader;
 import java.sql.SQLDataException;
@@ -24,8 +25,6 @@
 import java.util.Set;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.ConcurrentMap;
-import org.jetbrains.annotations.NotNull;
-import org.jetbrains.annotations.Nullable;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
@@ -75,6 +74,11 @@
         testForR8(Backend.DEX)
             .addInnerClasses(GetGenericInterfaceTest.class)
             .addKeepMainRule(Executor.class)
+            .applyIf(
+                parameters.getApiLevel().isLessThan(AndroidApiLevel.O),
+                builder ->
+                    builder.addDontWarnRetargetLibraryMember(
+                        "virtualDispatch$Date$toInstant$dispatchInterface"))
             .noMinification()
             .setMinApi(parameters.getApiLevel())
             .enableCoreLibraryDesugaring(parameters.getApiLevel(), keepRuleConsumer)
@@ -301,7 +305,6 @@
       return null;
     }
 
-    @Nullable
     @Override
     public V put(K k, V v) {
       return null;
@@ -313,46 +316,43 @@
     }
 
     @Override
-    public void putAll(@NotNull Map<? extends K, ? extends V> map) {}
+    public void putAll(Map<? extends K, ? extends V> map) {}
 
     @Override
     public void clear() {}
 
-    @NotNull
     @Override
     public Set<K> keySet() {
       return null;
     }
 
-    @NotNull
     @Override
     public Collection<V> values() {
       return null;
     }
 
-    @NotNull
     @Override
     public Set<Entry<K, V>> entrySet() {
       return null;
     }
 
     @Override
-    public V putIfAbsent(@NotNull K k, V v) {
+    public V putIfAbsent(K k, V v) {
       return null;
     }
 
     @Override
-    public boolean remove(@NotNull Object o, Object o1) {
+    public boolean remove(Object o, Object o1) {
       return false;
     }
 
     @Override
-    public boolean replace(@NotNull K k, @NotNull V v, @NotNull V v1) {
+    public boolean replace(K k, V v, V v1) {
       return false;
     }
 
     @Override
-    public V replace(@NotNull K k, @NotNull V v) {
+    public V replace(K k, V v) {
       return null;
     }
   }
@@ -394,7 +394,6 @@
       return null;
     }
 
-    @Nullable
     @Override
     public V put(K k, V v) {
       return null;
@@ -406,46 +405,43 @@
     }
 
     @Override
-    public void putAll(@NotNull Map<? extends K, ? extends V> map) {}
+    public void putAll(Map<? extends K, ? extends V> map) {}
 
     @Override
     public void clear() {}
 
-    @NotNull
     @Override
     public Set<K> keySet() {
       return null;
     }
 
-    @NotNull
     @Override
     public Collection<V> values() {
       return null;
     }
 
-    @NotNull
     @Override
     public Set<Entry<K, V>> entrySet() {
       return null;
     }
 
     @Override
-    public V putIfAbsent(@NotNull K k, V v) {
+    public V putIfAbsent(K k, V v) {
       return null;
     }
 
     @Override
-    public boolean remove(@NotNull Object o, Object o1) {
+    public boolean remove(Object o, Object o1) {
       return false;
     }
 
     @Override
-    public boolean replace(@NotNull K k, @NotNull V v, @NotNull V v1) {
+    public boolean replace(K k, V v, V v1) {
       return false;
     }
 
     @Override
-    public V replace(@NotNull K k, @NotNull V v) {
+    public V replace(K k, V v) {
       return null;
     }
 
@@ -475,21 +471,18 @@
       return false;
     }
 
-    @NotNull
     @Override
     public Iterator<E> iterator() {
       return null;
     }
 
-    @NotNull
     @Override
     public Object[] toArray() {
       return new Object[0];
     }
 
-    @NotNull
     @Override
-    public <T> T[] toArray(@NotNull T[] ts) {
+    public <T> T[] toArray(T[] ts) {
       return null;
     }
 
@@ -504,27 +497,27 @@
     }
 
     @Override
-    public boolean containsAll(@NotNull Collection<?> collection) {
+    public boolean containsAll(Collection<?> collection) {
       return false;
     }
 
     @Override
-    public boolean addAll(@NotNull Collection<? extends E> collection) {
+    public boolean addAll(Collection<? extends E> collection) {
       return false;
     }
 
     @Override
-    public boolean addAll(int i, @NotNull Collection<? extends E> collection) {
+    public boolean addAll(int i, Collection<? extends E> collection) {
       return false;
     }
 
     @Override
-    public boolean removeAll(@NotNull Collection<?> collection) {
+    public boolean removeAll(Collection<?> collection) {
       return false;
     }
 
     @Override
-    public boolean retainAll(@NotNull Collection<?> collection) {
+    public boolean retainAll(Collection<?> collection) {
       return false;
     }
 
@@ -559,19 +552,16 @@
       return 0;
     }
 
-    @NotNull
     @Override
     public ListIterator<E> listIterator() {
       return null;
     }
 
-    @NotNull
     @Override
     public ListIterator<E> listIterator(int i) {
       return null;
     }
 
-    @NotNull
     @Override
     public List<E> subList(int i, int i1) {
       return null;
@@ -603,21 +593,18 @@
       return false;
     }
 
-    @NotNull
     @Override
     public Iterator<E> iterator() {
       return null;
     }
 
-    @NotNull
     @Override
     public Object[] toArray() {
       return new Object[0];
     }
 
-    @NotNull
     @Override
-    public <T> T[] toArray(@NotNull T[] ts) {
+    public <T> T[] toArray(T[] ts) {
       return null;
     }
 
@@ -632,27 +619,27 @@
     }
 
     @Override
-    public boolean containsAll(@NotNull Collection<?> collection) {
+    public boolean containsAll(Collection<?> collection) {
       return false;
     }
 
     @Override
-    public boolean addAll(@NotNull Collection<? extends E> collection) {
+    public boolean addAll(Collection<? extends E> collection) {
       return false;
     }
 
     @Override
-    public boolean addAll(int i, @NotNull Collection<? extends E> collection) {
+    public boolean addAll(int i, Collection<? extends E> collection) {
       return false;
     }
 
     @Override
-    public boolean removeAll(@NotNull Collection<?> collection) {
+    public boolean removeAll(Collection<?> collection) {
       return false;
     }
 
     @Override
-    public boolean retainAll(@NotNull Collection<?> collection) {
+    public boolean retainAll(Collection<?> collection) {
       return false;
     }
 
@@ -687,19 +674,16 @@
       return 0;
     }
 
-    @NotNull
     @Override
     public ListIterator<E> listIterator() {
       return null;
     }
 
-    @NotNull
     @Override
     public ListIterator<E> listIterator(int i) {
       return null;
     }
 
-    @NotNull
     @Override
     public List<E> subList(int i, int i1) {
       return null;
@@ -710,7 +694,6 @@
   }
 
   static class CollectionMapImplements2<R, C> implements Iterable<R>, Map<R, C> {
-    @NotNull
     @Override
     public Iterator<R> iterator() {
       return null;
@@ -741,7 +724,6 @@
       return null;
     }
 
-    @Nullable
     @Override
     public C put(R r, C c) {
       return null;
@@ -753,24 +735,21 @@
     }
 
     @Override
-    public void putAll(@NotNull Map<? extends R, ? extends C> map) {}
+    public void putAll(Map<? extends R, ? extends C> map) {}
 
     @Override
     public void clear() {}
 
-    @NotNull
     @Override
     public Set<R> keySet() {
       return null;
     }
 
-    @NotNull
     @Override
     public Collection<C> values() {
       return null;
     }
 
-    @NotNull
     @Override
     public Set<Entry<R, C>> entrySet() {
       return null;
@@ -778,7 +757,6 @@
   }
 
   static class CollectionMapExtendImplement<R, C> extends HashMap<R, C> implements Iterable<R> {
-    @NotNull
     @Override
     public Iterator<R> iterator() {
       return null;
@@ -809,7 +787,6 @@
       return null;
     }
 
-    @Nullable
     @Override
     public C put(R r, C c) {
       return null;
@@ -821,24 +798,21 @@
     }
 
     @Override
-    public void putAll(@NotNull Map<? extends R, ? extends C> map) {}
+    public void putAll(Map<? extends R, ? extends C> map) {}
 
     @Override
     public void clear() {}
 
-    @NotNull
     @Override
     public Set<R> keySet() {
       return null;
     }
 
-    @NotNull
     @Override
     public Collection<C> values() {
       return null;
     }
 
-    @NotNull
     @Override
     public Set<Entry<R, C>> entrySet() {
       return null;
@@ -847,7 +821,6 @@
 
   static class CollectionMapImplements2Integer1<C>
       implements Iterable<PathClassLoader>, Map<PathClassLoader, C> {
-    @NotNull
     @Override
     public Iterator<PathClassLoader> iterator() {
       return null;
@@ -878,7 +851,6 @@
       return null;
     }
 
-    @Nullable
     @Override
     public C put(PathClassLoader unsafe, C c) {
       return null;
@@ -890,24 +862,21 @@
     }
 
     @Override
-    public void putAll(@NotNull Map<? extends PathClassLoader, ? extends C> map) {}
+    public void putAll(Map<? extends PathClassLoader, ? extends C> map) {}
 
     @Override
     public void clear() {}
 
-    @NotNull
     @Override
     public Set<PathClassLoader> keySet() {
       return null;
     }
 
-    @NotNull
     @Override
     public Collection<C> values() {
       return null;
     }
 
-    @NotNull
     @Override
     public Set<Entry<PathClassLoader, C>> entrySet() {
       return null;
@@ -916,7 +885,6 @@
 
   static class CollectionMapExtendImplementInteger1<C> extends HashMap<PathClassLoader, C>
       implements Iterable<PathClassLoader> {
-    @NotNull
     @Override
     public Iterator<PathClassLoader> iterator() {
       return null;
@@ -924,7 +892,6 @@
   }
 
   static class CollectionMapImplements2Integer2<R> implements Iterable<R>, Map<R, PathClassLoader> {
-    @NotNull
     @Override
     public Iterator<R> iterator() {
       return null;
@@ -955,7 +922,6 @@
       return null;
     }
 
-    @Nullable
     @Override
     public PathClassLoader put(R r, PathClassLoader unsafe) {
       return null;
@@ -967,24 +933,21 @@
     }
 
     @Override
-    public void putAll(@NotNull Map<? extends R, ? extends PathClassLoader> map) {}
+    public void putAll(Map<? extends R, ? extends PathClassLoader> map) {}
 
     @Override
     public void clear() {}
 
-    @NotNull
     @Override
     public Set<R> keySet() {
       return null;
     }
 
-    @NotNull
     @Override
     public Collection<PathClassLoader> values() {
       return null;
     }
 
-    @NotNull
     @Override
     public Set<Entry<R, PathClassLoader>> entrySet() {
       return null;
@@ -993,7 +956,6 @@
 
   static class CollectionMapExtendImplementInteger2<R> extends HashMap<R, PathClassLoader>
       implements Iterable<R> {
-    @NotNull
     @Override
     public Iterator<R> iterator() {
       return null;
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/gson/GsonAllMapsTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/gson/GsonAllMapsTest.java
index 45e6af7..d5a6637 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/gson/GsonAllMapsTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/gson/GsonAllMapsTest.java
@@ -61,6 +61,7 @@
         .addProgramFiles(GSON_2_8_1_JAR)
         .addKeepMainRule(AllMapsTestClass.class)
         .addKeepRuleFiles(GSON_CONFIGURATION)
+        .allowUnusedDontWarnPatterns()
         .allowUnusedProguardConfigurationRules()
         .allowDiagnosticMessages()
         .enableCoreLibraryDesugaring(parameters.getApiLevel(), keepRuleConsumer)
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/gson/GsonOptionalTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/gson/GsonOptionalTest.java
index d3fd68a..62cf8d2 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/gson/GsonOptionalTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/gson/GsonOptionalTest.java
@@ -56,6 +56,7 @@
         .addProgramFiles(GSON_2_8_1_JAR)
         .addKeepMainRule(OptionalTestClass.class)
         .addKeepRuleFiles(GSON_CONFIGURATION)
+        .allowUnusedDontWarnPatterns()
         .allowUnusedProguardConfigurationRules()
         .addOptionsModification(opt -> opt.ignoreMissingClasses = true)
         .allowDiagnosticMessages()
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdktests/Jdk11MathTests.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdktests/Jdk11MathTests.java
index 8a07522..2c62b8f 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdktests/Jdk11MathTests.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdktests/Jdk11MathTests.java
@@ -113,6 +113,7 @@
         .addKeepMainRule(DIVMOD)
         .addKeepMainRule(EXACTARITH)
         .addProgramFiles(JDK_11_MATH_TEST_CLASS_FILES)
+        .addLibraryFiles(ToolHelper.getMostRecentAndroidJar())
         .setMinApi(parameters.getRuntime())
         .run(parameters.getRuntime(), EXACTARITH)
         .assertSuccessWithOutput("");
@@ -124,6 +125,7 @@
         .addKeepMainRule(DIVMOD)
         .addKeepMainRule(EXACTARITH)
         .addProgramFiles(JDK_11_MATH_TEST_CLASS_FILES)
+        .addLibraryFiles(ToolHelper.getMostRecentAndroidJar())
         .setMinApi(parameters.getRuntime())
         .run(parameters.getRuntime(), DIVMOD)
         .assertSuccessWithOutput("");
@@ -134,6 +136,7 @@
     testForR8(parameters.getBackend())
         .addProgramFiles(JDK_11_STRICT_MATH_TEST_CLASS_FILES)
         .addKeepMainRule(EXACTARITH)
+        .addLibraryFiles(ToolHelper.getMostRecentAndroidJar())
         .setMinApi(parameters.getRuntime())
         .run(parameters.getRuntime(), EXACTARITH)
         .assertSuccessWithOutput("");
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdktests/Jdk11StreamTests.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdktests/Jdk11StreamTests.java
index 394f36c..0901c28 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdktests/Jdk11StreamTests.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdktests/Jdk11StreamTests.java
@@ -287,14 +287,17 @@
       D8TestRunResult result =
           compileResult.run(
               parameters.getRuntime(), "TestNGMainRunner", verbosity, runnableTests.get(path));
-      if (result.getStdOut().contains("java.lang.NoSuchMethodError")
-          && Arrays.stream(missingDesugaredMethods())
-              .anyMatch(method -> result.getStdOut().contains(method))) {
+      String stdout = result.getStdOut();
+      if (stdout.contains("java.lang.NoSuchMethodError")
+          && Arrays.stream(missingDesugaredMethods()).anyMatch(stdout::contains)) {
         // TODO(b/134732760): support Java 9 APIs.
-      } else if (result.getStdOut().contains("in class Ljava/util/Random")
-          && result.getStdOut().contains("java.lang.NoSuchMethodError")) {
+      } else if (stdout.contains("java.lang.NoSuchMethodError")
+          && stdout.contains("org.openjdk.tests.java.util.stream.IterateTest.testIterate")) {
+        // TODO(b/134732760): support Java 9 APIs.
+      } else if (stdout.contains("in class Ljava/util/Random")
+          && stdout.contains("java.lang.NoSuchMethodError")) {
         // TODO(b/134732760): Random Java 9 Apis, support or do not use them.
-      } else if (result.getStdOut().contains("java.lang.AssertionError")) {
+      } else if (stdout.contains("java.lang.AssertionError")) {
         // TODO(b/134732760): Investigate and fix these issues.
       } else {
         String errorMessage = "STDOUT:\n" + result.getStdOut() + "STDERR:\n" + result.getStdErr();
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 89748a7..aa5393a 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
@@ -133,7 +133,8 @@
             .addKeepAllClassesRule()
             .addKeepAttributes(ProguardKeepAttributes.RUNTIME_VISIBLE_ANNOTATIONS)
             .setMinApi(parameters.getApiLevel())
-            .allowDiagnosticWarningMessages();
+            .allowDiagnosticWarningMessages()
+            .addDontWarnJetBrains();
     KeepRuleConsumer keepRuleConsumer = null;
     if (desugarLibrary) {
       keepRuleConsumer = createKeepRuleConsumer(parameters);
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/r8ondex/R8CompiledThroughDexTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/r8ondex/R8CompiledThroughDexTest.java
index b61e9d8..bb66f2e 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/r8ondex/R8CompiledThroughDexTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/r8ondex/R8CompiledThroughDexTest.java
@@ -7,7 +7,11 @@
 import static junit.framework.TestCase.assertEquals;
 import static org.junit.Assert.assertTrue;
 
+import com.android.tools.r8.CompilationMode;
+import com.android.tools.r8.OutputMode;
 import com.android.tools.r8.R8;
+import com.android.tools.r8.R8Command;
+import com.android.tools.r8.R8Command.Builder;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.TestRuntime;
@@ -16,10 +20,15 @@
 import com.android.tools.r8.ToolHelper.ProcessResult;
 import com.android.tools.r8.cf.bootstrap.BootstrapCurrentEqualityTest;
 import com.android.tools.r8.desugar.desugaredlibrary.DesugaredLibraryTestBase;
-import java.io.File;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.Pair;
+import com.google.common.collect.ImmutableList;
 import java.nio.file.Path;
 import java.nio.file.Paths;
+import java.util.ArrayList;
 import java.util.Collections;
+import java.util.List;
+import java.util.function.Consumer;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
@@ -29,18 +38,16 @@
 @RunWith(Parameterized.class)
 public class R8CompiledThroughDexTest extends DesugaredLibraryTestBase {
 
-  private static final boolean testExternal = true;
+  private static final boolean minify = false;
 
   private final TestParameters parameters;
 
   @Parameters(name = "{0}")
   public static TestParametersCollection data() {
-    // We do not use withDexRuntimesStartingAtIncluding to exclude dex-default and therefore
-    // avoid this 2 * 8 minutes test running on tools/test.py.
+    // We only run this test with ART 8 and with full desugaring to avoid the large runtime on ART.
     return getTestParameters()
         .withDexRuntime(Version.V8_1_0)
-        .withDexRuntime(Version.V9_0_0)
-        .withAllApiLevels()
+        .withApiLevel(AndroidApiLevel.B)
         .build();
   }
 
@@ -49,13 +56,61 @@
   }
 
   private static String commandLinePathFor(String string) {
+    return commandLinePathFor(Paths.get(string));
+  }
+
+  private static String commandLinePathFor(Path path) {
     // We switch to absolute path due to the art frameworks requiring to run the command in a
     // different folder.
-    return Paths.get(string).toAbsolutePath().toString();
+    return path.toAbsolutePath().toString();
   }
 
   private static final String R8_KEEP = Paths.get("src/main/keep.txt").toAbsolutePath().toString();
 
+  private Pair<List<String>, Consumer<Builder>> buildArguments() {
+    ImmutableList.Builder<String> arguments = ImmutableList.builder();
+    List<Consumer<Builder>> buildup = new ArrayList<>();
+
+    arguments.add(commandLinePathFor(ToolHelper.R8_WITH_RELOCATED_DEPS_JAR));
+    buildup.add(b -> b.addProgramFiles(ToolHelper.R8_WITH_RELOCATED_DEPS_JAR));
+
+    arguments.add("--release");
+    buildup.add(b -> b.setMode(CompilationMode.RELEASE));
+
+    arguments.add("--min-api").add(Integer.toString(parameters.getApiLevel().getLevel()));
+    buildup.add(b -> b.setMinApiLevel(parameters.getApiLevel().getLevel()));
+
+    arguments.add("--lib").add(commandLinePathFor(ToolHelper.JAVA_8_RUNTIME));
+    buildup.add(b -> b.addLibraryFiles(ToolHelper.getJava8RuntimeJar()));
+
+    arguments.add("--pg-conf").add(commandLinePathFor(R8_KEEP));
+    buildup.add(b -> b.addProguardConfigurationFiles(Paths.get(R8_KEEP)));
+
+    if (!minify) {
+      arguments.add("--no-minification");
+      buildup.add(b -> b.setDisableMinification(true));
+    }
+
+    Consumer<Builder> consumer = b -> {};
+    for (Consumer<Builder> step : buildup) {
+      consumer = consumer.andThen(step);
+    }
+
+    return new Pair<>(arguments.build(), consumer);
+  }
+
+  private List<String> getSharedArguments() {
+    return buildArguments().getFirst();
+  }
+
+  private Consumer<Builder> getSharedBuilder() {
+    return buildArguments().getSecond();
+  }
+
+  private void printTime(String title, long start) {
+    System.out.println(title + ": " + ((System.nanoTime() - start) / 1000000000) + "s");
+  }
+
   @Test
   public void testR8CompiledWithR8Dex() throws Exception {
     // Compile once R8_WITH_RELOCATED_DEPS_JAR using normal R8_WITH_RELOCATED_DEPS_JAR to dex,
@@ -66,67 +121,80 @@
     // We use extra VM parameters for memory. The command parameters should look like:
     // -Xmx512m com...R8 --release --min-api 1 --output path/to/folder --lib rt.jar
     // --pg-conf R8KeepRules r8.jar
-    // The 512m memory is required to make it work on ART since default is lower.
+    // The 512m memory is required to run on ART but any higher and the runtime will fail too.
 
-    File ouputFolder = temp.newFolder("output");
+    Path outputFolder = temp.newFolder("output").toPath();
+    Path outputThroughCf = outputFolder.resolve("outThroughCf.zip");
 
-    // Compile R8 to dex on the JVM.
-    Path outputThroughCf = ouputFolder.toPath().resolve("outThroughCf.zip").toAbsolutePath();
-    if (testExternal) {
+    // First run compiles with R8 in process and thus with assertions.
+    {
+      long start = System.nanoTime();
+      // Manually construct the R8 command as the test builder will change defaults compared
+      // to the CLI invocation (eg, compressed and pg-map output).
+      Builder builder = R8Command.builder().setOutput(outputThroughCf, OutputMode.DexIndexed);
+      getSharedBuilder().accept(builder);
+      R8.run(builder.build());
+      printTime("R8/JVM in-process", start);
+    }
+
+    // Second run compiles with R8 externally checking it to be equal with the first compilation.
+    // If this fails, likely due to non-determinism, then that is much faster than waiting for the
+    // Art run compilation to finish if the same error can be found directly.
+    {
+      long start = System.nanoTime();
+      Path outputThroughCfExternal = outputFolder.resolve("outThroughCf_external.zip");
+      Path r8jar = ToolHelper.R8_WITH_RELOCATED_DEPS_JAR;
       ProcessResult javaProcessResult =
           ToolHelper.runJava(
               TestRuntime.getCheckedInJdk9(),
-              Collections.singletonList(ToolHelper.R8_WITH_RELOCATED_DEPS_JAR),
-              "-Xmx512m",
-              R8.class.getTypeName(),
-              "--release",
-              "--min-api",
-              Integer.toString(parameters.getApiLevel().getLevel()),
-              "--output",
-              outputThroughCf.toString(),
-              "--lib",
-              ToolHelper.JAVA_8_RUNTIME,
-              "--pg-conf",
-              R8_KEEP,
-              ToolHelper.R8_WITH_RELOCATED_DEPS_JAR.toAbsolutePath().toString());
+              Collections.singletonList(r8jar),
+              ImmutableList.builder()
+                  .add("-Xmx1g")
+                  .add(R8.class.getTypeName())
+                  .add("--output")
+                  .add(commandLinePathFor(outputThroughCfExternal))
+                  .addAll(getSharedArguments())
+                  .build()
+                  .toArray(new String[0]));
+      printTime("R8/JVM external", start);
       assertEquals(javaProcessResult.toString(), 0, javaProcessResult.exitCode);
-    } else {
-      testForR8(parameters.getBackend())
-          .addProgramFiles(ToolHelper.R8_WITH_RELOCATED_DEPS_JAR)
-          .addLibraryFiles(ToolHelper.getJava8RuntimeJar())
-          .addKeepRuleFiles(Paths.get(R8_KEEP))
-          .setMinApi(parameters.getApiLevel())
-          .compile()
-          .writeToZip(outputThroughCf);
+      assertTrue(
+          "The output of R8/JVM in-process and R8/JVM external differ."
+              + " Make sure you have an up-to-date compilation of "
+              + r8jar
+              + ". If not, that could very likely cause the in-process run (eg, via intellij) to"
+              + " differ from the external run which uses "
+              + r8jar
+              + ". If up-to-date, the likely cause of this error is that R8 is non-deterministic.",
+          BootstrapCurrentEqualityTest.filesAreEqual(outputThroughCf, outputThroughCfExternal));
     }
 
-    // Compile R8 to Dex on Dex, using the previous dex artifact.
+    // Finally compile R8 on the ART runtime using the already compiled DEX version of R8.
     // We need the extra parameter --64 to use 64 bits frameworks.
-    Path ouputThroughDex = ouputFolder.toPath().resolve("outThroughDex.zip").toAbsolutePath();
-    ProcessResult artProcessResult =
-        ToolHelper.runArtRaw(
-            Collections.singletonList(outputThroughCf.toAbsolutePath().toString()),
-            R8.class.getTypeName(),
-            (ToolHelper.ArtCommandBuilder builder) ->
-                builder.appendArtOption("--64").appendArtOption("-Xmx512m"),
-            parameters.getRuntime().asDex().getVm(),
-            true,
-            "--release",
-            "--min-api",
-            Integer.toString(parameters.getApiLevel().getLevel()),
-            "--output",
-            ouputThroughDex.toString(),
-            "--lib",
-            commandLinePathFor(ToolHelper.JAVA_8_RUNTIME),
-            "--pg-conf",
-            commandLinePathFor(R8_KEEP),
-            ToolHelper.R8_WITH_RELOCATED_DEPS_JAR.toAbsolutePath().toString());
-    if (artProcessResult.exitCode != 0) {
-      System.out.println(artProcessResult);
+    {
+      long start = System.nanoTime();
+      Path outputThroughDex = outputFolder.resolve("outThroughDex.zip");
+      ProcessResult artProcessResult =
+          ToolHelper.runArtRaw(
+              Collections.singletonList(commandLinePathFor(outputThroughCf)),
+              R8.class.getTypeName(),
+              builder -> builder.appendArtOption("--64").appendArtOption("-Xmx512m"),
+              parameters.getRuntime().asDex().getVm(),
+              true,
+              ImmutableList.builder()
+                  .add("--output")
+                  .add(commandLinePathFor(outputThroughDex))
+                  .addAll(getSharedArguments())
+                  .build()
+                  .toArray(new String[0]));
+      printTime("R8/ART", start);
+      if (artProcessResult.exitCode != 0) {
+        System.out.println(artProcessResult);
+      }
+      assertEquals(0, artProcessResult.exitCode);
+      assertTrue(
+          "The output of R8/JVM in-process and R8/ART external differ.",
+          BootstrapCurrentEqualityTest.filesAreEqual(outputThroughCf, outputThroughDex));
     }
-    assertEquals(0, artProcessResult.exitCode);
-
-    // Ensure both generated artifacts are equal.
-    assertTrue(BootstrapCurrentEqualityTest.filesAreEqual(outputThroughCf, ouputThroughDex));
   }
 }
diff --git a/src/test/java/com/android/tools/r8/desugar/enclosingmethod/EnclosingMethodRewriteTest.java b/src/test/java/com/android/tools/r8/desugar/enclosingmethod/EnclosingMethodRewriteTest.java
index 897f4a7..3e60058 100644
--- a/src/test/java/com/android/tools/r8/desugar/enclosingmethod/EnclosingMethodRewriteTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/enclosingmethod/EnclosingMethodRewriteTest.java
@@ -3,20 +3,26 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.desugar.enclosingmethod;
 
-import static org.hamcrest.CoreMatchers.containsString;
+import static com.android.tools.r8.ir.desugar.InterfaceMethodRewriter.COMPANION_CLASS_NAME_SUFFIX;
+import static com.android.tools.r8.ir.desugar.InterfaceMethodRewriter.DEFAULT_METHOD_PREFIX;
+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.ClassFileConsumer.ArchiveConsumer;
-import com.android.tools.r8.CompilationFailedException;
 import com.android.tools.r8.R8FullTestBuilder;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.ToolHelper.DexVm;
+import com.android.tools.r8.ToolHelper.DexVm.Version;
 import com.android.tools.r8.utils.BooleanUtils;
-import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import com.google.common.collect.ImmutableList;
 import java.nio.file.Path;
 import java.util.Collection;
+import java.util.List;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
@@ -52,11 +58,6 @@
 @RunWith(Parameterized.class)
 public class EnclosingMethodRewriteTest extends TestBase {
   private static final Class<?> MAIN = TestClass.class;
-  private static final String JAVA_OUTPUT = StringUtils.lines(
-      "interface " + A.class.getName(),
-      "public default int " + A.class.getName() + ".def()",
-      "42"
-  );
 
   private final TestParameters parameters;
   private final boolean enableMinification;
@@ -64,8 +65,7 @@
   @Parameterized.Parameters(name = "{0} minification: {1}")
   public static Collection<Object[]> data() {
     return buildParameters(
-        getTestParameters().withAllRuntimes().withAllApiLevels().build(),
-        BooleanUtils.values());
+        getTestParameters().withAllRuntimesAndApiLevels().build(), BooleanUtils.values());
   }
 
   public EnclosingMethodRewriteTest(TestParameters parameters, boolean enableMinification) {
@@ -79,7 +79,7 @@
     testForJvm()
         .addTestClasspath()
         .run(parameters.getRuntime(), MAIN)
-        .assertSuccessWithOutput(JAVA_OUTPUT);
+        .assertSuccessWithOutputLines(getExpectedOutput());
   }
 
   @Test
@@ -88,7 +88,8 @@
     Path desugared = temp.newFile("desugared.jar").toPath().toAbsolutePath();
     R8FullTestBuilder builder =
         testForR8(parameters.getBackend())
-            .addProgramClasses(A.class, C.class, MAIN)
+            .addProgramClassesAndInnerClasses(A.class)
+            .addProgramClasses(C.class, MAIN)
             .addKeepMainRule(MAIN)
             .addKeepRules("-keepattributes InnerClasses,EnclosingMethod")
             .setProgramConsumer(new ArchiveConsumer(desugared))
@@ -99,41 +100,100 @@
       builder.addKeepAllClassesRule();
     }
     builder.compile().assertNoMessages();
-    try {
-      testForProguard()
-          .addProgramFiles(desugared)
-          .addKeepMainRule(MAIN)
-          .addKeepRules("-keepattributes InnerClasses,EnclosingMethod")
-          .run(parameters.getRuntime(), MAIN)
-          .assertSuccessWithOutput(JAVA_OUTPUT);
-    } catch (CompilationFailedException e) {
-      // TODO(b/70293332)
-      assertThat(e.getMessage(), containsString("unresolved references"));
-      assertThat(e.getMessage(), containsString(A.class.getName() + "$1"));
-    }
+    testForProguard()
+        .addProgramFiles(desugared)
+        .addKeepMainRule(MAIN)
+        .addKeepRules("-keepattributes InnerClasses,EnclosingMethod")
+        .run(parameters.getRuntime(), MAIN)
+        .assertSuccess();
   }
 
   @Test
   public void testR8() throws Exception {
     R8FullTestBuilder builder =
         testForR8(parameters.getBackend())
-            .addProgramClasses(A.class, C.class, MAIN)
+            .addProgramClassesAndInnerClasses(A.class)
+            .addProgramClasses(C.class, MAIN)
             .addKeepMainRule(MAIN)
             .addKeepRules("-keepattributes InnerClasses,EnclosingMethod")
             .setMinApi(parameters.getApiLevel());
     if (enableMinification) {
       builder.addKeepAllClassesRuleWithAllowObfuscation();
     } else {
-      builder.addKeepAllClassesRule();
-    }
-    String errorType = "ClassNotFoundException";
-    if (parameters.isDexRuntime()
-        && parameters.getRuntime().asDex().getVm().isOlderThanOrEqual(DexVm.ART_4_4_4_HOST)) {
-      errorType = "NoClassDefFoundError";
+      builder.noTreeShaking().noMinification();
     }
     builder
+        .compile()
+        .inspect(
+            inspect -> {
+              ClassSubject cImplSubject = inspect.clazz(A.class.getTypeName() + "$1");
+              assertThat(cImplSubject, isPresent());
+
+              ClassSubject enclosingClassSubject =
+                  inspect.clazz(
+                      parameters.canUseDefaultAndStaticInterfaceMethods()
+                          ? A.class.getTypeName()
+                          : A.class.getTypeName() + COMPANION_CLASS_NAME_SUFFIX);
+              assertThat(enclosingClassSubject, isPresent());
+              assertEquals(
+                  enclosingClassSubject.getDexProgramClass().getType(),
+                  cImplSubject
+                      .getDexProgramClass()
+                      .getEnclosingMethodAttribute()
+                      .getEnclosingMethod()
+                      .getHolderType());
+            })
         .run(parameters.getRuntime(), MAIN)
-        // TODO(b/70293332)
-        .assertFailureWithErrorThatMatches(containsString(errorType));
+        .apply(
+            result -> result.assertSuccessWithOutputLines(getExpectedOutput(result.inspector())));
+  }
+
+  private List<String> getExpectedOutput() {
+    return ImmutableList.of(
+        "interface " + A.class.getTypeName(),
+        "public default int " + A.class.getTypeName() + ".def()",
+        "42");
+  }
+
+  private List<String> getExpectedOutput(CodeInspector inspector) {
+    ClassSubject aClassSubject = inspector.clazz(A.class);
+    assertThat(aClassSubject, isPresent());
+
+    MethodSubject defMethodSubject = aClassSubject.uniqueMethodWithName("def");
+    assertThat(defMethodSubject, isPresent());
+
+    if (parameters.canUseDefaultAndStaticInterfaceMethods()) {
+      String modifiers =
+          parameters.isCfRuntime()
+                  || parameters.getDexRuntimeVersion().isNewerThanOrEqual(Version.V8_1_0)
+              ? "public default"
+              : "public";
+      return ImmutableList.of(
+          "interface " + aClassSubject.getFinalName(),
+          modifiers
+              + " int "
+              + aClassSubject.getFinalName()
+              + "."
+              + defMethodSubject.getFinalName()
+              + "()",
+          "42");
+    }
+
+    ClassSubject aCompanionClassSubject =
+        inspector.clazz(A.class.getTypeName() + COMPANION_CLASS_NAME_SUFFIX);
+    assertThat(aCompanionClassSubject, isPresent());
+
+    String methodNamePrefix = enableMinification ? "" : DEFAULT_METHOD_PREFIX;
+    return ImmutableList.of(
+        "class " + aCompanionClassSubject.getFinalName(),
+        "public static int "
+            + aCompanionClassSubject.getFinalName()
+            + "."
+            + methodNamePrefix
+            + defMethodSubject.getFinalName()
+            + "("
+            + aClassSubject.getFinalName()
+            + ")",
+        "42");
   }
 }
diff --git a/src/test/java/com/android/tools/r8/desugar/lambdas/DeduplicateLambdasWithDefaultMethodsTest.java b/src/test/java/com/android/tools/r8/desugar/lambdas/DeduplicateLambdasWithDefaultMethodsTest.java
new file mode 100644
index 0000000..b252cf4
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/lambdas/DeduplicateLambdasWithDefaultMethodsTest.java
@@ -0,0 +1,107 @@
+// 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.lambdas;
+
+import static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.references.Reference;
+import com.android.tools.r8.synthesis.SyntheticItemsTestUtils;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.codeinspector.FoundClassSubject;
+import com.google.common.collect.ImmutableSet;
+import java.util.stream.Collectors;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class DeduplicateLambdasWithDefaultMethodsTest extends TestBase {
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withNoneRuntime().build();
+  }
+
+  public DeduplicateLambdasWithDefaultMethodsTest(TestParameters parameters) {
+    parameters.assertNoneRuntime();
+  }
+
+  @Test
+  public void test() throws Exception {
+    assertEquals(
+        ImmutableSet.of(
+            Reference.classFromClass(I.class),
+            Reference.classFromClass(TestClass.class),
+            SyntheticItemsTestUtils.syntheticCompanionClass(I.class),
+            SyntheticItemsTestUtils.syntheticLambdaClass(TestClass.class, 0)),
+        testForD8(Backend.CF)
+            .addInnerClasses(getClass())
+            .setIntermediate(true)
+            .setMinApi(AndroidApiLevel.B)
+            .compile()
+            .inspector()
+            .allClasses()
+            .stream()
+            .map(FoundClassSubject::getFinalReference)
+            .collect(Collectors.toSet()));
+  }
+
+  interface I {
+    void foo();
+
+    // Lots of methods which may cause the ordering of methods on a class to change between builds.
+    default void a() {
+      System.out.print("a");
+    }
+
+    default void b() {
+      System.out.print("b");
+    }
+
+    default void c() {
+      System.out.print("c");
+    }
+
+    default void x() {
+      System.out.print("x");
+    }
+
+    default void y() {
+      System.out.print("y");
+    }
+
+    default void z() {
+      System.out.print("z");
+    }
+  }
+
+  public static class TestClass {
+    private static void foo() {
+      System.out.println("foo");
+    }
+
+    private static void pI(I i) {
+      i.a();
+      i.b();
+      i.c();
+      i.x();
+      i.y();
+      i.z();
+      i.foo();
+    }
+
+    public static void main(String[] args) {
+      // Duplication of the same lambda, each of which should become a shared instance.
+      pI(TestClass::foo);
+      pI(TestClass::foo);
+      pI(TestClass::foo);
+      pI(TestClass::foo);
+      pI(TestClass::foo);
+      pI(TestClass::foo);
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/desugar/lambdas/LambdaEqualityTest.java b/src/test/java/com/android/tools/r8/desugar/lambdas/LambdaEqualityTest.java
new file mode 100644
index 0000000..90c8009
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/lambdas/LambdaEqualityTest.java
@@ -0,0 +1,140 @@
+// 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.lambdas;
+
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.references.Reference;
+import com.android.tools.r8.utils.StringUtils;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+/**
+ * These tests document the behavior of lambdas w.r.t identity and equality.
+ *
+ * <p>The D8 and R8 compilers take the stance that a program should not rely on either identity or
+ * equality of any lambda metafactory allocated lambda. Thus the status of these tests differ
+ * between JVM, D8/CF, D8/DEX and R8 runs as the compilers may or may not share classes and
+ * allocations as seen fit.
+ */
+@RunWith(Parameterized.class)
+public class LambdaEqualityTest extends TestBase {
+
+  static final String EXPECTED_JAVAC =
+      StringUtils.lines(
+          "Same method refs",
+          "true",
+          "true",
+          "true",
+          "Different method refs",
+          "false",
+          "false",
+          "false",
+          "Empty lambda",
+          "false",
+          "false",
+          "false");
+
+  static final String EXPECTED_D8 =
+      StringUtils.lines(
+          "Same method refs",
+          "true",
+          "true",
+          "true",
+          "Different method refs",
+          "true", // D8 will share the class for the method references.
+          "false",
+          "false",
+          "Empty lambda",
+          "false",
+          "false",
+          "false");
+
+  static final String EXPECTED_R8 =
+      StringUtils.lines(
+          "Same method refs",
+          "true",
+          "true",
+          "true",
+          "Different method refs",
+          "true", // R8 will share the class for the method references.
+          "false",
+          "false",
+          "Empty lambda",
+          "true", // R8 will eliminate the call to the impl method thus making lambdas equal.
+          "true",
+          "true");
+
+  private final TestParameters parameters;
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimes().withAllApiLevelsAlsoForCf().build();
+  }
+
+  public LambdaEqualityTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testJvm() throws Exception {
+    assumeTrue(parameters.isCfRuntime());
+    testForRuntime(parameters)
+        .addInnerClasses(LambdaEqualityTest.class)
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutput(EXPECTED_JAVAC);
+  }
+
+  @Test
+  public void testD8() throws Exception {
+    testForD8(parameters.getBackend())
+        .addInnerClasses(LambdaEqualityTest.class)
+        .setMinApi(parameters.getApiLevel())
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutput(EXPECTED_D8);
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(LambdaEqualityTest.class)
+        .setMinApi(parameters.getApiLevel())
+        .addKeepMainRule(TestClass.class)
+        .addKeepMethodRules(
+            Reference.methodFromMethod(
+                TestClass.class.getDeclaredMethod(
+                    "compare", String.class, MyInterface.class, MyInterface.class)))
+        .run(parameters.getRuntime(), TestClass.class)
+        // The use of invoke dynamics prohibits the optimization and sharing of lambdas in R8.
+        .assertSuccessWithOutput(parameters.isCfRuntime() ? EXPECTED_JAVAC : EXPECTED_R8);
+  }
+
+  interface MyInterface {
+    void foo();
+  }
+
+  static class TestClass {
+
+    public static void compare(String msg, MyInterface i1, MyInterface i2) {
+      System.out.println(msg);
+      System.out.println(i1.getClass() == i2.getClass());
+      System.out.println(i1 == i2);
+      System.out.println(i1.equals(i2));
+    }
+
+    public static void main(String[] args) {
+      MyInterface println = System.out::println;
+      // These lambdas are physically the same and should remain so in all cases.
+      compare("Same method refs", println, println);
+      // These lambdas can be shared as they reference the same actual function.
+      compare("Different method refs", println, System.out::println);
+      // These lambdas cannot be shared (by D8) as javac will generate a lambda$main$X for each.
+      compare("Empty lambda", () -> {}, () -> {});
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/desugar/lambdas/LambdaNamingConflictTest.java b/src/test/java/com/android/tools/r8/desugar/lambdas/LambdaNamingConflictTest.java
new file mode 100644
index 0000000..1a6e364
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/lambdas/LambdaNamingConflictTest.java
@@ -0,0 +1,113 @@
+// 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.lambdas;
+
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.references.ClassReference;
+import com.android.tools.r8.synthesis.SyntheticItemsTestUtils;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.StringUtils;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class LambdaNamingConflictTest extends TestBase {
+
+  static final String EXPECTED = StringUtils.lines("boo!");
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters()
+        .withAllRuntimes()
+        .withApiLevel(AndroidApiLevel.B)
+        .enableApiLevelsForCf()
+        .build();
+  }
+
+  // The expected synthetic name is the context of the lambda, TestClass, and the first id.
+  private static final ClassReference CONFLICTING_NAME =
+      SyntheticItemsTestUtils.syntheticLambdaClass(TestClass.class, 0);
+
+  private final TestParameters parameters;
+
+  public LambdaNamingConflictTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testJvm() throws Exception {
+    assumeTrue(parameters.isCfRuntime());
+    testForJvm()
+        .addProgramClasses(I.class)
+        .addProgramClassFileData(getConflictingNameClass())
+        .addProgramClassFileData(getTransformedMainClass())
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutput(EXPECTED);
+  }
+
+  @Test
+  public void testD8() throws Exception {
+    testForD8(parameters.getBackend())
+        .addProgramClasses(I.class)
+        .addProgramClassFileData(getConflictingNameClass())
+        .addProgramClassFileData(getTransformedMainClass())
+        .setMinApi(parameters.getApiLevel())
+        .addOptionsModification(o -> o.testing.allowConflictingSyntheticTypes = true)
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutput(EXPECTED);
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    testForR8(parameters.getBackend())
+        .addProgramClasses(I.class)
+        .addProgramClassFileData(getConflictingNameClass())
+        .addProgramClassFileData(getTransformedMainClass())
+        .setMinApi(parameters.getApiLevel())
+        .addOptionsModification(o -> o.testing.allowConflictingSyntheticTypes = true)
+        .addKeepMainRule(TestClass.class)
+        // Ensure that R8 cannot remove or rename the conflicting name.
+        .addKeepClassAndMembersRules(CONFLICTING_NAME.getTypeName())
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutput(EXPECTED);
+  }
+
+  private byte[] getTransformedMainClass() throws Exception {
+    return transformer(TestClass.class)
+        .transformMethodInsnInMethod(
+            "main",
+            (opcode, owner, name, descriptor, isInterface, visitor) ->
+                visitor.visitMethodInsn(
+                    opcode, CONFLICTING_NAME.getBinaryName(), name, descriptor, isInterface))
+        .transform();
+  }
+
+  private byte[] getConflictingNameClass() throws Exception {
+    return transformer(WillBeConflictingName.class)
+        .setClassDescriptor(CONFLICTING_NAME.getDescriptor())
+        .transform();
+  }
+
+  interface I {
+    void bar();
+  }
+
+  static class WillBeConflictingName {
+    public static void foo(I i) {
+      i.bar();
+    }
+  }
+
+  static class TestClass {
+
+    public static void main(String[] args) {
+      WillBeConflictingName.foo(() -> System.out.println("boo!"));
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/desugar/lambdas/LambdaStaticInstanceFieldDuplicationTest.java b/src/test/java/com/android/tools/r8/desugar/lambdas/LambdaStaticInstanceFieldDuplicationTest.java
new file mode 100644
index 0000000..ac5b076
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/lambdas/LambdaStaticInstanceFieldDuplicationTest.java
@@ -0,0 +1,281 @@
+// 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.lambdas;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.OutputMode;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.references.MethodReference;
+import com.android.tools.r8.synthesis.SyntheticItemsTestUtils;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.InstructionSubject;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Sets;
+import com.google.common.collect.Sets.SetView;
+import java.nio.file.Path;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.stream.Collectors;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class LambdaStaticInstanceFieldDuplicationTest extends TestBase {
+
+  static final String EXPECTED = StringUtils.lines("User1.1", "User1.2", "User2");
+
+  static final List<Class<?>> CLASSES =
+      ImmutableList.of(TestClass.class, MyConsumer.class, Accept.class, User1.class, User2.class);
+
+  static final List<String> CLASS_TYPE_NAMES =
+      CLASSES.stream().map(Class::getTypeName).collect(Collectors.toList());
+
+  private final TestParameters parameters;
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters()
+        .withAllRuntimes()
+        .withApiLevel(AndroidApiLevel.J)
+        .enableApiLevelsForCf()
+        .build();
+  }
+
+  public LambdaStaticInstanceFieldDuplicationTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    // R8 does not support desugaring with class file output so this test is only valid for DEX.
+    assumeTrue(parameters.isDexRuntime());
+    runR8(false);
+    runR8(true);
+  }
+
+  private void runR8(boolean minify) throws Exception {
+    testForR8(parameters.getBackend())
+        .addProgramClasses(CLASSES)
+        .addKeepMainRule(TestClass.class)
+        // Prevent R8 from eliminating the lambdas by keeping the application of them.
+        .addKeepClassAndMembersRules(Accept.class)
+        .setMinApi(parameters.getApiLevel())
+        .minification(minify)
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutput(EXPECTED)
+        .inspect(this::checkNoOriginalsAndNoInternalSynthetics);
+  }
+
+  @Test
+  public void testD8() throws Exception {
+    testForD8(parameters.getBackend())
+        .addProgramClasses(CLASSES)
+        .setMinApi(parameters.getApiLevel())
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutput(EXPECTED)
+        .inspect(this::checkNoOriginalsAndNoInternalSynthetics)
+        .inspect(this::checkExpectedSynthetics);
+    ;
+  }
+
+  @Test
+  public void testD8Merging() throws Exception {
+    assumeTrue(
+        "b/147485959: Merging does not happen for CF due to lack of synthetic annotations",
+        parameters.isDexRuntime());
+    boolean intermediate = true;
+    runD8Merging(intermediate);
+  }
+
+  @Test
+  public void testD8MergingNonIntermediate() throws Exception {
+    boolean intermediate = false;
+    runD8Merging(intermediate);
+  }
+
+  private void runD8Merging(boolean intermediate) throws Exception {
+    // Compile part 1 of the input (maybe intermediate)
+    Path out1 =
+        testForD8(parameters.getBackend())
+            .addProgramClasses(User1.class)
+            .addClasspathClasses(CLASSES)
+            .setMinApi(parameters.getApiLevel())
+            .setIntermediate(intermediate)
+            .compile()
+            .writeToZip();
+
+    // Compile part 2 of the input (maybe intermediate)
+    Path out2 =
+        testForD8(parameters.getBackend())
+            .addProgramClasses(User2.class)
+            .addClasspathClasses(CLASSES)
+            .setMinApi(parameters.getApiLevel())
+            .setIntermediate(intermediate)
+            .compile()
+            .writeToZip();
+
+    SetView<MethodReference> syntheticsInParts =
+        Sets.union(
+            getSyntheticMethods(new CodeInspector(out1)),
+            getSyntheticMethods(new CodeInspector(out2)));
+
+    // Merge parts as an intermediate artifact.
+    // This will not merge synthetics regardless of the setting of intermediate.
+    Path out3 = temp.newFolder().toPath().resolve("out3.zip");
+    testForD8(parameters.getBackend())
+        .addProgramClasses(TestClass.class, MyConsumer.class, Accept.class)
+        .addProgramFiles(out1, out2)
+        .setMinApi(parameters.getApiLevel())
+        .setIntermediate(true)
+        .compile()
+        .writeToZip(out3)
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutput(EXPECTED)
+        .inspect(this::checkNoOriginalsAndNoInternalSynthetics)
+        .inspect(inspector -> assertEquals(syntheticsInParts, getSyntheticMethods(inspector)));
+
+    // Finally do a non-intermediate merge.
+    testForD8(parameters.getBackend())
+        .addProgramFiles(out3)
+        .setMinApi(parameters.getApiLevel())
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutput(EXPECTED)
+        .inspect(this::checkNoOriginalsAndNoInternalSynthetics)
+        .inspect(
+            inspector -> {
+              if (intermediate) {
+                // If all previous builds where intermediate then synthetics are merged.
+                checkExpectedSynthetics(inspector);
+              } else {
+                // Otherwise merging non-intermediate artifacts, synthetics will not be identified.
+                // Check that they are exactly as in the part inputs.
+                assertEquals(syntheticsInParts, getSyntheticMethods(inspector));
+              }
+            });
+  }
+
+  @Test
+  public void testD8FilePerClassFile() throws Exception {
+    assumeTrue(parameters.isDexRuntime());
+    runD8FilePerMode(OutputMode.DexFilePerClassFile);
+  }
+
+  @Test
+  public void testD8FilePerClass() throws Exception {
+    assumeTrue(parameters.isDexRuntime());
+    runD8FilePerMode(OutputMode.DexFilePerClass);
+  }
+
+  public void runD8FilePerMode(OutputMode outputMode) throws Exception {
+    Path perClassOutput =
+        testForD8(parameters.getBackend())
+            .setOutputMode(outputMode)
+            .addProgramClasses(CLASSES)
+            .setMinApi(parameters.getApiLevel())
+            .compile()
+            .writeToZip();
+    testForD8()
+        .addProgramFiles(perClassOutput)
+        .setMinApi(parameters.getApiLevel())
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutput(EXPECTED)
+        .inspect(this::checkNoOriginalsAndNoInternalSynthetics)
+        .inspect(this::checkExpectedSynthetics);
+  }
+
+  private void checkNoOriginalsAndNoInternalSynthetics(CodeInspector inspector) {
+    inspector.forAllClasses(
+        clazz -> {
+          assertFalse(SyntheticItemsTestUtils.isInternalLambda(clazz.getFinalReference()));
+          clazz.forAllMethods(
+              method ->
+                  assertTrue(
+                      "Unexpected invoke dynamic:\n" + method.getMethod().codeToString(),
+                      method.isAbstract()
+                          || method
+                              .streamInstructions()
+                              .noneMatch(InstructionSubject::isInvokeDynamic)));
+        });
+  }
+
+  private Set<MethodReference> getSyntheticMethods(CodeInspector inspector) {
+    Set<MethodReference> methods = new HashSet<>();
+    inspector.allClasses().stream()
+        .filter(c -> !CLASS_TYPE_NAMES.contains(c.getFinalName()))
+        .forEach(
+            c ->
+                c.allMethods(m -> !m.isInstanceInitializer() && !m.isClassInitializer())
+                    .forEach(m -> methods.add(m.asMethodReference())));
+    return methods;
+  }
+
+  private void checkExpectedSynthetics(CodeInspector inspector) throws Exception {
+    // Hardcoded set of expected synthetics in a "final" build. This set could change if the
+    // compiler makes any changes to the naming, sorting or grouping of synthetics. It is hard-coded
+    // here to check that the compiler generates this deterministically for any single run or merge
+    // of intermediates.
+    Set<MethodReference> expectedSynthetics =
+        ImmutableSet.of(
+            // User1 has two lambdas.
+            SyntheticItemsTestUtils.syntheticLambdaMethod(
+                User1.class, 0, MyConsumer.class.getMethod("accept", Object.class)),
+            SyntheticItemsTestUtils.syntheticLambdaMethod(
+                User1.class, 1, MyConsumer.class.getMethod("accept", Object.class)),
+            // User2 has one lambda.
+            SyntheticItemsTestUtils.syntheticLambdaMethod(
+                User2.class, 0, MyConsumer.class.getMethod("accept", Object.class)));
+    assertEquals(expectedSynthetics, getSyntheticMethods(inspector));
+  }
+
+  interface MyConsumer {
+    void accept(Object o);
+  }
+
+  static class Accept {
+    public static void accept(Object o, MyConsumer consumer) {
+      consumer.accept(o);
+    }
+  }
+
+  static class User1 {
+
+    private static void testSystemPrintln1() {
+      // The lambda will reference a lambda$x method on User1 which is created by javac for each
+      // lambda on the class. Thus there can be no sharing unless R8 inlines the lambda method into
+      // the desugared lambda classes.
+      Accept.accept("1.1", o -> System.out.println("User" + o));
+    }
+
+    private static void testSystemPrintln2() {
+      Accept.accept("1.2", o -> System.out.println("User" + o));
+    }
+  }
+
+  static class User2 {
+
+    private static void testSystemPrintln() {
+      Accept.accept("2", o -> System.out.println("User" + o));
+    }
+  }
+
+  static class TestClass {
+
+    public static void main(String[] args) {
+      User1.testSystemPrintln1();
+      User1.testSystemPrintln2();
+      User2.testSystemPrintln();
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/desugar/lambdas/LambdaToSysOutPrintlnDuplicationTest.java b/src/test/java/com/android/tools/r8/desugar/lambdas/LambdaToSysOutPrintlnDuplicationTest.java
new file mode 100644
index 0000000..5b73125
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/lambdas/LambdaToSysOutPrintlnDuplicationTest.java
@@ -0,0 +1,265 @@
+// 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.lambdas;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.OutputMode;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.references.MethodReference;
+import com.android.tools.r8.synthesis.SyntheticItemsTestUtils;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.InstructionSubject;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Sets;
+import com.google.common.collect.Sets.SetView;
+import java.nio.file.Path;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.stream.Collectors;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class LambdaToSysOutPrintlnDuplicationTest extends TestBase {
+
+  static final String EXPECTED = StringUtils.lines("User1", "User2");
+
+  static final List<Class<?>> CLASSES =
+      ImmutableList.of(TestClass.class, MyConsumer.class, Accept.class, User1.class, User2.class);
+
+  static final List<String> CLASS_TYPE_NAMES =
+      CLASSES.stream().map(Class::getTypeName).collect(Collectors.toList());
+
+  private final TestParameters parameters;
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters()
+        .withAllRuntimes()
+        .withApiLevel(AndroidApiLevel.J)
+        .enableApiLevelsForCf()
+        .build();
+  }
+
+  public LambdaToSysOutPrintlnDuplicationTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    // R8 does not support desugaring with class file output so this test is only valid for DEX.
+    assumeTrue(parameters.isDexRuntime());
+    runR8(false);
+    runR8(true);
+  }
+
+  private void runR8(boolean minify) throws Exception {
+    testForR8(parameters.getBackend())
+        .addProgramClasses(CLASSES)
+        .addKeepMainRule(TestClass.class)
+        .setMinApi(parameters.getApiLevel())
+        .minification(minify)
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutput(EXPECTED)
+        .inspect(this::checkNoOriginalsAndNoInternalSynthetics);
+  }
+
+  @Test
+  public void testD8() throws Exception {
+    testForD8(parameters.getBackend())
+        .addProgramClasses(CLASSES)
+        .setMinApi(parameters.getApiLevel())
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutput(EXPECTED)
+        .inspect(this::checkNoOriginalsAndNoInternalSynthetics)
+        .inspect(this::checkExpectedSynthetics);
+    ;
+  }
+
+  @Test
+  public void testD8Merging() throws Exception {
+    assumeTrue(
+        "b/147485959: Merging does not happen for CF due to lack of synthetic annotations",
+        parameters.isDexRuntime());
+    boolean intermediate = true;
+    runD8Merging(intermediate);
+  }
+
+  @Test
+  public void testD8MergingNonIntermediate() throws Exception {
+    boolean intermediate = false;
+    runD8Merging(intermediate);
+  }
+
+  private void runD8Merging(boolean intermediate) throws Exception {
+    // Compile part 1 of the input (maybe intermediate)
+    Path out1 =
+        testForD8(parameters.getBackend())
+            .addProgramClasses(User1.class)
+            .addClasspathClasses(CLASSES)
+            .setMinApi(parameters.getApiLevel())
+            .setIntermediate(intermediate)
+            .compile()
+            .writeToZip();
+
+    // Compile part 2 of the input (maybe intermediate)
+    Path out2 =
+        testForD8(parameters.getBackend())
+            .addProgramClasses(User2.class)
+            .addClasspathClasses(CLASSES)
+            .setMinApi(parameters.getApiLevel())
+            .setIntermediate(intermediate)
+            .compile()
+            .writeToZip();
+
+    SetView<MethodReference> syntheticsInParts =
+        Sets.union(
+            getSyntheticMethods(new CodeInspector(out1)),
+            getSyntheticMethods(new CodeInspector(out2)));
+
+    // Merge parts as an intermediate artifact.
+    // This will not merge synthetics regardless of the setting of intermediate.
+    Path out3 = temp.newFolder().toPath().resolve("out3.zip");
+    testForD8(parameters.getBackend())
+        .addProgramClasses(TestClass.class, MyConsumer.class, Accept.class)
+        .addProgramFiles(out1, out2)
+        .setMinApi(parameters.getApiLevel())
+        .setIntermediate(true)
+        .compile()
+        .writeToZip(out3)
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutput(EXPECTED)
+        .inspect(this::checkNoOriginalsAndNoInternalSynthetics)
+        .inspect(inspector -> assertEquals(syntheticsInParts, getSyntheticMethods(inspector)));
+
+    // Finally do a non-intermediate merge.
+    testForD8(parameters.getBackend())
+        .addProgramFiles(out3)
+        .setMinApi(parameters.getApiLevel())
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutput(EXPECTED)
+        .inspect(this::checkNoOriginalsAndNoInternalSynthetics)
+        .inspect(
+            inspector -> {
+              if (intermediate) {
+                // If all previous builds where intermediate then synthetics are merged.
+                checkExpectedSynthetics(inspector);
+              } else {
+                // Otherwise merging non-intermediate artifacts, synthetics will not be identified.
+                // Check that they are exactly as in the part inputs.
+                assertEquals(syntheticsInParts, getSyntheticMethods(inspector));
+              }
+            });
+  }
+
+  @Test
+  public void testD8FilePerClassFile() throws Exception {
+    assumeTrue(parameters.isDexRuntime());
+    runD8FilePerMode(OutputMode.DexFilePerClassFile);
+  }
+
+  @Test
+  public void testD8FilePerClass() throws Exception {
+    assumeTrue(parameters.isDexRuntime());
+    runD8FilePerMode(OutputMode.DexFilePerClass);
+  }
+
+  public void runD8FilePerMode(OutputMode outputMode) throws Exception {
+    Path perClassOutput =
+        testForD8(parameters.getBackend())
+            .setOutputMode(outputMode)
+            .addProgramClasses(CLASSES)
+            .setMinApi(parameters.getApiLevel())
+            .compile()
+            .writeToZip();
+    testForD8()
+        .addProgramFiles(perClassOutput)
+        .setMinApi(parameters.getApiLevel())
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutput(EXPECTED)
+        .inspect(this::checkNoOriginalsAndNoInternalSynthetics)
+        .inspect(this::checkExpectedSynthetics);
+  }
+
+  private void checkNoOriginalsAndNoInternalSynthetics(CodeInspector inspector) {
+    inspector.forAllClasses(
+        clazz -> {
+          assertFalse(SyntheticItemsTestUtils.isInternalLambda(clazz.getFinalReference()));
+          clazz.forAllMethods(
+              method ->
+                  assertTrue(
+                      "Unexpected invoke dynamic:\n" + method.getMethod().codeToString(),
+                      method.isAbstract()
+                          || method
+                              .streamInstructions()
+                              .noneMatch(InstructionSubject::isInvokeDynamic)));
+        });
+  }
+
+  private Set<MethodReference> getSyntheticMethods(CodeInspector inspector) {
+    Set<MethodReference> methods = new HashSet<>();
+    inspector.allClasses().stream()
+        .filter(c -> !CLASS_TYPE_NAMES.contains(c.getFinalName()))
+        .forEach(
+            c ->
+                c.allMethods(m -> !m.isInstanceInitializer())
+                    .forEach(m -> methods.add(m.asMethodReference())));
+    return methods;
+  }
+
+  private void checkExpectedSynthetics(CodeInspector inspector) throws Exception {
+    // Hardcoded set of expected synthetics in a "final" build. This set could change if the
+    // compiler makes any changes to the naming, sorting or grouping of synthetics. It is hard-coded
+    // here to check that the compiler generates this deterministically for any single run or merge
+    // of intermediates.
+    Set<MethodReference> expectedSynthetics =
+        ImmutableSet.of(
+            SyntheticItemsTestUtils.syntheticLambdaMethod(
+                User1.class, 0, MyConsumer.class.getMethod("accept", Object.class)));
+    assertEquals(expectedSynthetics, getSyntheticMethods(inspector));
+  }
+
+  interface MyConsumer {
+    void accept(Object o);
+  }
+
+  static class Accept {
+    public static void accept(Object o, MyConsumer consumer) {
+      consumer.accept(o);
+    }
+  }
+
+  static class User1 {
+
+    private static void testSystemPrintln() {
+      Accept.accept("User1", System.out::println);
+    }
+  }
+
+  static class User2 {
+
+    private static void testSystemPrintln() {
+      Accept.accept("User2", System.out::println);
+    }
+  }
+
+  static class TestClass {
+
+    public static void main(String[] args) {
+      User1.testSystemPrintln();
+      User2.testSystemPrintln();
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/desugar/lambdas/LegacyLambdaMergeTest.java b/src/test/java/com/android/tools/r8/desugar/lambdas/LegacyLambdaMergeTest.java
new file mode 100644
index 0000000..dd00acf
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/lambdas/LegacyLambdaMergeTest.java
@@ -0,0 +1,123 @@
+// 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.lambdas;
+
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.D8TestCompileResult;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.graph.AccessFlags;
+import com.android.tools.r8.references.ClassReference;
+import com.android.tools.r8.references.Reference;
+import com.android.tools.r8.transformers.ClassFileTransformer.MethodInsnTransform;
+import com.android.tools.r8.transformers.ClassFileTransformer.TypeInsnTransform;
+import com.android.tools.r8.utils.StringUtils;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.objectweb.asm.MethodVisitor;
+
+@RunWith(Parameterized.class)
+public class LegacyLambdaMergeTest extends TestBase {
+
+  static final String EXPECTED = StringUtils.lines("Hello, world");
+
+  private final TestParameters parameters;
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimes().withAllApiLevels().build();
+  }
+
+  public LegacyLambdaMergeTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testReference() throws Exception {
+    assumeTrue(parameters.isCfRuntime());
+    testForJvm()
+        .addProgramClassFileData(getTransformedMain())
+        // Add the lambda twice (JVM just picks the first).
+        .addProgramClassFileData(getTransformedLambda())
+        .addProgramClassFileData(getTransformedLambda())
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutput(EXPECTED);
+  }
+
+  @Test
+  public void testD8() throws Exception {
+    // Merging legacy lambdas is only valid for DEX inputs, thus also not R8 applicable.
+    assumeTrue(parameters.isDexRuntime());
+    D8TestCompileResult lambda =
+        testForD8()
+            .setMinApi(parameters.getApiLevel())
+            .addProgramClassFileData(getTransformedLambda())
+            .compile();
+    testForD8()
+        .setMinApi(parameters.getApiLevel())
+        .addProgramClassFileData(getTransformedMain())
+        // Add the lambda twice.
+        .addProgramFiles(lambda.writeToZip())
+        .addProgramFiles(lambda.writeToZip())
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutput(EXPECTED);
+  }
+
+  private ClassReference LAMBDA =
+      Reference.classFromDescriptor(
+          Reference.classFromClass(WillBeLambda.class)
+              .getDescriptor()
+              .replace("WillBeLambda", "-$$Lambda$XYZ"));
+
+  private byte[] getTransformedLambda() throws Exception {
+    return transformer(WillBeLambda.class)
+        .setClassDescriptor(LAMBDA.getDescriptor())
+        .setAccessFlags(AccessFlags::setSynthetic)
+        .transform();
+  }
+
+  private byte[] getTransformedMain() throws Exception {
+    return transformer(TestClass.class)
+        .transformMethodInsnInMethod(
+            "main",
+            new MethodInsnTransform() {
+              @Override
+              public void visitMethodInsn(
+                  int opcode,
+                  String owner,
+                  String name,
+                  String descriptor,
+                  boolean isInterface,
+                  MethodVisitor visitor) {
+                visitor.visitMethodInsn(
+                    opcode, LAMBDA.getBinaryName(), name, descriptor, isInterface);
+              }
+            })
+        .transformTypeInsnInMethod(
+            "main",
+            new TypeInsnTransform() {
+              @Override
+              public void visitTypeInsn(int opcode, String type, MethodVisitor visitor) {
+                visitor.visitTypeInsn(opcode, LAMBDA.getBinaryName());
+              }
+            })
+        .transform();
+  }
+
+  static class WillBeLambda {
+    public void foo() {
+      System.out.println("Hello, world");
+    }
+  }
+
+  static class TestClass {
+
+    public static void main(String[] args) {
+      new WillBeLambda().foo();
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/desugar/lambdas/mergedcontext/MergedContextTest.java b/src/test/java/com/android/tools/r8/desugar/lambdas/mergedcontext/MergedContextTest.java
new file mode 100644
index 0000000..35e31d5
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/lambdas/mergedcontext/MergedContextTest.java
@@ -0,0 +1,106 @@
+// 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.mergedcontext;
+
+import com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.NoHorizontalClassMerging;
+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 org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class MergedContextTest extends TestBase {
+
+  static final String EXPECTED = StringUtils.lines("B::foo", "C::bar");
+
+  private final TestParameters parameters;
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimes().withAllApiLevels().build();
+  }
+
+  public MergedContextTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void test() throws Exception {
+    testForR8(parameters.getBackend())
+        .addProgramClasses(TestClass.class, A.class, B.class, C.class)
+        .addKeepClassAndMembersRules(TestClass.class)
+        .addKeepRules("-repackageclasses \"repackaged\"")
+        .enableInliningAnnotations()
+        .enableNoHorizontalClassMergingAnnotations()
+        .enableNeverClassInliningAnnotations()
+        .setMinApi(parameters.getApiLevel())
+        .addHorizontallyMergedClassesInspector(
+            inspector -> {
+              inspector.assertClassNotMerged(C.class);
+              inspector.assertMergedInto(B.class, A.class);
+            })
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutput(EXPECTED);
+  }
+
+  /* This class will be merged with class B (with A being the result). This class has a package
+   * protected access to ensure that it cannot be repackaged. */
+  @NeverClassInline
+  public static class A {
+
+    @NeverInline
+    public void ensureNotRepackaged() {
+      TestClass.packageProtectedMethodToDisableRepackage();
+    }
+  }
+
+  /* This class is merged into A. */
+  @NeverClassInline
+  public static class B {
+
+    @NeverInline
+    public Runnable foo() {
+      C c = new C();
+      // This synthetic lambda class uses package protected access to C. Its context will initially
+      // be B, thus the synthetic will internally be B-$$Synthetic. The lambda can be repackaged
+      // together with the accessed class C. However, once A and B are merged as A, the context
+      // implicitly changes. If repackaging does not either see or adjust the context, the result
+      // will be that the external synthetic lambda will become A-$$Synthetic,
+      // with the consequence that the call to repackaged.C.protectedMethod() will throw IAE.
+      return () -> {
+        System.out.println("B::foo");
+        c.packageProtectedMethod();
+      };
+    }
+  }
+
+  @NeverClassInline
+  @NoHorizontalClassMerging
+  public static class C {
+
+    @NeverInline
+    void packageProtectedMethod() {
+      System.out.println("C::bar");
+    }
+  }
+
+  static class TestClass {
+
+    static void packageProtectedMethodToDisableRepackage() {
+      if (System.nanoTime() < 0) {
+        throw new RuntimeException();
+      }
+    }
+
+    public static void main(String[] args) {
+      new A().ensureNotRepackaged();
+      new B().foo().run();
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/FullNestOnProgramPathTest.java b/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/FullNestOnProgramPathTest.java
index e4447b53..8a37cd0 100644
--- a/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/FullNestOnProgramPathTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/FullNestOnProgramPathTest.java
@@ -50,7 +50,7 @@
     return getTestParameters()
         .withCfRuntimesStartingFromIncluding(CfVm.JDK11)
         .withDexRuntimes()
-        .withAllApiLevels()
+        .withApiLevelsStartingAtIncluding(apiLevelWithInvokeCustomSupport())
         .build();
   }
 
@@ -107,11 +107,7 @@
           .addKeepAllAttributes()
           .setMinApi(parameters.getApiLevel())
           .addProgramFiles(classesOfNest(nestID))
-          .addOptionsModification(
-              options -> {
-                options.enableNestBasedAccessDesugaring = true;
-                options.enableNestReduction = false;
-              })
+          .addOptionsModification(options -> options.enableNestReduction = false)
           .compile()
           .run(parameters.getRuntime(), getMainClass(nestID))
           .assertSuccessWithOutput(getExpectedResult(nestID));
@@ -125,7 +121,6 @@
       testForD8()
           .setMinApi(parameters.getApiLevel())
           .addProgramFiles(classesOfNest(nestID))
-          .addOptionsModification(options -> options.enableNestBasedAccessDesugaring = true)
           .compile()
           .run(parameters.getRuntime(), getMainClass(nestID))
           .assertSuccessWithOutput(getExpectedResult(nestID));
@@ -136,10 +131,6 @@
       throws CompilationFailedException {
     return testForD8(getStaticTemp())
         .addProgramFiles(JAR)
-        .addOptionsModification(
-            options -> {
-              options.enableNestBasedAccessDesugaring = true;
-            })
         .setMinApi(minApi)
         .compile();
   }
@@ -150,12 +141,9 @@
         .noTreeShaking()
         .noMinification()
         .addKeepAllAttributes()
-        .addOptionsModification(
-            options -> {
-              options.enableNestBasedAccessDesugaring = true;
-              options.enableNestReduction = false;
-            })
+        .addOptionsModification(options -> options.enableNestReduction = false)
         .addProgramFiles(JAR)
+        .addInliningAnnotations()
         .setMinApi(minApi)
         .compile();
   }
diff --git a/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/Java11R8CompilationTest.java b/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/Java11R8CompilationTest.java
index 6ebe0db..bf9a226 100644
--- a/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/Java11R8CompilationTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/Java11R8CompilationTest.java
@@ -5,7 +5,6 @@
 package com.android.tools.r8.desugar.nestaccesscontrol;
 
 import static junit.framework.TestCase.assertTrue;
-import static org.hamcrest.CoreMatchers.containsString;
 
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
@@ -51,10 +50,13 @@
         .setMinApi(parameters.getApiLevel())
         .addProgramFiles(ToolHelper.R8_WITH_RELOCATED_DEPS_11_JAR)
         .addKeepRuleFiles(MAIN_KEEP)
+        // TODO(b/177967938): Investigate why this is needed.
+        .applyIf(
+            parameters.getApiLevel().isLessThan(AndroidApiLevel.O),
+            builder -> builder.addDontWarnJavaLangInvoke().addDontWarnJavaNioFile(),
+            builder -> builder.addDontWarnJavaLangInvoke("StringConcatFactory"))
         .addOptionsModification(opt -> opt.ignoreMissingClasses = true)
-        .allowDiagnosticWarningMessages()
         .compile()
-        .assertAllWarningMessagesMatch(containsString("Missing class "))
         .inspect(this::assertNotEmpty)
         .inspect(Java11R8CompilationTest::assertNoNests);
   }
diff --git a/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/MinimumNumberOfBridgesGenerated.java b/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/MinimumNumberOfBridgesGenerated.java
index 9d01c91..c55df8c 100644
--- a/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/MinimumNumberOfBridgesGenerated.java
+++ b/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/MinimumNumberOfBridgesGenerated.java
@@ -13,7 +13,7 @@
 import com.android.tools.r8.TestRuntime.CfVm;
 import com.android.tools.r8.ToolHelper.DexVm;
 import com.android.tools.r8.graph.DexEncodedMethod;
-import com.android.tools.r8.ir.desugar.NestBasedAccessDesugaring;
+import com.android.tools.r8.ir.desugar.nest.NestBasedAccessDesugaring;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.android.tools.r8.utils.codeinspector.FoundMethodSubject;
@@ -37,7 +37,7 @@
         .withCfRuntimesStartingFromIncluding(CfVm.JDK11)
         .withDexRuntime(DexVm.Version.first())
         .withDexRuntime(DexVm.Version.last())
-        .withAllApiLevels()
+        .withApiLevelsStartingAtIncluding(apiLevelWithInvokeCustomSupport())
         .build();
   }
 
diff --git a/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/NestAttributesUpdateTest.java b/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/NestAttributesUpdateTest.java
index 816a233..aa22447 100644
--- a/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/NestAttributesUpdateTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/NestAttributesUpdateTest.java
@@ -94,6 +94,7 @@
               options.enableClassInlining = false;
             })
         .addProgramFiles(classesMatching(outerNestName))
+        .addInliningAnnotations()
         .compile()
         .inspect(
             inspector -> {
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 7f2fede..66e2ec7 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
@@ -4,12 +4,15 @@
 
 package com.android.tools.r8.desugar.nestaccesscontrol;
 
+import static com.android.tools.r8.DiagnosticsMatcher.diagnosticType;
 import static com.android.tools.r8.desugar.nestaccesscontrol.NestAccessControlTestUtils.CLASSES_PATH;
 import static com.android.tools.r8.desugar.nestaccesscontrol.NestAccessControlTestUtils.CLASS_NAMES;
 import static com.android.tools.r8.utils.FileUtils.CLASS_EXTENSION;
 import static java.util.stream.Collectors.toList;
 import static org.hamcrest.core.StringContains.containsString;
 import static org.hamcrest.core.StringEndsWith.endsWith;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 
@@ -20,9 +23,12 @@
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
 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.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;
@@ -47,17 +53,19 @@
         .withCfRuntimesStartingFromIncluding(CfVm.JDK11)
         .withDexRuntime(DexVm.Version.first())
         .withDexRuntime(DexVm.Version.last())
-        .withAllApiLevels()
+        .withApiLevelsStartingAtIncluding(apiLevelWithInvokeCustomSupport())
         .build();
   }
 
   @Test
-  public void testWarningD8() throws Exception {
+  public void testD8() throws Exception {
     Assume.assumeTrue(parameters.isDexRuntime());
-    testIncompleteNestWarning(true, true);
-    testMissingNestHostWarning(true, true);
+    testMissingNestHostError(true);
+    testIncompleteNestError(true);
   }
-
+  // TODO R8:
+  // appView.options().reportMissingNestHost(clazz);
+  // clazz.clearNestHost();
   @Test
   public void testWarningR8() throws Exception {
     testIncompleteNestWarning(false, parameters.isDexRuntime());
@@ -66,8 +74,8 @@
 
   @Test
   public void testErrorR8() {
-    testMissingNestHostError();
-    testIncompleteNestError();
+    testMissingNestHostError(false);
+    testIncompleteNestError(false);
   }
 
   private TestCompilerBuilder<?, ?, ?, ?, ?> compileOnlyClassesMatching(
@@ -81,10 +89,7 @@
             .map(name -> CLASSES_PATH.resolve(name + CLASS_EXTENSION))
             .collect(toList());
     if (d8) {
-      return testForD8()
-          .setMinApi(parameters.getApiLevel())
-          .addProgramFiles(matchingClasses)
-          .addOptionsModification(options -> options.enableNestBasedAccessDesugaring = true);
+      return testForD8().setMinApi(parameters.getApiLevel()).addProgramFiles(matchingClasses);
     } else {
       return testForR8(parameters.getBackend())
           .noTreeShaking()
@@ -92,23 +97,49 @@
           .addKeepAllAttributes()
           .setMinApi(parameters.getApiLevel())
           .addProgramFiles(matchingClasses)
+          .addLibraryFiles(ToolHelper.getMostRecentAndroidJar())
+          .addDontWarn("java.lang.invoke.StringConcatFactory")
           .addOptionsModification(
               options -> {
-                options.enableNestBasedAccessDesugaring = true;
                 options.ignoreMissingClasses = ignoreMissingClasses;
+                options.testing.enableExperimentalMissingClassesReporting = true;
               })
           .allowDiagnosticWarningMessages(allowDiagnosticWarningMessages);
     }
   }
 
-  private void testMissingNestHostError() {
+  private void testMissingNestHostError(boolean d8) {
     try {
       Matcher<String> innerClassMatcher =
           containsString("BasicNestHostWithInnerClassMethods$BasicNestedClass");
-      compileOnlyClassesMatching(innerClassMatcher, false, false, false)
+      compileOnlyClassesMatching(innerClassMatcher, d8, false, false)
           .compileWithExpectedDiagnostics(
               diagnostics -> {
-                diagnostics.assertErrorMessageThatMatches(containsString("requires its nest host"));
+                if (d8) {
+                  diagnostics
+                      .assertOnlyErrors()
+                      .assertErrorsMatch(
+                          diagnosticType(MissingNestHostNestDesugarDiagnostic.class));
+
+                  MissingNestHostNestDesugarDiagnostic diagnostic =
+                      (MissingNestHostNestDesugarDiagnostic) diagnostics.getErrors().get(0);
+                  assertEquals(
+                      "Class BasicNestHostWithInnerClassMethods$BasicNestedClass requires its nest "
+                          + "host BasicNestHostWithInnerClassMethods to be on program or class "
+                          + "path.",
+                      diagnostic.getDiagnosticMessage());
+                } else {
+                  diagnostics
+                      .assertOnlyErrors()
+                      .assertErrorsMatch(diagnosticType(MissingClassesDiagnostic.class));
+
+                  MissingClassesDiagnostic diagnostic =
+                      (MissingClassesDiagnostic) diagnostics.getErrors().get(0);
+                  assertEquals(1, diagnostic.getMissingClasses().size());
+                  assertEquals(
+                      "nesthostexample.BasicNestHostWithInnerClassMethods",
+                      diagnostic.getMissingClasses().iterator().next().getTypeName());
+                }
               });
     } catch (CompilationFailedException e) {
       // Expected failure.
@@ -117,14 +148,37 @@
     fail("Should have raised an exception for missing nest host");
   }
 
-  private void testIncompleteNestError() {
+  private void testIncompleteNestError(boolean d8) {
     try {
       Matcher<String> innerClassMatcher = endsWith("BasicNestHostWithInnerClassMethods");
-      compileOnlyClassesMatching(innerClassMatcher, false, false, false)
+      compileOnlyClassesMatching(innerClassMatcher, d8, false, false)
           .compileWithExpectedDiagnostics(
               diagnostics -> {
-                diagnostics.assertErrorMessageThatMatches(
-                    containsString("requires its nest mates"));
+                if (d8) {
+                  diagnostics
+                      .assertOnlyErrors()
+                      .assertErrorsMatch(diagnosticType(IncompleteNestNestDesugarDiagnosic.class));
+
+                  IncompleteNestNestDesugarDiagnosic diagnostic =
+                      (IncompleteNestNestDesugarDiagnosic) diagnostics.getErrors().get(0);
+                  assertEquals(
+                      "Compilation of classes nesthostexample.BasicNestHostWithInnerClassMethods "
+                          + "requires its nest mates "
+                          + "nesthostexample.BasicNestHostWithInnerClassMethods$BasicNestedClass "
+                          + "(unavailable) to be on program or class path.",
+                      diagnostic.getDiagnosticMessage());
+                } else {
+                  diagnostics
+                      .assertOnlyErrors()
+                      .assertErrorsMatch(diagnosticType(MissingClassesDiagnostic.class));
+
+                  MissingClassesDiagnostic diagnostic =
+                      (MissingClassesDiagnostic) diagnostics.getErrors().get(0);
+                  assertEquals(1, diagnostic.getMissingClasses().size());
+                  assertEquals(
+                      "nesthostexample.BasicNestHostWithInnerClassMethods$BasicNestedClass",
+                      diagnostic.getMissingClasses().iterator().next().getTypeName());
+                }
               });
     } catch (Exception e) {
       // Expected failure.
@@ -139,15 +193,31 @@
     TestCompileResult<?, ?> compileResult =
         compileOnlyClassesMatching(innerClassMatcher, d8, !d8, true).compile();
     assertTrue(compileResult.getDiagnosticMessages().getWarnings().size() >= 1);
-    if (desugarWarning) {
+    if (d8 && desugarWarning) {
       assertTrue(
           compileResult.getDiagnosticMessages().getWarnings().stream()
               .anyMatch(warn -> warn instanceof MissingNestHostNestDesugarDiagnostic));
-    } else if (!d8) {
+    }
+    if (!d8) {
       // R8 should raise extra warning when cleaning the nest.
-      assertTrue(
-          compileResult.getDiagnosticMessages().getWarnings().stream()
-              .anyMatch(warn -> warn.getDiagnosticMessage().contains("requires its nest host")));
+      compileResult.inspectDiagnosticMessages(
+          diagnostics -> {
+            diagnostics.assertOnlyWarnings();
+            if (parameters.isCfRuntime() || parameters.canUseDefaultAndStaticInterfaceMethods()) {
+              diagnostics.assertWarningsMatch(diagnosticType(MissingClassesDiagnostic.class));
+            } else {
+              diagnostics.assertWarningsMatch(
+                  diagnosticType(MissingClassesDiagnostic.class),
+                  diagnosticType(InterfaceDesugarMissingTypeDiagnostic.class));
+            }
+
+            MissingClassesDiagnostic diagnostic =
+                (MissingClassesDiagnostic) diagnostics.getWarnings().get(0);
+            assertEquals(1, diagnostic.getMissingClasses().size());
+            assertEquals(
+                "nesthostexample.BasicNestHostWithInnerClassMethods",
+                diagnostic.getMissingClasses().iterator().next().getTypeName());
+          });
     }
   }
 
@@ -156,13 +226,32 @@
     TestCompileResult<?, ?> compileResult =
         compileOnlyClassesMatching(innerClassMatcher, d8, !d8, true).compile();
     assertTrue(compileResult.getDiagnosticMessages().getWarnings().size() >= 1);
-    if (desugarWarning) {
+    if (d8 && desugarWarning) {
       assertTrue(
           compileResult.getDiagnosticMessages().getWarnings().stream()
               .anyMatch(warn -> warn instanceof IncompleteNestNestDesugarDiagnosic));
-    } else if (!d8) {
+    }
+    if (!d8) {
       // R8 should raise extra warning when cleaning the nest.
-      compileResult.assertWarningMessageThatMatches(containsString("requires its nest mates"));
+      compileResult.inspectDiagnosticMessages(
+          diagnostics -> {
+            diagnostics.assertOnlyWarnings();
+            if (parameters.isCfRuntime() || parameters.canUseDefaultAndStaticInterfaceMethods()) {
+              diagnostics.assertWarningsMatch(diagnosticType(MissingClassesDiagnostic.class));
+            } else {
+              diagnostics.assertWarningsMatch(
+                  diagnosticType(MissingClassesDiagnostic.class),
+                  diagnosticType(InterfaceDesugarMissingTypeDiagnostic.class));
+            }
+
+            MissingClassesDiagnostic diagnostic =
+                (MissingClassesDiagnostic) diagnostics.getWarnings().get(0);
+            assertNotNull(diagnostic);
+            assertEquals(1, diagnostic.getMissingClasses().size());
+            assertEquals(
+                "nesthostexample.BasicNestHostWithInnerClassMethods$BasicNestedClass",
+                diagnostic.getMissingClasses().iterator().next().getTypeName());
+          });
     }
   }
 }
diff --git a/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/NestConstructorRemovedArgTest.java b/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/NestConstructorRemovedArgTest.java
index 1ed0e0a..85bade9 100644
--- a/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/NestConstructorRemovedArgTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/NestConstructorRemovedArgTest.java
@@ -33,7 +33,7 @@
         .withCfRuntimesStartingFromIncluding(CfVm.JDK11)
         .withDexRuntime(DexVm.Version.first())
         .withDexRuntime(DexVm.Version.last())
-        .withAllApiLevels()
+        .withApiLevelsStartingAtIncluding(apiLevelWithInvokeCustomSupport())
         .build();
   }
 
diff --git a/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/NestOnProgramAndClassPathTest.java b/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/NestOnProgramAndClassPathTest.java
index bb59533..4029469 100644
--- a/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/NestOnProgramAndClassPathTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/NestOnProgramAndClassPathTest.java
@@ -99,9 +99,7 @@
     testForD8()
         .addProgramFiles(inner.writeToZip(), host.writeToZip())
         .setMinApi(parameters.getApiLevel())
-        .addOptionsModification(options -> options.enableNestBasedAccessDesugaring = true)
         .compile()
-        .inspect(inspector -> assertEquals(3, inspector.allClasses().size()))
         .run(parameters.getRuntime(), getMainClass("constructors"))
         .assertSuccessWithOutput(getExpectedResult("constructors"));
   }
@@ -117,7 +115,6 @@
         .setMinApi(parameters.getApiLevel())
         .addProgramFiles(matchingClasses)
         .addClasspathFiles(JAR)
-        .addOptionsModification(options -> options.enableNestBasedAccessDesugaring = true)
         .compile();
   }
 
diff --git a/src/test/java/com/android/tools/r8/desugar/staticinterfacemethod/MissingMethodTest.java b/src/test/java/com/android/tools/r8/desugar/staticinterfacemethod/MissingMethodTest.java
index f147d3b..d36953c 100644
--- a/src/test/java/com/android/tools/r8/desugar/staticinterfacemethod/MissingMethodTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/staticinterfacemethod/MissingMethodTest.java
@@ -6,7 +6,6 @@
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
-import com.android.tools.r8.TestRunResult;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
@@ -28,33 +27,19 @@
 
   @Test
   public void testReference() throws Exception {
-    TestRunResult<?> result =
-        testForRuntime(parameters)
-            .addProgramClassFileData(InterfaceDump.dump(), MainDump.dump())
-            .run(parameters.getRuntime(), "Main");
-    if (parameters.isDexRuntime()
-        && parameters.getApiLevel().isLessThan(apiLevelWithStaticInterfaceMethodsSupport())) {
-      // TODO(b/69835274): Desugaring should preserve exception.
-      result.assertFailureWithErrorThatThrows(NoClassDefFoundError.class);
-    } else {
-      result.assertFailureWithErrorThatThrows(NoSuchMethodError.class);
-    }
+    testForRuntime(parameters)
+        .addProgramClassFileData(InterfaceDump.dump(), MainDump.dump())
+        .run(parameters.getRuntime(), "Main")
+        .assertFailureWithErrorThatThrows(NoSuchMethodError.class);
   }
 
   @Test
   public void testR8() throws Exception {
-    TestRunResult<?> result =
-        testForR8(parameters.getBackend())
-            .addProgramClassFileData(InterfaceDump.dump(), MainDump.dump())
-            .addKeepMainRule("Main")
-            .setMinApi(parameters.getApiLevel())
-            .run(parameters.getRuntime(), "Main");
-    if (parameters.isDexRuntime()
-        && parameters.getApiLevel().isLessThan(apiLevelWithStaticInterfaceMethodsSupport())) {
-      // TODO(b/69835274): Desugaring should preserve exception.
-      result.assertFailureWithErrorThatThrows(NoClassDefFoundError.class);
-    } else {
-      result.assertFailureWithErrorThatThrows(NoSuchMethodError.class);
-    }
+    testForR8(parameters.getBackend())
+        .addProgramClassFileData(InterfaceDump.dump(), MainDump.dump())
+        .addKeepMainRule("Main")
+        .setMinApi(parameters.getApiLevel())
+        .run(parameters.getRuntime(), "Main")
+        .assertFailureWithErrorThatThrows(NoSuchMethodError.class);
   }
 }
diff --git a/src/test/java/com/android/tools/r8/desugar/twr/TwrCloseResourceDuplicationTest.java b/src/test/java/com/android/tools/r8/desugar/twr/TwrCloseResourceDuplicationTest.java
new file mode 100644
index 0000000..aac4a9e
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/twr/TwrCloseResourceDuplicationTest.java
@@ -0,0 +1,160 @@
+// 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.twr;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assume.assumeTrue;
+
+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.ZipUtils;
+import com.android.tools.r8.utils.codeinspector.FoundClassSubject;
+import java.io.IOException;
+import java.util.List;
+import java.util.jar.JarFile;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class TwrCloseResourceDuplicationTest extends TestBase {
+
+  static final int INPUT_CLASSES = 3;
+
+  static final String EXPECTED =
+      StringUtils.lines(
+          "foo opened 1",
+          "foo post close 1",
+          "foo opened 2",
+          "foo caught from 2: RuntimeException",
+          "foo post close 2",
+          "bar opened 1",
+          "bar post close 1",
+          "bar opened 2",
+          "bar caught from 2: RuntimeException",
+          "bar post close 2");
+
+  private final TestParameters parameters;
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimes().withAllApiLevelsAlsoForCf().build();
+  }
+
+  public TwrCloseResourceDuplicationTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  private String getZipFile() throws IOException {
+    return ZipUtils.ZipBuilder.builder(temp.newFile("file.zip").toPath())
+        // DEX VMs from 4.4 up-to 9.0 including, will fail if no entry is added.
+        .addBytes("entry", new byte[1])
+        .build()
+        .toString();
+  }
+
+  @Test
+  public void testJvm() throws Exception {
+    assumeTrue(parameters.isCfRuntime());
+    testForJvm()
+        .addInnerClasses(getClass())
+        .run(parameters.getRuntime(), TestClass.class, getZipFile())
+        .assertSuccessWithOutput(EXPECTED);
+  }
+
+  @Test
+  public void testD8() throws Exception {
+    testForD8(parameters.getBackend())
+        .addInnerClasses(getClass())
+        .setMinApi(parameters.getApiLevel())
+        .run(parameters.getRuntime(), TestClass.class, getZipFile())
+        .assertSuccessWithOutput(EXPECTED)
+        .inspect(
+            inspector -> {
+              // There should be exactly one synthetic class besides the three program classes.
+              int expectedSynthetics =
+                  parameters.getApiLevel().isLessThan(apiLevelWithTwrCloseResourceSupport())
+                      ? 1
+                      : 0;
+              assertEquals(INPUT_CLASSES + expectedSynthetics, inspector.allClasses().size());
+            });
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    assumeTrue(parameters.isDexRuntime());
+    testForR8(parameters.getBackend())
+        .addInnerClasses(getClass())
+        .addKeepMainRule(TestClass.class)
+        .addKeepClassAndMembersRules(Foo.class, Bar.class)
+        .applyIf(
+            parameters.getApiLevel().isLessThan(apiLevelWithTwrCloseResourceSupport()),
+            builder -> builder.addDontWarn("java.lang.AutoCloseable"))
+        .setMinApi(parameters.getApiLevel())
+        .noMinification()
+        .run(parameters.getRuntime(), TestClass.class, getZipFile())
+        .assertSuccessWithOutput(EXPECTED)
+        .inspect(
+            inspector -> {
+              // R8 will optimize the generated methods for the two cases below where the thrown
+              // exception is known or not, thus the synthetic methods will be 2.
+              int expectedSynthetics =
+                  parameters.getApiLevel().isLessThan(apiLevelWithTwrCloseResourceSupport())
+                      ? 2
+                      : 0;
+              List<FoundClassSubject> foundClassSubjects = inspector.allClasses();
+              assertEquals(INPUT_CLASSES + expectedSynthetics, foundClassSubjects.size());
+            });
+  }
+
+  static class Foo {
+    void foo(String name) {
+      try (JarFile f = new JarFile(name)) {
+        System.out.println("foo opened 1");
+      } catch (Exception e) {
+        System.out.println("foo caught from 1: " + e.getClass().getSimpleName());
+      } finally {
+        System.out.println("foo post close 1");
+      }
+      try (JarFile f = new JarFile(name)) {
+        System.out.println("foo opened 2");
+        throw new RuntimeException();
+      } catch (Exception e) {
+        System.out.println("foo caught from 2: " + e.getClass().getSimpleName());
+      } finally {
+        System.out.println("foo post close 2");
+      }
+    }
+  }
+
+  static class Bar {
+    void bar(String name) {
+      try (JarFile f = new JarFile(name)) {
+        System.out.println("bar opened 1");
+      } catch (Exception e) {
+        System.out.println("bar caught from 1: " + e.getClass().getSimpleName());
+      } finally {
+        System.out.println("bar post close 1");
+      }
+      try (JarFile f = new JarFile(name)) {
+        System.out.println("bar opened 2");
+        throw new RuntimeException();
+      } catch (Exception e) {
+        System.out.println("bar caught from 2: " + e.getClass().getSimpleName());
+      } finally {
+        System.out.println("bar post close 2");
+      }
+    }
+  }
+
+  static class TestClass {
+
+    public static void main(String[] args) {
+      new Foo().foo(args[0]);
+      new Bar().bar(args[0]);
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/desugaring/lambdanames/PackageDependentLambdaNamesTest.java b/src/test/java/com/android/tools/r8/desugaring/lambdanames/PackageDependentLambdaNamesTest.java
index 934a15b..e76e476 100644
--- a/src/test/java/com/android/tools/r8/desugaring/lambdanames/PackageDependentLambdaNamesTest.java
+++ b/src/test/java/com/android/tools/r8/desugaring/lambdanames/PackageDependentLambdaNamesTest.java
@@ -12,6 +12,7 @@
 import com.android.tools.r8.utils.BooleanUtils;
 import com.android.tools.r8.utils.DescriptorUtils;
 import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.codeinspector.FoundClassSubject;
 import java.util.List;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -47,11 +48,11 @@
     if (parameters.isDexRuntime()) {
       result.inspect(
           inspector -> {
-            // When in the same package we expect the two System.out::print lambdas to be shared.
+            // With the hygienic synthetics the reference to System.out::print can always be shared.
             assertEquals(
-                samePackage ? 2 : 3,
+                2,
                 inspector.allClasses().stream()
-                    .filter(c -> c.isSynthesizedJavaLambdaClass())
+                    .filter(FoundClassSubject::isSynthesizedJavaLambdaClass)
                     .count());
           });
     }
@@ -85,7 +86,6 @@
     return transformer.transform();
   }
 
-  @FunctionalInterface
   public interface StringConsumer {
     void accept(String arg);
   }
diff --git a/src/test/java/com/android/tools/r8/dexsplitter/DexSplitterMergeRegression.java b/src/test/java/com/android/tools/r8/dexsplitter/DexSplitterMergeRegression.java
index c9dd274..780696e 100644
--- a/src/test/java/com/android/tools/r8/dexsplitter/DexSplitterMergeRegression.java
+++ b/src/test/java/com/android/tools/r8/dexsplitter/DexSplitterMergeRegression.java
@@ -62,8 +62,7 @@
             r8FullTestBuilder
                 .enableNoVerticalClassMergingAnnotations()
                 .enableInliningAnnotations()
-                .noMinification()
-                .addOptionsModification(o -> o.testing.deterministicSortingBasedOnDexType = true);
+                .noMinification();
     ProcessResult processResult =
         testDexSplitter(
             parameters,
diff --git a/src/test/java/com/android/tools/r8/dexsplitter/SplitterTestBase.java b/src/test/java/com/android/tools/r8/dexsplitter/SplitterTestBase.java
index c41a1d1..dce9fbc 100644
--- a/src/test/java/com/android/tools/r8/dexsplitter/SplitterTestBase.java
+++ b/src/test/java/com/android/tools/r8/dexsplitter/SplitterTestBase.java
@@ -143,6 +143,7 @@
         .addProgramClasses(baseClasses)
         .addFeatureSplit(
             builder -> simpleSplitProvider(builder, featureOutput, temp, featureClasses))
+        .addInliningAnnotations()
         .setMinApi(parameters.getApiLevel())
         .addKeepMainRule(SplitRunner.class)
         .addKeepClassRules(toRun);
@@ -182,6 +183,7 @@
             .addClasspathClasses(baseClasses)
             .addClasspathClasses(RunInterface.class)
             .addKeepAllClassesRule()
+            .addInliningAnnotations()
             .setMinApi(parameters.getApiLevel())
             .compile()
             .writeToZip();
diff --git a/src/test/java/com/android/tools/r8/diagnostics/ErrorDuringIrConversionTest.java b/src/test/java/com/android/tools/r8/diagnostics/ErrorDuringIrConversionTest.java
index b9eabe6..89088ac 100644
--- a/src/test/java/com/android/tools/r8/diagnostics/ErrorDuringIrConversionTest.java
+++ b/src/test/java/com/android/tools/r8/diagnostics/ErrorDuringIrConversionTest.java
@@ -86,7 +86,8 @@
     try {
       testForD8()
           .apply(addTestClassWithOrigin())
-          .addOptionsModification(options -> options.testing.hookInIrConversion = () -> throwNPE())
+          .addOptionsModification(
+              options -> options.testing.hookInIrConversion = ErrorDuringIrConversionTest::throwNPE)
           .compileWithExpectedDiagnostics(
               diagnostics -> {
                 // Check that the error is reported as an error to the diagnostics handler.
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 3dabac7..4b1bb9e 100644
--- a/src/test/java/com/android/tools/r8/diagnostics/ModifyDiagnosticsLevelTest.java
+++ b/src/test/java/com/android/tools/r8/diagnostics/ModifyDiagnosticsLevelTest.java
@@ -48,12 +48,11 @@
             })
         .allowDiagnosticInfoMessages()
         .compileWithExpectedDiagnostics(
-            diagnostics -> {
-              diagnostics
-                  .assertOnlyInfos()
-                  .assertInfosCount(1)
-                  .assertInfosMatch(diagnosticMessage(startsWith(MISSING_CLASS_MESSAGE_PREFIX)));
-            });
+            diagnostics ->
+                diagnostics
+                    .assertOnlyInfos()
+                    .assertInfosCount(1)
+                    .assertInfosMatch(diagnosticMessage(startsWith(MISSING_CLASS_MESSAGE_PREFIX))));
   }
 
   @Test
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/AnnotationEnumUnboxingTest.java b/src/test/java/com/android/tools/r8/enumunboxing/AnnotationEnumUnboxingTest.java
index 20362c3..0bc6b67 100644
--- a/src/test/java/com/android/tools/r8/enumunboxing/AnnotationEnumUnboxingTest.java
+++ b/src/test/java/com/android/tools/r8/enumunboxing/AnnotationEnumUnboxingTest.java
@@ -51,6 +51,7 @@
         .addKeepRuntimeVisibleAnnotations()
         .enableNeverClassInliningAnnotations()
         .enableInliningAnnotations()
+        .enableMemberValuePropagationAnnotations()
         .enableNoVerticalClassMergingAnnotations()
         .addOptionsModification(opt -> enableEnumOptions(opt, enumValueOptimization))
         .allowDiagnosticInfoMessages()
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/EnumToStringLibTest.java b/src/test/java/com/android/tools/r8/enumunboxing/EnumToStringLibTest.java
index 89395ef..b21cbd0 100644
--- a/src/test/java/com/android/tools/r8/enumunboxing/EnumToStringLibTest.java
+++ b/src/test/java/com/android/tools/r8/enumunboxing/EnumToStringLibTest.java
@@ -97,13 +97,6 @@
     return testForR8(Backend.CF)
         .addProgramClasses(ToStringLib.class, ToStringLib.LibEnum.class)
         .addKeepRules(enumKeepRules.getKeepRules())
-        // TODO(b/160535629): Work-around on some optimizations relying on $VALUES name.
-        .addKeepRules(
-            "-keep enum "
-                + ToStringLib.LibEnum.class.getName()
-                + " { static "
-                + ToStringLib.LibEnum.class.getName()
-                + "[] $VALUES; }")
         .addOptionsModification(opt -> enableEnumOptions(opt, enumValueOptimization))
         .addKeepMethodRules(
             Reference.methodFromMethod(
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/kotlin/SimpleKotlinEnumUnboxingTest.java b/src/test/java/com/android/tools/r8/enumunboxing/kotlin/SimpleKotlinEnumUnboxingTest.java
index a4f280c..3c48865 100644
--- a/src/test/java/com/android/tools/r8/enumunboxing/kotlin/SimpleKotlinEnumUnboxingTest.java
+++ b/src/test/java/com/android/tools/r8/enumunboxing/kotlin/SimpleKotlinEnumUnboxingTest.java
@@ -4,8 +4,10 @@
 
 package com.android.tools.r8.enumunboxing.kotlin;
 
+import static com.android.tools.r8.DiagnosticsMatcher.diagnosticMessage;
 import static com.android.tools.r8.KotlinTestBase.getCompileMemoizer;
 import static com.android.tools.r8.ToolHelper.getKotlinCompilers;
+import static org.hamcrest.core.StringContains.containsString;
 import static org.junit.Assume.assumeTrue;
 
 import com.android.tools.r8.KotlinCompilerTool.KotlinCompiler;
@@ -68,18 +70,25 @@
   public void testEnumUnboxing() throws Exception {
     assumeTrue(parameters.isDexRuntime());
     testForR8(parameters.getBackend())
-        .addProgramFiles(jars.getForConfiguration(kotlinCompiler, targetVersion))
+        .addProgramFiles(
+            jars.getForConfiguration(kotlinCompiler, targetVersion),
+            ToolHelper.getKotlinStdlibJar(kotlinCompiler))
         .addKeepMainRule(PKG + ".MainKt")
         .addKeepRules(enumKeepRules.getKeepRules())
         .addKeepRuntimeVisibleAnnotations()
         .addOptionsModification(opt -> enableEnumOptions(opt, enumValueOptimization))
-        .allowDiagnosticInfoMessages()
+        .allowDiagnosticMessages()
         .setMinApi(parameters.getApiLevel())
         .compile()
         .inspectDiagnosticMessages(
-            m -> {
+            messages -> {
+              messages
+                  .assertNoErrors()
+                  .assertAllWarningsMatch(
+                      diagnosticMessage(
+                          containsString("Resource 'META-INF/MANIFEST.MF' already exists.")));
               assertEnumIsUnboxed(
-                  PKG + ".Color", SimpleKotlinEnumUnboxingTest.class.getSimpleName(), m);
+                  PKG + ".Color", SimpleKotlinEnumUnboxingTest.class.getSimpleName(), messages);
             })
         .run(parameters.getRuntime(), PKG + ".MainKt")
         .assertSuccessWithOutputLines("RED", "GREEN", "BLUE");
diff --git a/src/test/java/com/android/tools/r8/graph/DexTypeTest.java b/src/test/java/com/android/tools/r8/graph/DexTypeTest.java
index 82ee9e8..83164c5 100644
--- a/src/test/java/com/android/tools/r8/graph/DexTypeTest.java
+++ b/src/test/java/com/android/tools/r8/graph/DexTypeTest.java
@@ -9,9 +9,11 @@
 
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.dex.ApplicationReader;
+import com.android.tools.r8.ir.analysis.type.InterfaceCollection;
 import com.android.tools.r8.utils.AndroidApp;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.Timing;
+import java.util.HashSet;
 import java.util.Set;
 import org.junit.BeforeClass;
 import org.junit.Test;
@@ -53,7 +55,7 @@
 
     // class ArrayList implements List
     DexType arrayList = factory.createType("Ljava/util/ArrayList;");
-    Set<DexType> interfaces = appInfo.implementedInterfaces(arrayList);
+    Set<DexType> interfaces = setOfAll(appInfo.implementedInterfaces(arrayList));
     assertThat(interfaces, hasItems(serializable));
     assertThat(interfaces, hasItems(iterable));
     assertThat(interfaces, hasItems(collection));
@@ -62,7 +64,7 @@
 
     // class LinkedList implements List, Deque
     DexType linkedList = factory.createType("Ljava/util/LinkedList;");
-    interfaces = appInfo.implementedInterfaces(linkedList);
+    interfaces = setOfAll(appInfo.implementedInterfaces(linkedList));
     assertThat(interfaces, hasItems(serializable));
     assertThat(interfaces, hasItems(iterable));
     assertThat(interfaces, hasItems(collection));
@@ -71,6 +73,12 @@
     assertThat(interfaces, hasItems(queue));
   }
 
+  private static Set<DexType> setOfAll(InterfaceCollection interfaces) {
+    HashSet<DexType> set = new HashSet<>(interfaces.size());
+    interfaces.forEach((iface, ignoredIsKnown) -> set.add(iface));
+    return set;
+  }
+
   @Test
   public void implementedInterfaces_collections_kotlin() {
     DexType iterable = factory.createType("Ljava/lang/Iterable;");
@@ -84,13 +92,13 @@
     DexType ktAbsList = factory.createType("Lkotlin/collections/AbstractList;");
     DexType ktAbsSet = factory.createType("Lkotlin/collections/AbstractSet;");
 
-    Set<DexType> interfaces = appInfo.implementedInterfaces(ktAbsList);
+    Set<DexType> interfaces = setOfAll(appInfo.implementedInterfaces(ktAbsList));
     assertThat(interfaces, hasItems(iterable));
     assertThat(interfaces, hasItems(collection));
     assertThat(interfaces, hasItems(list));
     assertThat(interfaces, not(hasItems(set)));
 
-    interfaces = appInfo.implementedInterfaces(ktAbsSet);
+    interfaces = setOfAll(appInfo.implementedInterfaces(ktAbsSet));
     assertThat(interfaces, hasItems(iterable));
     assertThat(interfaces, hasItems(collection));
     assertThat(interfaces, hasItems(set));
@@ -107,7 +115,7 @@
     DexType pType = factory.createType("Ljava/lang/reflect/ParameterizedType;");
     DexType klass = factory.createType("Ljava/lang/Class;");
 
-    Set<DexType> interfaces = appInfo.implementedInterfaces(klass);
+    Set<DexType> interfaces = setOfAll(appInfo.implementedInterfaces(klass));
     assertThat(interfaces, hasItems(serializable));
     assertThat(interfaces, hasItems(annotatedElement));
     assertThat(interfaces, hasItems(genericDeclaration));
@@ -130,7 +138,7 @@
     DexType mutableReference0 =
         factory.createType("Lkotlin/jvm/internal/MutablePropertyReference0;");
 
-    Set<DexType> interfaces = appInfo.implementedInterfaces(mutableReference0);
+    Set<DexType> interfaces = setOfAll(appInfo.implementedInterfaces(mutableReference0));
     assertThat(interfaces, hasItems(kCallable));
     assertThat(interfaces, hasItems(kProperty));
     assertThat(interfaces, hasItems(kMutableProperty));
@@ -147,15 +155,14 @@
     // interface Function0 : Function
     DexType function0 = factory.createType("Lkotlin/jvm/functions/Function0;");
 
-    Set<DexType> interfaces = appInfo.implementedInterfaces(lambda);
+    Set<DexType> interfaces = setOfAll(appInfo.implementedInterfaces(lambda));
     assertThat(interfaces, not(hasItems(lambda)));
     assertThat(interfaces, hasItems(function));
     assertThat(interfaces, hasItems(functionBase));
 
-    interfaces = appInfo.implementedInterfaces(function0);
+    interfaces = setOfAll(appInfo.implementedInterfaces(function0));
     assertThat(interfaces, hasItems(function0));
     assertThat(interfaces, hasItems(function));
     assertThat(interfaces, not(hasItems(functionBase)));
   }
-
 }
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 213db97..7277139 100644
--- a/src/test/java/com/android/tools/r8/graph/MissingClassThrowingTest.java
+++ b/src/test/java/com/android/tools/r8/graph/MissingClassThrowingTest.java
@@ -65,7 +65,7 @@
   }
 
   @Test
-  public void testSuperTypeOfExceptions() throws Exception {
+  public void testSuperTypeOfExceptions() {
     AssertUtils.assertFailsCompilation(
         () ->
             testForR8(parameters.getBackend())
diff --git a/src/test/java/com/android/tools/r8/graph/invokespecial/InvokeSpecialOnOtherInterfaceTest.java b/src/test/java/com/android/tools/r8/graph/invokespecial/InvokeSpecialOnOtherInterfaceTest.java
index 183ae6b..509bfa2 100644
--- a/src/test/java/com/android/tools/r8/graph/invokespecial/InvokeSpecialOnOtherInterfaceTest.java
+++ b/src/test/java/com/android/tools/r8/graph/invokespecial/InvokeSpecialOnOtherInterfaceTest.java
@@ -34,20 +34,20 @@
   @Test
   public void testRuntime() throws Exception {
     testForRuntime(parameters.getRuntime(), parameters.getApiLevel())
-        .addProgramClasses(I.class, Main.class)
+        .addProgramClassesAndInnerClasses(Main.class)
+        .addProgramClasses(I.class)
         .addProgramClassFileData(getClassWithTransformedInvoked())
         .run(parameters.getRuntime(), Main.class)
-        .assertFailureWithErrorThatThrows(
-            parameters.isCfRuntime()
-                ? VerifyError.class
-                // TODO(b/144410139): Consider making this a compilation failure.
-                : NoClassDefFoundError.class);
+        // TODO(b/144410139): Consider making this a compilation failure when generating DEX.
+        .assertSuccessWithOutputLinesIf(parameters.isDexRuntime(), "Hello World!")
+        .assertFailureWithErrorThatThrowsIf(parameters.isCfRuntime(), VerifyError.class);
   }
 
   @Test
   public void testR8() throws Exception {
     testForR8(parameters.getBackend())
-        .addProgramClasses(I.class, Main.class)
+        .addProgramClassesAndInnerClasses(Main.class)
+        .addProgramClasses(I.class)
         .addProgramClassFileData(getClassWithTransformedInvoked())
         .addKeepMainRule(Main.class)
         .setMinApi(parameters.getApiLevel())
@@ -56,7 +56,7 @@
             parameters.isCfRuntime()
                 ? VerifyError.class
                 // TODO(b/144410139): Consider making this a compilation failure.
-                : NoClassDefFoundError.class);
+                : NullPointerException.class);
   }
 
   private byte[] getClassWithTransformedInvoked() throws IOException {
diff --git a/src/test/java/com/android/tools/r8/graph/invokespecial/InvokeSpecialToSubclassTest.java b/src/test/java/com/android/tools/r8/graph/invokespecial/InvokeSpecialToSubclassTest.java
index b439787..9c44e2a 100644
--- a/src/test/java/com/android/tools/r8/graph/invokespecial/InvokeSpecialToSubclassTest.java
+++ b/src/test/java/com/android/tools/r8/graph/invokespecial/InvokeSpecialToSubclassTest.java
@@ -42,25 +42,21 @@
   @Test
   public void testRuntime() throws Exception {
     testForRuntime(parameters.getRuntime(), parameters.getApiLevel())
-        .addProgramClasses(EmptySubC.class, C.class, Main.class)
+        .addProgramClasses(EmptySubC.class, C.class, D.class, Main.class)
         .addProgramClassFileData(getClassBWithTransformedInvoked(holder))
         .run(parameters.getRuntime(), Main.class)
-        // The failures are very different from one back-end to another: verification error,
-        // invalid invoke-super, segmentation fault, NoSuchMethod, etc.
-        .assertFailure();
+        .assertSuccessWithOutputLines("Should not be called.");
   }
 
   @Test
   public void testR8() throws Exception {
     testForR8(parameters.getBackend())
-        .addProgramClasses(EmptySubC.class, C.class, Main.class)
+        .addProgramClasses(EmptySubC.class, C.class, D.class, Main.class)
         .addProgramClassFileData(getClassBWithTransformedInvoked(holder))
         .addKeepMainRule(Main.class)
         .setMinApi(parameters.getApiLevel())
         .run(parameters.getRuntime(), Main.class)
-        // The failures are very different from one back-end to another: verification error,
-        // invalid invoke-super, segmentation fault, NoSuchMethod, etc.
-        .assertFailure();
+        .assertSuccessWithOutputLines("Should not be called.");
   }
 
   private byte[] getClassBWithTransformedInvoked(Class<?> holder) throws IOException {
diff --git a/src/test/java/com/android/tools/r8/internal/proto/ChromeProtoRewritingTest.java b/src/test/java/com/android/tools/r8/internal/proto/ChromeProtoRewritingTest.java
index 318de07..12236fc 100644
--- a/src/test/java/com/android/tools/r8/internal/proto/ChromeProtoRewritingTest.java
+++ b/src/test/java/com/android/tools/r8/internal/proto/ChromeProtoRewritingTest.java
@@ -44,6 +44,8 @@
             keepAllProtosRule(),
             keepDynamicMethodSignatureRule(),
             keepNewMessageInfoSignatureRule())
+        .addDontWarn("android.content.pm.IPackageManager")
+        .allowUnusedDontWarnPatterns()
         .allowUnusedProguardConfigurationRules()
         .enableProtoShrinking(false)
         .setMinApi(AndroidApiLevel.N)
diff --git a/src/test/java/com/android/tools/r8/internal/proto/Proto2BuilderOnlyReferencedFromDynamicMethodTest.java b/src/test/java/com/android/tools/r8/internal/proto/Proto2BuilderOnlyReferencedFromDynamicMethodTest.java
index 6405047..d603cc1 100644
--- a/src/test/java/com/android/tools/r8/internal/proto/Proto2BuilderOnlyReferencedFromDynamicMethodTest.java
+++ b/src/test/java/com/android/tools/r8/internal/proto/Proto2BuilderOnlyReferencedFromDynamicMethodTest.java
@@ -49,6 +49,7 @@
         .addKeepRuleFiles(PROTOBUF_LITE_PROGUARD_RULES)
         .allowAccessModification()
         .allowDiagnosticMessages()
+        .allowUnusedDontWarnPatterns()
         .allowUnusedProguardConfigurationRules()
         .enableProtoShrinking()
         .setMinApi(parameters.getApiLevel())
diff --git a/src/test/java/com/android/tools/r8/internal/proto/Proto2BuilderShrinkingTest.java b/src/test/java/com/android/tools/r8/internal/proto/Proto2BuilderShrinkingTest.java
index 1375f54..868c061 100644
--- a/src/test/java/com/android/tools/r8/internal/proto/Proto2BuilderShrinkingTest.java
+++ b/src/test/java/com/android/tools/r8/internal/proto/Proto2BuilderShrinkingTest.java
@@ -74,6 +74,7 @@
             .addKeepRuleFiles(PROTOBUF_LITE_PROGUARD_RULES)
             .allowAccessModification()
             .allowDiagnosticMessages()
+            .allowUnusedDontWarnPatterns()
             .allowUnusedProguardConfigurationRules()
             .enableInliningAnnotations()
             .enableProtoShrinking()
diff --git a/src/test/java/com/android/tools/r8/internal/proto/Proto2ShrinkingTest.java b/src/test/java/com/android/tools/r8/internal/proto/Proto2ShrinkingTest.java
index d88e281..2d2e79f 100644
--- a/src/test/java/com/android/tools/r8/internal/proto/Proto2ShrinkingTest.java
+++ b/src/test/java/com/android/tools/r8/internal/proto/Proto2ShrinkingTest.java
@@ -87,6 +87,7 @@
             .addNoHorizontalClassMergingRule(PARTIALLY_USED + "$Enum$1")
             .allowAccessModification(allowAccessModification)
             .allowDiagnosticMessages()
+            .allowUnusedDontWarnPatterns()
             .allowUnusedProguardConfigurationRules()
             .enableProguardTestOptions()
             .enableProtoShrinking()
@@ -356,6 +357,7 @@
         .addKeepRules(keepDynamicMethodSignatureRule(), keepNewMessageInfoSignatureRule())
         .allowAccessModification(allowAccessModification)
         .allowDiagnosticMessages()
+        .allowUnusedDontWarnPatterns()
         .allowUnusedProguardConfigurationRules()
         .enableProtoShrinking()
         .minification(enableMinification)
diff --git a/src/test/java/com/android/tools/r8/internal/proto/Proto3ShrinkingTest.java b/src/test/java/com/android/tools/r8/internal/proto/Proto3ShrinkingTest.java
index 429c340..a3c0705 100644
--- a/src/test/java/com/android/tools/r8/internal/proto/Proto3ShrinkingTest.java
+++ b/src/test/java/com/android/tools/r8/internal/proto/Proto3ShrinkingTest.java
@@ -60,6 +60,7 @@
         .addKeepRuleFiles(PROTOBUF_LITE_PROGUARD_RULES)
         .allowAccessModification(allowAccessModification)
         .allowDiagnosticMessages()
+        .allowUnusedDontWarnPatterns()
         .allowUnusedProguardConfigurationRules()
         .enableProtoShrinking()
         .minification(enableMinification)
@@ -108,6 +109,7 @@
         .addKeepRules(keepDynamicMethodSignatureRule(), keepNewMessageInfoSignatureRule())
         .allowAccessModification(allowAccessModification)
         .allowDiagnosticMessages()
+        .allowUnusedDontWarnPatterns()
         .allowUnusedProguardConfigurationRules()
         .enableProtoShrinking()
         .minification(enableMinification)
diff --git a/src/test/java/com/android/tools/r8/internal/proto/YouTubeV1508ProtoRewritingTest.java b/src/test/java/com/android/tools/r8/internal/proto/YouTubeV1508ProtoRewritingTest.java
index 5596fc7..7cdab46 100644
--- a/src/test/java/com/android/tools/r8/internal/proto/YouTubeV1508ProtoRewritingTest.java
+++ b/src/test/java/com/android/tools/r8/internal/proto/YouTubeV1508ProtoRewritingTest.java
@@ -31,6 +31,7 @@
 
   public YouTubeV1508ProtoRewritingTest(TestParameters parameters) {
     super(15, 8);
+    parameters.assertNoneRuntime();
   }
 
   @Test
@@ -55,6 +56,7 @@
         .addMainDexRuleFiles(getMainDexRuleFiles())
         .allowCheckDiscardedErrors(true)
         .allowDiagnosticMessages()
+        .allowUnusedDontWarnPatterns()
         .allowUnusedProguardConfigurationRules()
         .setMinApi(AndroidApiLevel.H_MR2)
         .compile()
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 cb48232..18f15fd 100644
--- a/src/test/java/com/android/tools/r8/ir/InlineTest.java
+++ b/src/test/java/com/android/tools/r8/ir/InlineTest.java
@@ -21,7 +21,6 @@
 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.ProguardClassFilter;
 import com.android.tools.r8.shaking.ProguardConfiguration;
 import com.android.tools.r8.shaking.ProguardKeepRule;
 import com.android.tools.r8.shaking.RootSetBuilder;
@@ -75,10 +74,9 @@
                 ImmutableList.of(ProguardKeepRule.defaultKeepAllRule(unused -> {})))
             .run(executorService));
     Timing timing = Timing.empty();
-    Enqueuer enqueuer = EnqueuerFactory.createForInitialTreeShaking(appView, subtypingInfo);
-    appView.setAppInfo(
-        enqueuer.traceApplication(
-            appView.rootSet(), ProguardClassFilter.empty(), executorService, timing));
+    Enqueuer enqueuer =
+        EnqueuerFactory.createForInitialTreeShaking(appView, executorService, subtypingInfo);
+    appView.setAppInfo(enqueuer.traceApplication(appView.rootSet(), executorService, timing));
     return new TestApplication(appView, method, additionalCode);
   }
 
diff --git a/src/test/java/com/android/tools/r8/ir/analysis/fieldaccess/FieldBitAccessInfoTest.java b/src/test/java/com/android/tools/r8/ir/analysis/fieldaccess/FieldBitAccessInfoTest.java
index a2fe521..553a2f4 100644
--- a/src/test/java/com/android/tools/r8/ir/analysis/fieldaccess/FieldBitAccessInfoTest.java
+++ b/src/test/java/com/android/tools/r8/ir/analysis/fieldaccess/FieldBitAccessInfoTest.java
@@ -36,7 +36,6 @@
 import it.unimi.dsi.fastutil.objects.Reference2IntMap;
 import it.unimi.dsi.fastutil.objects.Reference2IntOpenHashMap;
 import java.io.IOException;
-import java.util.concurrent.ExecutionException;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
@@ -49,7 +48,7 @@
 
   @Parameters(name = "{0}")
   public static TestParametersCollection data() {
-    return getTestParameters().withAllRuntimes().build();
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
   }
 
   public FieldBitAccessInfoTest(TestParameters parameters) {
@@ -61,7 +60,7 @@
     testForR8(parameters.getBackend())
         .addProgramClasses(TestClass.class)
         .addKeepMainRule(TestClass.class)
-        .setMinApi(parameters.getRuntime())
+        .setMinApi(parameters.getApiLevel())
         .compile()
         .run(parameters.getRuntime(), TestClass.class)
         .assertSuccessWithOutputLines(
@@ -93,7 +92,7 @@
     clazz.forEachProgramMethod(
         method -> {
           IRCode code = method.buildIR(appView);
-          fieldAccessAnalysis.recordFieldAccesses(code, feedback, new MethodProcessorMock());
+          fieldAccessAnalysis.recordFieldAccesses(code, feedback, new PrimaryMethodProcessorMock());
         });
 
     int bitsReadInBitField = feedback.bitsReadPerField.getInt(uniqueFieldByName(clazz, "bitField"));
@@ -116,7 +115,7 @@
     }
   }
 
-  private AppView<AppInfoWithClassHierarchy> buildApp() throws IOException, ExecutionException {
+  private AppView<AppInfoWithClassHierarchy> buildApp() throws IOException {
     DexItemFactory dexItemFactory = new DexItemFactory();
     InternalOptions options = new InternalOptions(dexItemFactory, new Reporter());
     options.programConsumer =
@@ -210,12 +209,7 @@
     }
   }
 
-  static class MethodProcessorMock extends MethodProcessor {
-
-    @Override
-    public Phase getPhase() {
-      return Phase.PRIMARY;
-    }
+  static class PrimaryMethodProcessorMock extends MethodProcessor {
 
     @Override
     public boolean shouldApplyCodeRewritings(ProgramMethod method) {
@@ -223,6 +217,11 @@
     }
 
     @Override
+    public boolean isPrimaryMethodProcessor() {
+      return true;
+    }
+
+    @Override
     public boolean isProcessedConcurrently(ProgramMethod method) {
       return false;
     }
diff --git a/src/test/java/com/android/tools/r8/ir/analysis/type/MissingClassesJoinTest.java b/src/test/java/com/android/tools/r8/ir/analysis/type/MissingClassesJoinTest.java
index 6d3c6cb..e654b89 100644
--- a/src/test/java/com/android/tools/r8/ir/analysis/type/MissingClassesJoinTest.java
+++ b/src/test/java/com/android/tools/r8/ir/analysis/type/MissingClassesJoinTest.java
@@ -69,6 +69,7 @@
               .addProgramClasses(A.class, ASub1.class, Box.class, TestClass.class)
               .addKeepAllClassesRule()
               .addOptionsModification(options -> options.testing.allowTypeErrors = allowTypeErrors)
+              .addDontWarn(ASub2.class)
               .allowDiagnosticWarningMessages()
               .enableNoVerticalClassMergingAnnotations()
               .setMinApi(parameters.getApiLevel())
diff --git a/src/test/java/com/android/tools/r8/ir/analysis/type/NullabilityTest.java b/src/test/java/com/android/tools/r8/ir/analysis/type/NullabilityTest.java
index a4c9785..3a6f0e4 100644
--- a/src/test/java/com/android/tools/r8/ir/analysis/type/NullabilityTest.java
+++ b/src/test/java/com/android/tools/r8/ir/analysis/type/NullabilityTest.java
@@ -13,7 +13,6 @@
 
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
-import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.ir.code.Argument;
@@ -33,6 +32,7 @@
 import com.android.tools.r8.ir.optimize.nonnull.NonNullAfterFieldAccess;
 import com.android.tools.r8.ir.optimize.nonnull.NonNullAfterInvoke;
 import com.android.tools.r8.naming.MemberNaming.MethodSignature;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.utils.DescriptorUtils;
 import com.android.tools.r8.utils.Timing;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
@@ -63,7 +63,7 @@
       boolean npeCaught,
       BiConsumer<AppView<?>, IRCode> inspector)
       throws Exception {
-    AppView<? extends AppInfoWithClassHierarchy> appView = build(mainClass);
+    AppView<AppInfoWithLiveness> appView = build(mainClass);
     CodeInspector codeInspector = new CodeInspector(appView.appInfo().app());
     MethodSubject fooSubject = codeInspector.clazz(mainClass.getName()).method(signature);
     IRCode irCode = fooSubject.buildIR();
diff --git a/src/test/java/com/android/tools/r8/ir/analysis/type/TypeElementWidthTest.java b/src/test/java/com/android/tools/r8/ir/analysis/type/TypeElementWidthTest.java
index 304d344..c07c431 100644
--- a/src/test/java/com/android/tools/r8/ir/analysis/type/TypeElementWidthTest.java
+++ b/src/test/java/com/android/tools/r8/ir/analysis/type/TypeElementWidthTest.java
@@ -10,7 +10,6 @@
 
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.graph.DexItemFactory;
-import com.google.common.collect.ImmutableSet;
 import org.junit.Test;
 
 public class TypeElementWidthTest extends TestBase {
@@ -78,7 +77,7 @@
     DexItemFactory dexItemFactory = new DexItemFactory();
     ClassTypeElement referenceType =
         ClassTypeElement.create(
-            dexItemFactory.objectType, Nullability.maybeNull(), ImmutableSet.of());
+            dexItemFactory.objectType, Nullability.maybeNull(), InterfaceCollection.empty());
     assertFalse(referenceType.isSinglePrimitive());
     assertFalse(referenceType.isWidePrimitive());
     assertEquals(1, referenceType.requiredRegisters());
diff --git a/src/test/java/com/android/tools/r8/ir/analysis/type/TypeLatticeTest.java b/src/test/java/com/android/tools/r8/ir/analysis/type/TypeLatticeTest.java
index 5cdd0ca..4f15b2d 100644
--- a/src/test/java/com/android/tools/r8/ir/analysis/type/TypeLatticeTest.java
+++ b/src/test/java/com/android/tools/r8/ir/analysis/type/TypeLatticeTest.java
@@ -20,6 +20,7 @@
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.DirectMappedDexApplication;
+import com.android.tools.r8.ir.analysis.type.InterfaceCollection.Builder;
 import com.android.tools.r8.utils.AndroidApp;
 import com.android.tools.r8.utils.DescriptorUtils;
 import com.android.tools.r8.utils.InternalOptions;
@@ -554,6 +555,14 @@
         Nullability.bottom(), ReferenceTypeElement.getNull().asMeetWithNotNull().nullability());
   }
 
+  private static InterfaceCollection itfs(DexType... types) {
+    Builder builder = InterfaceCollection.builder();
+    for (DexType type : types) {
+      builder.addInterface(type, true);
+    }
+    return builder.build();
+  }
+
   @Test
   public void testLeastUpperBoundOfInterfaces() {
     DexType collection = factory.createType("Ljava/util/Collection;");
@@ -561,37 +570,32 @@
     DexType list = factory.createType("Ljava/util/List;");
     DexType serializable = factory.serializableType;
 
-    Set<DexType> lub =
-        computeLeastUpperBoundOfInterfaces(appView, ImmutableSet.of(set), ImmutableSet.of(list));
+    InterfaceCollection lub = computeLeastUpperBoundOfInterfaces(appView, itfs(set), itfs(list));
     assertEquals(1, lub.size());
-    assertTrue(lub.contains(collection));
-    verifyViaPairwiseJoin(lub,
-        ImmutableSet.of(set), ImmutableSet.of(list));
+    assertTrue(lub.containsKnownInterface(collection));
+    assertEquals(collection, lub.getSingleKnownInterface());
+    verifyViaPairwiseJoin(lub, ImmutableSet.of(set), ImmutableSet.of(list));
 
     lub =
         computeLeastUpperBoundOfInterfaces(
-            appView, ImmutableSet.of(set, serializable), ImmutableSet.of(list, serializable));
+            appView, itfs(set, serializable), itfs(list, serializable));
     assertEquals(2, lub.size());
-    assertTrue(lub.contains(collection));
-    assertTrue(lub.contains(serializable));
+    assertTrue(lub.containsKnownInterface(collection));
+    assertTrue(lub.containsKnownInterface(serializable));
     verifyViaPairwiseJoin(lub,
         ImmutableSet.of(set, serializable), ImmutableSet.of(list, serializable));
 
-    lub =
-        computeLeastUpperBoundOfInterfaces(
-            appView, ImmutableSet.of(set), ImmutableSet.of(list, serializable));
+    lub = computeLeastUpperBoundOfInterfaces(appView, itfs(set), itfs(list, serializable));
     assertEquals(1, lub.size());
-    assertTrue(lub.contains(collection));
-    assertFalse(lub.contains(serializable));
+    assertTrue(lub.containsKnownInterface(collection));
+    assertFalse(lub.containsKnownInterface(serializable));
     verifyViaPairwiseJoin(lub,
         ImmutableSet.of(set), ImmutableSet.of(list, serializable));
 
-    lub =
-        computeLeastUpperBoundOfInterfaces(
-            appView, ImmutableSet.of(set, serializable), ImmutableSet.of(list));
+    lub = computeLeastUpperBoundOfInterfaces(appView, itfs(set, serializable), itfs(list));
     assertEquals(1, lub.size());
-    assertTrue(lub.contains(collection));
-    assertFalse(lub.contains(serializable));
+    assertTrue(lub.containsKnownInterface(collection));
+    assertFalse(lub.containsKnownInterface(serializable));
     verifyViaPairwiseJoin(lub,
         ImmutableSet.of(set, serializable), ImmutableSet.of(list));
 
@@ -599,16 +603,14 @@
     DexType wType = factory.createType("Ljava/lang/reflect/WildcardType;");
     DexType pType = factory.createType("Ljava/lang/reflect/ParameterizedType;");
 
-    lub =
-        computeLeastUpperBoundOfInterfaces(appView, ImmutableSet.of(wType), ImmutableSet.of(pType));
+    lub = computeLeastUpperBoundOfInterfaces(appView, itfs(wType), itfs(pType));
     assertEquals(1, lub.size());
-    assertTrue(lub.contains(type));
+    assertTrue(lub.containsKnownInterface(type));
+    assertEquals(type, lub.getSingleKnownInterface());
     verifyViaPairwiseJoin(lub,
         ImmutableSet.of(wType), ImmutableSet.of(pType));
 
-    lub =
-        computeLeastUpperBoundOfInterfaces(
-            appView, ImmutableSet.of(list, serializable), ImmutableSet.of(pType));
+    lub = computeLeastUpperBoundOfInterfaces(appView, itfs(list, serializable), itfs(pType));
     assertEquals(0, lub.size());
     verifyViaPairwiseJoin(lub,
         ImmutableSet.of(list, serializable), ImmutableSet.of(pType));
@@ -621,41 +623,36 @@
         DescriptorUtils.javaTypeToDescriptor(I3.class.getCanonicalName()));
     DexType i4 = factory.createType(
         DescriptorUtils.javaTypeToDescriptor(I4.class.getCanonicalName()));
-    lub = computeLeastUpperBoundOfInterfaces(appView, ImmutableSet.of(i3), ImmutableSet.of(i4));
+    lub = computeLeastUpperBoundOfInterfaces(appView, itfs(i3), itfs(i4));
     assertEquals(2, lub.size());
-    assertTrue(lub.contains(i1));
-    assertTrue(lub.contains(i2));
+    assertTrue(lub.containsKnownInterface(i1));
+    assertTrue(lub.containsKnownInterface(i2));
     verifyViaPairwiseJoin(lub,
         ImmutableSet.of(i3), ImmutableSet.of(i4));
   }
 
-  private void verifyViaPairwiseJoin(Set<DexType> lub, Set<DexType> s1, Set<DexType>s2) {
-    ImmutableSet.Builder<DexType> builder = ImmutableSet.builder();
+  private void verifyViaPairwiseJoin(InterfaceCollection lub, Set<DexType> s1, Set<DexType> s2) {
+    InterfaceCollection.Builder builder = InterfaceCollection.builder();
     for (DexType i1 : s1) {
       for (DexType i2 : s2) {
-        Set<DexType> lubPerPair =
-            computeLeastUpperBoundOfInterfaces(appView, ImmutableSet.of(i1), ImmutableSet.of(i2));
-        for (DexType lubInterface : lubPerPair) {
-          builder.add(lubInterface);
-        }
+        InterfaceCollection lubPerPair =
+            computeLeastUpperBoundOfInterfaces(
+                appView, InterfaceCollection.singleton(i1), InterfaceCollection.singleton(i2));
+        lubPerPair.forEach(builder::addInterface);
       }
     }
-    Set<DexType> pairwiseJoin = builder.build();
-    ImmutableSet.Builder<DexType> lubBuilder = ImmutableSet.builder();
-    for (DexType itf : pairwiseJoin) {
-      // If there is a strict sub interface of this interface, it is not the least element.
-      if (pairwiseJoin.stream()
-          .anyMatch(other -> appView.appInfo().isStrictSubtypeOf(other, itf))) {
-        continue;
-      }
-      lubBuilder.add(itf);
-    }
-    Set<DexType> pairwiseLub = lubBuilder.build();
-
-    assertEquals(pairwiseLub.size(), lub.size());
-    for (DexType i : pairwiseLub) {
-      assertTrue(lub.contains(i));
-    }
+    InterfaceCollection pairwiseJoin = builder.build();
+    InterfaceCollection.Builder lubBuilder = InterfaceCollection.builder();
+    pairwiseJoin.forEach(
+        (itf, isKnown) -> {
+          // If there is a strict sub interface of this interface, it is not the least element.
+          if (!pairwiseJoin.anyMatch(
+              (other, ignoredOtherIsKnown) -> appView.appInfo().isStrictSubtypeOf(other, itf))) {
+            lubBuilder.addInterface(itf, isKnown);
+          }
+        });
+    InterfaceCollection pairwiseLub = lubBuilder.build();
+    assertEquals(pairwiseLub, lub);
   }
 
 }
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/NonNullTrackerTest.java b/src/test/java/com/android/tools/r8/ir/optimize/NonNullTrackerTest.java
index daa21f4..f5431bb 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/NonNullTrackerTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/NonNullTrackerTest.java
@@ -9,7 +9,6 @@
 
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
-import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.InstancePut;
@@ -22,6 +21,7 @@
 import com.android.tools.r8.ir.optimize.nonnull.NonNullAfterInvoke;
 import com.android.tools.r8.ir.optimize.nonnull.NonNullAfterNullCheck;
 import com.android.tools.r8.naming.MemberNaming.MethodSignature;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.utils.Timing;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.android.tools.r8.utils.codeinspector.MethodSubject;
@@ -49,7 +49,7 @@
       int expectedNumberOfNonNull,
       Consumer<IRCode> testAugmentedIRCode)
       throws Exception {
-    AppView<? extends AppInfoWithClassHierarchy> appView = build(testClass);
+    AppView<AppInfoWithLiveness> appView = build(testClass);
     CodeInspector codeInspector = new CodeInspector(appView.appInfo().app());
     MethodSubject fooSubject = codeInspector.clazz(testClass.getName()).method(signature);
     IRCode code = fooSubject.buildIR();
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/NonNullTrackerTestBase.java b/src/test/java/com/android/tools/r8/ir/optimize/NonNullTrackerTestBase.java
index 6b5f0cb..7d5e23e 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/NonNullTrackerTestBase.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/NonNullTrackerTestBase.java
@@ -5,13 +5,12 @@
 
 import com.android.tools.r8.TestBase;
 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.shaking.AppInfoWithLiveness;
 
 public abstract class NonNullTrackerTestBase extends TestBase {
 
-  protected AppView<? extends AppInfoWithClassHierarchy> build(Class<?> mainClass)
-      throws Exception {
-    return computeAppViewWithSubtyping(buildAndroidApp(ToolHelper.getClassAsBytes(mainClass)));
+  protected AppView<AppInfoWithLiveness> build(Class<?> mainClass) throws Exception {
+    return computeAppViewWithLiveness(buildAndroidApp(ToolHelper.getClassAsBytes(mainClass)));
   }
 }
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/SimplifyIfNotNullTest.java b/src/test/java/com/android/tools/r8/ir/optimize/SimplifyIfNotNullTest.java
index 14d7e6b..da19669 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/SimplifyIfNotNullTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/SimplifyIfNotNullTest.java
@@ -5,7 +5,9 @@
 
 import static org.junit.Assert.assertEquals;
 
+import com.android.tools.r8.R8FullTestBuilder;
 import com.android.tools.r8.TestBase;
+import com.android.tools.r8.ThrowableConsumer;
 import com.android.tools.r8.ir.optimize.nonnull.FieldAccessTest;
 import com.android.tools.r8.ir.optimize.nonnull.NonNullAfterArrayAccess;
 import com.android.tools.r8.ir.optimize.nonnull.NonNullAfterFieldAccess;
@@ -32,10 +34,19 @@
   }
 
   private void testR8(Class<?> testClass, List<MethodSignature> signatures) throws Exception {
+    testR8(testClass, signatures, null);
+  }
+
+  private void testR8(
+      Class<?> testClass,
+      List<MethodSignature> signatures,
+      ThrowableConsumer<R8FullTestBuilder> configuration)
+      throws Exception {
     CodeInspector codeInspector =
         testForR8(Backend.DEX)
             .addProgramClasses(testClass)
             .addKeepRules("-keep class " + testClass.getCanonicalName() + " { *; }")
+            .apply(configuration)
             .compile()
             .inspector();
     verifyAbsenceOfIf(codeInspector, testClass, signatures);
@@ -67,6 +78,9 @@
         new String[]{FieldAccessTest.class.getCanonicalName()});
     MethodSignature foo2 = new MethodSignature("foo2", "int",
         new String[]{FieldAccessTest.class.getCanonicalName()});
-    testR8(NonNullAfterFieldAccess.class, ImmutableList.of(foo, bar, foo2));
+    testR8(
+        NonNullAfterFieldAccess.class,
+        ImmutableList.of(foo, bar, foo2),
+        testBuilder -> testBuilder.addProgramClasses(FieldAccessTest.class));
   }
 }
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/SubsumedCatchHandlerTest.java b/src/test/java/com/android/tools/r8/ir/optimize/SubsumedCatchHandlerTest.java
index 87de375..19d7226 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/SubsumedCatchHandlerTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/SubsumedCatchHandlerTest.java
@@ -86,6 +86,7 @@
         testForR8(backend)
             .addInnerClasses(SubsumedCatchHandlerTest.class)
             .addKeepMainRule(TestClass.class)
+            .enableForceInliningAnnotations()
             .enableInliningAnnotations()
             .run(TestClass.class)
             .assertSuccessWithOutput(expected)
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/canonicalization/EffectivelyFinalFieldCanonicalizationTest.java b/src/test/java/com/android/tools/r8/ir/optimize/canonicalization/EffectivelyFinalFieldCanonicalizationTest.java
index 4e73445..59eff5b 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/canonicalization/EffectivelyFinalFieldCanonicalizationTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/canonicalization/EffectivelyFinalFieldCanonicalizationTest.java
@@ -40,6 +40,8 @@
     testForR8(parameters.getBackend())
         .addInnerClasses(EffectivelyFinalFieldCanonicalizationTest.class)
         .addKeepMainRule(TestClass.class)
+        .enableInliningAnnotations()
+        .enableNeverClassInliningAnnotations()
         .setMinApi(parameters.getApiLevel())
         .compile()
         .inspect(this::inspect)
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/checkcast/CastToUninstantiatedClassTest.java b/src/test/java/com/android/tools/r8/ir/optimize/checkcast/CastToUninstantiatedClassTest.java
index aa385fd..895e6ab 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/checkcast/CastToUninstantiatedClassTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/checkcast/CastToUninstantiatedClassTest.java
@@ -25,8 +25,7 @@
 
   @Parameters(name = "{0}")
   public static TestParametersCollection data() {
-    // TODO(b/172194277): Add support for synthetics when generating CF.
-    return getTestParameters().withDexRuntimes().withAllApiLevels().build();
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
   }
 
   public CastToUninstantiatedClassTest(TestParameters parameters) {
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/classinitializerdefaults/NonFinalFieldWithDefaultValueTest.java b/src/test/java/com/android/tools/r8/ir/optimize/classinitializerdefaults/NonFinalFieldWithDefaultValueTest.java
index c9033f5..2922b90 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/classinitializerdefaults/NonFinalFieldWithDefaultValueTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/classinitializerdefaults/NonFinalFieldWithDefaultValueTest.java
@@ -58,6 +58,7 @@
     testForR8(parameters.getBackend())
         .addInnerClasses(NonFinalFieldWithDefaultValueTest.class)
         .addKeepMainRule(TestClass.class)
+        .enableInliningAnnotations()
         .setMinApi(parameters.getApiLevel())
         .compile()
         .inspect(this::inspect)
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerTest.java b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerTest.java
index e0266bd..38bb02e 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerTest.java
@@ -4,7 +4,6 @@
 
 package com.android.tools.r8.ir.optimize.classinliner;
 
-import static com.android.tools.r8.ir.desugar.LambdaRewriter.LAMBDA_CLASS_NAME_PREFIX;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isAbsent;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
 import static org.hamcrest.CoreMatchers.containsString;
@@ -322,8 +321,8 @@
     Set<String> expectedTypes = Sets.newHashSet("java.lang.StringBuilder");
     expectedTypes.addAll(
         inspector.allClasses().stream()
+            .filter(FoundClassSubject::isSynthesizedJavaLambdaClass)
             .map(FoundClassSubject::getFinalName)
-            .filter(name -> name.contains(LAMBDA_CLASS_NAME_PREFIX))
             .collect(Collectors.toList()));
     assertEquals(expectedTypes, collectTypes(clazz.uniqueMethodWithName("testStatefulLambda")));
     assertTrue(
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ExtraMethodNullTest.java b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ExtraMethodNullTest.java
index 3ece2da..9890166 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ExtraMethodNullTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ExtraMethodNullTest.java
@@ -4,21 +4,36 @@
 
 package com.android.tools.r8.ir.optimize.classinliner;
 
-import static org.hamcrest.CoreMatchers.containsString;
 
-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;
 
+@RunWith(Parameterized.class)
 public class ExtraMethodNullTest extends TestBase {
 
+  private final TestParameters parameters;
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  public ExtraMethodNullTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
   @Test
   public void test() throws Exception {
-    testForR8(Backend.DEX)
+    testForR8(parameters.getBackend())
         .addProgramClassesAndInnerClasses(One.class)
         .addKeepMainRule(One.class)
-        .run(One.class)
-        .assertFailureWithErrorThatMatches(containsString("java.lang.NullPointerException"));
+        .setMinApi(parameters.getApiLevel())
+        .run(parameters.getRuntime(), One.class)
+        .assertFailureWithErrorThatThrows(NullPointerException.class);
   }
 
   public static class One {
@@ -30,7 +45,6 @@
     }
 
     static class Other {
-      @NeverInline
       Object print(Object one) {
         return one;
       }
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/devirtualize/InterfaceRenewalInLoopDebugTestRunner.java b/src/test/java/com/android/tools/r8/ir/optimize/devirtualize/InterfaceRenewalInLoopDebugTestRunner.java
index b21e168..be426b1 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/devirtualize/InterfaceRenewalInLoopDebugTestRunner.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/devirtualize/InterfaceRenewalInLoopDebugTestRunner.java
@@ -26,13 +26,15 @@
 
   @Test
   public void test() throws Throwable {
-    R8TestCompileResult result = testForR8(Backend.CF)
-        .setMode(CompilationMode.DEBUG)
-        .addProgramClasses(TestInterface.class, IMPL, MAIN)
-        .addKeepMainRule(MAIN)
-        .addKeepRules(ImmutableList.of("-keepattributes SourceFile,LineNumberTable"))
-        .noMinification()
-        .compile();
+    R8TestCompileResult result =
+        testForR8(Backend.CF)
+            .setMode(CompilationMode.DEBUG)
+            .addProgramClasses(TestInterface.class, IMPL, MAIN)
+            .addKeepMainRule(MAIN)
+            .addKeepRules(ImmutableList.of("-keepattributes SourceFile,LineNumberTable"))
+            .enableNoVerticalClassMergingAnnotations()
+            .noMinification()
+            .compile();
 
     CodeInspector inspector = result.inspector();
     ClassSubject mainSubject = inspector.clazz(MAIN);
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/fields/NonFinalFinalFieldTest.java b/src/test/java/com/android/tools/r8/ir/optimize/fields/NonFinalFinalFieldTest.java
index 292f74e..d8cdbc5 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/fields/NonFinalFinalFieldTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/fields/NonFinalFinalFieldTest.java
@@ -49,6 +49,7 @@
         .addProgramClasses(TestClass.class)
         .addProgramClassFileData(getProgramClassFileData())
         .addKeepMainRule(TestClass.class)
+        .enableNeverClassInliningAnnotations()
         .setMinApi(parameters.getApiLevel())
         .compile()
         .run(parameters.getRuntime(), TestClass.class)
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/inliner/InlineMappingOnSameLineTest.java b/src/test/java/com/android/tools/r8/ir/optimize/inliner/InlineMappingOnSameLineTest.java
index a8abf65..c4a0b9d 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/inliner/InlineMappingOnSameLineTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/inliner/InlineMappingOnSameLineTest.java
@@ -9,6 +9,7 @@
 
 import com.android.tools.r8.CompilationMode;
 import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.R8TestBuilder;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.naming.retrace.RetraceTestBase;
 import com.android.tools.r8.naming.retrace.StackTrace;
@@ -46,6 +47,11 @@
   }
 
   @Override
+  public void configure(R8TestBuilder<?> builder) {
+    builder.enableInliningAnnotations();
+  }
+
+  @Override
   public Class<?> getMainClass() {
     return Main.class;
   }
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/inliner/InliningIntoVisibilityBridgeTest.java b/src/test/java/com/android/tools/r8/ir/optimize/inliner/InliningIntoVisibilityBridgeTest.java
index a3b0c4a..46727f2 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/inliner/InliningIntoVisibilityBridgeTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/inliner/InliningIntoVisibilityBridgeTest.java
@@ -10,6 +10,7 @@
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.junit.Assert.assertEquals;
 
+import com.android.tools.r8.R8TestBuilder;
 import com.android.tools.r8.R8TestRunResult;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.ir.optimize.inliner.testclasses.InliningIntoVisibilityBridgeTestClasses;
@@ -47,10 +48,10 @@
             .addInnerClasses(InliningIntoVisibilityBridgeTest.class)
             .addInnerClasses(InliningIntoVisibilityBridgeTestClasses.class)
             .addKeepMainRule(TestClass.class)
-            .addKeepRules(
-                neverInline
-                    ? ("-neverinline class " + getClassA().getTypeName() + " { method(); }")
-                    : "")
+            .addForceInliningAnnotations()
+            .addInliningAnnotations()
+            .applyIf(neverInline, R8TestBuilder::enableInliningAnnotations)
+            .applyIf(!neverInline, R8TestBuilder::enableForceInliningAnnotations)
             .enableNoVerticalClassMergingAnnotations()
             .enableProguardTestOptions()
             .compile()
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/inliner/MultipleIndirectCallSitesTest.java b/src/test/java/com/android/tools/r8/ir/optimize/inliner/MultipleIndirectCallSitesTest.java
index a0ed3e6..09bb71b 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/inliner/MultipleIndirectCallSitesTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/inliner/MultipleIndirectCallSitesTest.java
@@ -29,7 +29,8 @@
 
   @Parameterized.Parameters(name = "{1}, invoke A.m(): {0}")
   public static List<Object[]> data() {
-    return buildParameters(BooleanUtils.values(), getTestParameters().withAllRuntimes().build());
+    return buildParameters(
+        BooleanUtils.values(), getTestParameters().withAllRuntimesAndApiLevels().build());
   }
 
   public MultipleIndirectCallSitesTest(boolean invokeMethodOnA, TestParameters parameters) {
@@ -47,6 +48,7 @@
         // kept, there is a single call site that invokes A.m(). However, this does not mean that
         // A.m() has a single call site, so it should not allow inlining of A.m().
         .addKeepRules(getKeepRules())
+        .enableNeverClassInliningAnnotations()
         .setMinApi(parameters.getRuntime())
         .compile()
         .inspect(this::verifyInlining)
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/inliner/Regress131349148.java b/src/test/java/com/android/tools/r8/ir/optimize/inliner/Regress131349148.java
index 09b0ae3..235aa07 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/inliner/Regress131349148.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/inliner/Regress131349148.java
@@ -26,7 +26,10 @@
 
   @Parameters(name = "{0}")
   public static TestParametersCollection params() {
-    return getTestParameters().withDexRuntimes().withAllApiLevels().build();
+    return getTestParameters()
+        .withDexRuntimes()
+        .withApiLevelsStartingAtIncluding(AndroidApiLevel.K)
+        .build();
   }
 
   public Regress131349148(TestParameters parameters) {
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/inliner/testclasses/InliningIntoVisibilityBridgeTestClasses.java b/src/test/java/com/android/tools/r8/ir/optimize/inliner/testclasses/InliningIntoVisibilityBridgeTestClasses.java
index c8c60ae..54018ee 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/inliner/testclasses/InliningIntoVisibilityBridgeTestClasses.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/inliner/testclasses/InliningIntoVisibilityBridgeTestClasses.java
@@ -5,6 +5,7 @@
 package com.android.tools.r8.ir.optimize.inliner.testclasses;
 
 import com.android.tools.r8.ForceInline;
+import com.android.tools.r8.NeverInline;
 import com.android.tools.r8.NoVerticalClassMerging;
 
 public class InliningIntoVisibilityBridgeTestClasses {
@@ -17,6 +18,7 @@
   static class InliningIntoVisibilityBridgeTestClassA {
 
     @ForceInline
+    @NeverInline
     public static void method() {
       System.out.println("Hello world");
     }
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/instanceofremoval/ZipFileInstanceOfAutoCloseableTest.java b/src/test/java/com/android/tools/r8/ir/optimize/instanceofremoval/ZipFileInstanceOfAutoCloseableTest.java
new file mode 100644
index 0000000..b15cca9
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/instanceofremoval/ZipFileInstanceOfAutoCloseableTest.java
@@ -0,0 +1,226 @@
+// 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.optimize.instanceofremoval;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeTrue;
+
+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.dex.ApplicationReader;
+import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.DirectMappedDexApplication;
+import com.android.tools.r8.ir.analysis.type.ClassTypeElement;
+import com.android.tools.r8.ir.analysis.type.InterfaceCollection;
+import com.android.tools.r8.ir.analysis.type.Nullability;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.AndroidApp;
+import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.Timing;
+import com.android.tools.r8.utils.ZipUtils.ZipBuilder;
+import java.io.IOException;
+import java.nio.channels.Channel;
+import java.nio.file.Path;
+import java.util.jar.JarFile;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class ZipFileInstanceOfAutoCloseableTest extends TestBase {
+
+  static final String EXPECTED_PRE_API_19 = StringUtils.lines("Not an AutoCloseable");
+  static final String EXPECTED_POST_API_19 = StringUtils.lines("Is an AutoCloseable");
+
+  private final TestParameters parameters;
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  public ZipFileInstanceOfAutoCloseableTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  private boolean runtimeZipFileIsCloseable() {
+    return parameters.isCfRuntime()
+        || parameters
+            .getRuntime()
+            .asDex()
+            .maxSupportedApiLevel()
+            .isGreaterThanOrEqualTo(AndroidApiLevel.K);
+  }
+
+  private String expectedOutput() {
+    return runtimeZipFileIsCloseable() ? EXPECTED_POST_API_19 : EXPECTED_PRE_API_19;
+  }
+
+  private Path getAndroidJar() {
+    // Always use an android jar later than API 19. Thus at compile-time ZipFile < Closeable.
+    return ToolHelper.getAndroidJar(AndroidApiLevel.LATEST);
+  }
+
+  private String getZipFile() throws IOException {
+    return ZipBuilder.builder(temp.newFile("file.zip").toPath())
+        .addBytes("entry", new byte[1])
+        .build()
+        .toString();
+  }
+
+  @Test
+  public void testD8() throws Exception {
+    assumeTrue(parameters.isDexRuntime());
+    testForD8()
+        .addInnerClasses(ZipFileInstanceOfAutoCloseableTest.class)
+        .setMinApi(parameters.getApiLevel())
+        .addLibraryFiles(getAndroidJar())
+        .run(parameters.getRuntime(), TestClass.class, getZipFile())
+        .assertSuccessWithOutput(expectedOutput());
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(ZipFileInstanceOfAutoCloseableTest.class)
+        .addKeepMainRule(TestClass.class)
+        .setMinApi(parameters.getApiLevel())
+        .addLibraryFiles(getAndroidJar())
+        .run(parameters.getRuntime(), TestClass.class, getZipFile())
+        .assertSuccessWithOutput(expectedOutput());
+  }
+
+  @Test
+  public void testTypeStructure() throws Exception {
+    assumeTrue(parameters.isDexRuntime());
+    // Set the min API and create the raw app.
+    InternalOptions options = new InternalOptions();
+    options.minApiLevel = parameters.getApiLevel().getLevel();
+    DirectMappedDexApplication application =
+        new ApplicationReader(
+                AndroidApp.builder()
+                    .addProgramFiles(ToolHelper.getClassFileForTestClass(MyJarFileScanner.class))
+                    .addLibraryFiles(getAndroidJar())
+                    .build(),
+                options,
+                Timing.empty())
+            .read()
+            .toDirect();
+    AppView<AppInfoWithClassHierarchy> appView = AppView.createForR8(application);
+
+    // Type references.
+    DexType zipFileType = appView.dexItemFactory().createType("Ljava/util/zip/ZipFile;");
+    DexType closeableType = appView.dexItemFactory().createType("Ljava/io/Closeable;");
+    DexType autoCloseableType = appView.dexItemFactory().createType("Ljava/lang/AutoCloseable;");
+    DexType scannerType = appView.dexItemFactory().createType("Ljava/util/Scanner;");
+    DexType myJarFileChannel = buildType(MyJarFileScanner.class, appView.dexItemFactory());
+
+    // Read the computed interface types. Both the "type element" which is used in IR and the full
+    // computed dependencies.
+    ClassTypeElement zipFileTypeElement = makeTypeElement(appView, zipFileType);
+    {
+      InterfaceCollection directInterfaces = zipFileTypeElement.getInterfaces();
+      InterfaceCollection allInterfaces = appView.appInfo().implementedInterfaces(zipFileType);
+      if (zipFileHasCloseable()) {
+        // After API 19 / K the types are known and present.
+        assertEquals(closeableType, directInterfaces.getSingleKnownInterface());
+        assertTrue(allInterfaces.containsKnownInterface(closeableType));
+        assertTrue(allInterfaces.containsKnownInterface(autoCloseableType));
+        assertEquals(2, allInterfaces.size());
+      } else {
+        // The interfaces are still present due to the android jar for K, but they are marked
+        // unknown.
+        assertTrue(directInterfaces.contains(closeableType).isUnknown());
+        assertFalse(directInterfaces.containsKnownInterface(closeableType));
+        assertEquals(1, directInterfaces.size());
+        // Same for the collection of all interfaces. Since the
+        assertTrue(allInterfaces.contains(closeableType).isUnknown());
+        assertTrue(allInterfaces.contains(autoCloseableType).isUnknown());
+        assertFalse(allInterfaces.containsKnownInterface(closeableType));
+        assertFalse(allInterfaces.containsKnownInterface(autoCloseableType));
+        assertEquals(2, allInterfaces.size());
+      }
+    }
+
+    ClassTypeElement scannerTypeElement = makeTypeElement(appView, scannerType);
+    {
+      // Scanner implements Closable and Iterator on all APIs.
+      InterfaceCollection directInterfaces = scannerTypeElement.getInterfaces();
+      assertTrue(directInterfaces.containsKnownInterface(closeableType));
+      assertEquals(2, directInterfaces.size());
+
+      // Joining a type of known with a type of unknown should still be unknown.
+      ClassTypeElement joinLeft1 =
+          scannerTypeElement.join(zipFileTypeElement, appView).asClassType();
+      ClassTypeElement joinRight1 =
+          zipFileTypeElement.join(scannerTypeElement, appView).asClassType();
+      if (zipFileHasCloseable()) {
+        assertTrue(joinLeft1.getInterfaces().contains(closeableType).isTrue());
+        assertTrue(joinRight1.getInterfaces().contains(closeableType).isTrue());
+      } else {
+        assertTrue(joinLeft1.getInterfaces().contains(closeableType).isUnknown());
+        assertTrue(joinRight1.getInterfaces().contains(closeableType).isUnknown());
+      }
+    }
+
+    // Custom class derived from JarFile and Channel, thus it must implement closable.
+    ClassTypeElement myJarFileScannerTypeElement = makeTypeElement(appView, myJarFileChannel);
+
+    // Joining with Scanner will retain it as always Closeable.
+    ClassTypeElement joinWithScanner =
+        myJarFileScannerTypeElement.join(scannerTypeElement, appView).asClassType();
+    assertTrue(joinWithScanner.getInterfaces().contains(closeableType).isTrue());
+
+    // Joining with ZipFile will loose the assurance that it is Closeable.
+    ClassTypeElement joinWithZipFile =
+        myJarFileScannerTypeElement.join(zipFileTypeElement, appView).asClassType();
+    assertTrue(joinWithZipFile.getInterfaces().contains(closeableType).isPossiblyTrue());
+    assertEquals(
+        zipFileHasCloseable(), joinWithZipFile.getInterfaces().contains(closeableType).isTrue());
+  }
+
+  private boolean zipFileHasCloseable() {
+    return parameters.getApiLevel().isGreaterThanOrEqualTo(AndroidApiLevel.K);
+  }
+
+  private static ClassTypeElement makeTypeElement(
+      AppView<AppInfoWithClassHierarchy> appView, DexType type) {
+    return ClassTypeElement.create(type, Nullability.maybeNull(), appView);
+  }
+
+  static class MyJarFileScanner extends JarFile implements Channel {
+
+    public MyJarFileScanner(String name) throws IOException {
+      super(name);
+    }
+
+    @Override
+    public boolean isOpen() {
+      return false;
+    }
+  }
+
+  static class TestClass {
+
+    public static void foo(Object o) throws Exception {
+      if (o instanceof AutoCloseable) {
+        System.out.println("Is an AutoCloseable");
+        ((AutoCloseable) o).close();
+      } else {
+        System.out.println("Not an AutoCloseable");
+      }
+    }
+
+    public static void main(String[] args) throws Exception {
+      foo(new JarFile(args[0]));
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/AssumeNotNullTest.java b/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/AssumeNotNullTest.java
new file mode 100644
index 0000000..fda891e
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/AssumeNotNullTest.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.optimize.membervaluepropagation;
+
+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.assertTrue;
+
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.InstructionSubject;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+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 AssumeNotNullTest extends TestBase {
+
+  private final String flavor;
+  private final TestParameters parameters;
+
+  @Parameters(name = "{1}, flavor: {0}")
+  public static List<Object[]> data() {
+    return buildParameters(
+        ImmutableList.of("assumenosideeffects", "assumevalues"),
+        getTestParameters().withAllRuntimesAndApiLevels().build());
+  }
+
+  public AssumeNotNullTest(String flavor, TestParameters parameters) {
+    this.flavor = flavor;
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void test() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(getClass())
+        .addKeepMainRule(Main.class)
+        .addKeepRules(
+            "-" + flavor + " class " + Factory.class.getTypeName() + " {",
+            "  java.lang.Object create() return 1;",
+            "}",
+            "-" + flavor + " class " + Singleton.class.getTypeName() + " {",
+            "  java.lang.Object INSTANCE return 1;",
+            "}")
+        .enableInliningAnnotations()
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .inspect(
+            inspector -> {
+              MethodSubject mainMethodSubject = inspector.clazz(Main.class).mainMethod();
+              assertThat(mainMethodSubject, isPresent());
+              if (flavor.equals("assumenosideeffects")) {
+                // With -assumenosideeffects, the method should become empty.
+                assertTrue(
+                    mainMethodSubject
+                        .streamInstructions()
+                        .allMatch(InstructionSubject::isReturnVoid));
+              } else {
+                // With -assumevalues, the Singleton.INSTANCE access should remain along with the
+                // Factory.create() invoke.
+                ClassSubject factoryClassSubject = inspector.clazz(Factory.class);
+                assertThat(factoryClassSubject, isPresent());
+
+                ClassSubject singletonClassSubject = inspector.clazz(Singleton.class);
+                assertThat(singletonClassSubject, isPresent());
+
+                assertEquals(
+                    2,
+                    mainMethodSubject
+                        .streamInstructions()
+                        .filter(
+                            instruction -> instruction.isFieldAccess() || instruction.isInvoke())
+                        .count());
+                assertTrue(
+                    mainMethodSubject
+                        .streamInstructions()
+                        .anyMatch(
+                            instruction ->
+                                instruction.isStaticGet()
+                                    && instruction.getField().getHolderType()
+                                        == singletonClassSubject.getDexProgramClass().getType()));
+                assertTrue(
+                    mainMethodSubject
+                        .streamInstructions()
+                        .filter(InstructionSubject::isInvoke)
+                        .anyMatch(
+                            instruction ->
+                                instruction.getMethod().getHolderType()
+                                    == factoryClassSubject.getDexProgramClass().getType()));
+              }
+            })
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithEmptyOutput();
+  }
+
+  static class Main {
+
+    public static void main(String[] args) {
+      if (Singleton.INSTANCE == null) {
+        System.out.println("Foo");
+      }
+      if (Factory.create() == null) {
+        System.out.println("Bar");
+      }
+    }
+  }
+
+  static class Factory {
+
+    @NeverInline
+    public static Object create() {
+      return System.currentTimeMillis() > 0 ? new Object() : null;
+    }
+  }
+
+  static class Singleton {
+
+    public static Object INSTANCE = System.currentTimeMillis() > 0 ? new Object() : null;
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/B143686595.java b/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/B143686595.java
index 036bde7..4f95f4d 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/B143686595.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/B143686595.java
@@ -39,6 +39,7 @@
         .addProgramClasses(TestClass.class)
         .addClasspathClasses(I.class)
         .addKeepMainRule(TestClass.class)
+        .enableInliningAnnotations()
         .setMinApi(parameters.getApiLevel())
         .compile()
         .addRunClasspathFiles(libraryResult.writeToZip())
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/InstanceFieldValuePropagationWithMultipleInstanceInitializersTest.java b/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/InstanceFieldValuePropagationWithMultipleInstanceInitializersTest.java
index d2b739e..6c7cf37 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/InstanceFieldValuePropagationWithMultipleInstanceInitializersTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/InstanceFieldValuePropagationWithMultipleInstanceInitializersTest.java
@@ -40,6 +40,7 @@
     testForR8(parameters.getBackend())
         .addInnerClasses(InstanceFieldValuePropagationWithMultipleInstanceInitializersTest.class)
         .addKeepMainRule(TestClass.class)
+        .enableNeverClassInliningAnnotations()
         .setMinApi(parameters.getApiLevel())
         .compile()
         .inspect(this::inspect)
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/outliner/OutlineFromStaticInterfaceMethodTest.java b/src/test/java/com/android/tools/r8/ir/optimize/outliner/OutlineFromStaticInterfaceMethodTest.java
index ec4c859..c0267f2 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/outliner/OutlineFromStaticInterfaceMethodTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/outliner/OutlineFromStaticInterfaceMethodTest.java
@@ -50,6 +50,7 @@
               options.outline.minSize = 2;
             })
         .enableInliningAnnotations()
+        .enableNeverClassInliningAnnotations()
         .setMinApi(parameters.getApiLevel())
         .compile()
         .inspect(this::inspect)
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/reflection/GetClassTest.java b/src/test/java/com/android/tools/r8/ir/optimize/reflection/GetClassTest.java
index 3e28157..d1d5f1f 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/reflection/GetClassTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/reflection/GetClassTest.java
@@ -13,6 +13,7 @@
 import com.android.tools.r8.ForceInline;
 import com.android.tools.r8.NeverInline;
 import com.android.tools.r8.NoHorizontalClassMerging;
+import com.android.tools.r8.R8TestBuilder;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.ListUtils;
@@ -218,6 +219,8 @@
     testForR8(parameters.getBackend())
         .setMode(mode)
         .addInnerClasses(GetClassTest.class)
+        .addForceInliningAnnotations()
+        .applyIf(mode == CompilationMode.RELEASE, R8TestBuilder::enableForceInliningAnnotations)
         .enableInliningAnnotations()
         .enableNoHorizontalClassMergingAnnotations()
         .addKeepMainRule(MAIN)
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/reflection/GetSimpleNameTest.java b/src/test/java/com/android/tools/r8/ir/optimize/reflection/GetSimpleNameTest.java
index a9a5c6f..81612ee 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/reflection/GetSimpleNameTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/reflection/GetSimpleNameTest.java
@@ -214,6 +214,7 @@
     R8TestRunResult result =
         testForR8(parameters.getBackend())
             .addProgramFiles(classPaths)
+            .enableForceInliningAnnotations()
             .enableInliningAnnotations()
             .addKeepMainRule(MAIN)
             .addKeepRules("-keep class **.ClassGetSimpleName*")
@@ -243,6 +244,7 @@
             .addKeepRules("-keep,allowobfuscation class **.Outer*")
             .addKeepAttributes("InnerClasses", "EnclosingMethod")
             .addKeepRules("-printmapping " + createNewMappingPath().toAbsolutePath().toString())
+            .enableForceInliningAnnotations()
             .minification(enableMinification)
             .setMinApi(parameters.getApiLevel())
             .addOptionsModification(this::configure)
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/staticizer/ClassStaticizerTest.java b/src/test/java/com/android/tools/r8/ir/optimize/staticizer/ClassStaticizerTest.java
index 9ee6ad7..6e91c38 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/staticizer/ClassStaticizerTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/staticizer/ClassStaticizerTest.java
@@ -116,6 +116,7 @@
         .addKeepMainRule(main)
         .addKeepAttributes("InnerClasses", "EnclosingMethod")
         .addOptionsModification(this::configure)
+        .enableInliningAnnotations()
         .setMinApi(parameters.getApiLevel())
         .run(parameters.getRuntime(), main)
         .assertSuccessWithOutput(EXPECTED);
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/string/StringLengthTest.java b/src/test/java/com/android/tools/r8/ir/optimize/string/StringLengthTest.java
index 59c3d5c..7924a77 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/string/StringLengthTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/string/StringLengthTest.java
@@ -96,6 +96,7 @@
     R8TestRunResult result =
         testForR8(parameters.getBackend())
             .addProgramClasses(MAIN)
+            .enableForceInliningAnnotations()
             .enableInliningAnnotations()
             .addKeepMainRule(MAIN)
             .setMinApi(parameters.getApiLevel())
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/string/StringValueOfTest.java b/src/test/java/com/android/tools/r8/ir/optimize/string/StringValueOfTest.java
index 7d68bda..2e1709c 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/string/StringValueOfTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/string/StringValueOfTest.java
@@ -111,6 +111,7 @@
     SingleTestRunResult<?> result =
         testForR8(parameters.getBackend())
             .addProgramClassesAndInnerClasses(MAIN)
+            .enableForceInliningAnnotations()
             .enableInliningAnnotations()
             .enableMemberValuePropagationAnnotations()
             .addKeepMainRule(MAIN)
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/switches/StringSwitchWitNonConstIdTest.java b/src/test/java/com/android/tools/r8/ir/optimize/switches/StringSwitchWitNonConstIdTest.java
new file mode 100644
index 0000000..c993353
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/switches/StringSwitchWitNonConstIdTest.java
@@ -0,0 +1,106 @@
+// 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.optimize.switches;
+
+import static org.junit.Assume.assumeTrue;
+
+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;
+
+@RunWith(Parameterized.class)
+public class StringSwitchWitNonConstIdTest extends TestBase {
+
+  private final TestParameters parameters;
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  public StringSwitchWitNonConstIdTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testD8() throws Exception {
+    assumeTrue(parameters.isDexRuntime());
+    testForD8()
+        .addProgramClasses(Main.class)
+        .release()
+        .setMinApi(parameters.getApiLevel())
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines("Foo", "Bar", "Baz", "Qux");
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    testForR8(parameters.getBackend())
+        .addProgramClasses(Main.class)
+        .addKeepMainRule(Main.class)
+        .enableInliningAnnotations()
+        .release()
+        .setMinApi(parameters.getApiLevel())
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines("Foo", "Bar", "Baz", "Qux");
+  }
+
+  static class Main {
+
+    public static void main(String[] args) {
+      test("Foo");
+      test("Bar");
+      test("Baz");
+      test("Qux");
+    }
+
+    @NeverInline
+    static void test(String str) {
+      int hashCode = str.hashCode();
+      int id = 0;
+      int foo = one();
+      switch (hashCode) {
+        case 70822: // "Foo".hashCode()
+          if (str.equals("Foo")) {
+            id = foo;
+          }
+          break;
+        case 66547: // "Bar".hashCode()
+          if (str.equals("Bar")) {
+            id = 2;
+          }
+          break;
+        case 66555: // "Baz".hashCode()
+          if (str.equals("Baz")) {
+            id = 3;
+          }
+          break;
+      }
+      switch (id) {
+        case 1:
+          System.out.println("Foo");
+          break;
+        case 2:
+          System.out.println("Bar");
+          break;
+        case 3:
+          System.out.println("Baz");
+          break;
+        default:
+          System.out.println("Qux");
+          break;
+      }
+    }
+
+    @NeverInline
+    static int one() {
+      return 1;
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/switches/SwitchMapInvalidInitTest.java b/src/test/java/com/android/tools/r8/ir/optimize/switches/SwitchMapInvalidInitTest.java
index c8d8086..cb50223 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/switches/SwitchMapInvalidInitTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/switches/SwitchMapInvalidInitTest.java
@@ -35,6 +35,7 @@
     testForR8(parameters.getBackend())
         .addKeepMainRule(Main.class)
         .enableInliningAnnotations()
+        .enableNeverClassInliningAnnotations()
         .addInnerClasses(SwitchMapInvalidInitTest.class)
         .setMinApi(parameters.getApiLevel())
         .compile()
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/switches/SwitchMapInvalidOrdinalTest.java b/src/test/java/com/android/tools/r8/ir/optimize/switches/SwitchMapInvalidOrdinalTest.java
index a779afb..8c84a76 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/switches/SwitchMapInvalidOrdinalTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/switches/SwitchMapInvalidOrdinalTest.java
@@ -45,6 +45,7 @@
                 + "com.android.tools.r8.ir.optimize.switches.SwitchMapInvalidOrdinalTest$MyEnum {"
                 + " static <fields>; }")
         .addInnerClasses(SwitchMapInvalidOrdinalTest.class)
+        .enableNeverClassInliningAnnotations()
         .setMinApi(parameters.getApiLevel())
         .compile()
         .run(parameters.getRuntime(), Main.class)
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 8f7c579..c1b1f8b 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
@@ -17,4 +17,12 @@
       throw new ClassCastException();
     }
   }
+
+  public static IncompatibleClassChangeError throwIncompatibleClassChangeError() {
+    throw new IncompatibleClassChangeError();
+  }
+
+  public static NoSuchMethodError throwNoSuchMethodError() {
+    throw new NoSuchMethodError();
+  }
 }
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/uninstantiatedtypes/UninstantiatedAnnotatedArgumentsTest.java b/src/test/java/com/android/tools/r8/ir/optimize/uninstantiatedtypes/UninstantiatedAnnotatedArgumentsTest.java
index 035ab54..c6dfe83 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/uninstantiatedtypes/UninstantiatedAnnotatedArgumentsTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/uninstantiatedtypes/UninstantiatedAnnotatedArgumentsTest.java
@@ -54,6 +54,7 @@
   public void test() throws Exception {
     testForR8(parameters.getBackend())
         .addInnerClasses(UninstantiatedAnnotatedArgumentsTest.class)
+        .addConstantArgumentAnnotations()
         .addKeepMainRule(TestClass.class)
         .addKeepClassRules(Instantiated.class, Uninstantiated.class)
         .addKeepAttributes("RuntimeVisibleParameterAnnotations")
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/unusedarguments/UnusedAnnotatedArgumentsTest.java b/src/test/java/com/android/tools/r8/ir/optimize/unusedarguments/UnusedAnnotatedArgumentsTest.java
index 09c0de7..bd1b805 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/unusedarguments/UnusedAnnotatedArgumentsTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/unusedarguments/UnusedAnnotatedArgumentsTest.java
@@ -52,6 +52,7 @@
   public void test() throws Exception {
     testForR8(parameters.getBackend())
         .addInnerClasses(UnusedAnnotatedArgumentsTest.class)
+        .addUnusedArgumentAnnotations()
         .addKeepMainRule(TestClass.class)
         .addKeepClassRules(Used.class, Unused.class)
         .addKeepAttributes("RuntimeVisibleParameterAnnotations")
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/unusedinterfaces/UnusedInterfaceRemovalPackageBoundaryTest.java b/src/test/java/com/android/tools/r8/ir/optimize/unusedinterfaces/UnusedInterfaceRemovalPackageBoundaryTest.java
new file mode 100644
index 0000000..9c4046d
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/unusedinterfaces/UnusedInterfaceRemovalPackageBoundaryTest.java
@@ -0,0 +1,91 @@
+// Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.optimize.unusedinterfaces;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isAbsent;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.NoVerticalClassMerging;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.ir.optimize.unusedinterfaces.testclasses.UnusedInterfaceRemovalPackageBoundaryTestClasses;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+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 UnusedInterfaceRemovalPackageBoundaryTest extends TestBase {
+
+  private static final Class<?> I_CLASS = UnusedInterfaceRemovalPackageBoundaryTestClasses.getI();
+  private static final Class<?> J_CLASS = UnusedInterfaceRemovalPackageBoundaryTestClasses.J.class;
+
+  private final TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  public UnusedInterfaceRemovalPackageBoundaryTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void test() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(getClass(), UnusedInterfaceRemovalPackageBoundaryTestClasses.class)
+        .addKeepMainRule(TestClass.class)
+        .addKeepClassRules(I_CLASS)
+        .enableNeverClassInliningAnnotations()
+        .enableNoVerticalClassMergingAnnotations()
+        .setMinApi(parameters.getApiLevel())
+        .noMinification()
+        .compile()
+        .inspect(
+            inspector -> {
+              ClassSubject iClassSubject = inspector.clazz(I_CLASS);
+              assertThat(iClassSubject, isPresent());
+
+              ClassSubject jClassSubject = inspector.clazz(J_CLASS);
+              assertThat(jClassSubject, isPresent());
+
+              ClassSubject kClassSubject = inspector.clazz(K.class);
+              assertThat(kClassSubject, isAbsent());
+
+              ClassSubject aClassSubject = inspector.clazz(A.class);
+              assertThat(aClassSubject, isPresent());
+              assertEquals(1, aClassSubject.getDexProgramClass().getInterfaces().size());
+              assertEquals(
+                  jClassSubject.getDexProgramClass().getType(),
+                  aClassSubject.getDexProgramClass().getInterfaces().get(0));
+            })
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutputLines("A");
+  }
+
+  static class TestClass {
+
+    public static void main(String[] args) {
+      new A();
+    }
+  }
+
+  @NoVerticalClassMerging
+  interface K extends UnusedInterfaceRemovalPackageBoundaryTestClasses.J {}
+
+  @NeverClassInline
+  static class A implements K {
+
+    A() {
+      System.out.println("A");
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/unusedinterfaces/testclasses/UnusedInterfaceRemovalPackageBoundaryTestClasses.java b/src/test/java/com/android/tools/r8/ir/optimize/unusedinterfaces/testclasses/UnusedInterfaceRemovalPackageBoundaryTestClasses.java
new file mode 100644
index 0000000..0b623b2
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/unusedinterfaces/testclasses/UnusedInterfaceRemovalPackageBoundaryTestClasses.java
@@ -0,0 +1,20 @@
+// Copyright (c) 2021, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.optimize.unusedinterfaces.testclasses;
+
+import com.android.tools.r8.NoVerticalClassMerging;
+
+public class UnusedInterfaceRemovalPackageBoundaryTestClasses {
+
+  @NoVerticalClassMerging
+  interface I {}
+
+  @NoVerticalClassMerging
+  public interface J extends I {}
+
+  public static Class<?> getI() {
+    return I.class;
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/regalloc/RegisterMoveSchedulerTest.java b/src/test/java/com/android/tools/r8/ir/regalloc/RegisterMoveSchedulerTest.java
index 8d01a11..105008c 100644
--- a/src/test/java/com/android/tools/r8/ir/regalloc/RegisterMoveSchedulerTest.java
+++ b/src/test/java/com/android/tools/r8/ir/regalloc/RegisterMoveSchedulerTest.java
@@ -99,6 +99,17 @@
     }
 
     @Override
+    public void replaceCurrentInstructionWithThrow(
+        AppView<?> appView,
+        IRCode code,
+        ListIterator<BasicBlock> blockIterator,
+        Value exceptionValue,
+        Set<BasicBlock> blocksToRemove,
+        Set<Value> affectedValues) {
+      throw new Unimplemented();
+    }
+
+    @Override
     public void replaceCurrentInstructionWithThrowNull(
         AppView<? extends AppInfoWithClassHierarchy> appView,
         IRCode code,
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 293caa8..e4622be 100644
--- a/src/test/java/com/android/tools/r8/kotlin/KotlinClassInlinerTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/KotlinClassInlinerTest.java
@@ -16,6 +16,7 @@
 import com.android.tools.r8.KotlinCompilerTool.KotlinCompiler;
 import com.android.tools.r8.R8FullTestBuilder;
 import com.android.tools.r8.R8TestRunResult;
+import com.android.tools.r8.TestShrinkerBuilder;
 import com.android.tools.r8.ThrowableConsumer;
 import com.android.tools.r8.ToolHelper.KotlinTargetVersion;
 import com.android.tools.r8.code.NewInstance;
@@ -90,6 +91,7 @@
                 testBuilder
                     // TODO(jsjeon): Introduce @NeverInline to kotlinR8TestResources
                     .addKeepRules("-neverinline class * { void test*State*(...); }")
+                    .addDontWarnJetBrainsNotNullAnnotation()
                     .noClassInlining())
         .inspect(
             inspector -> {
@@ -110,7 +112,8 @@
             testBuilder ->
                 testBuilder
                     // TODO(jsjeon): Introduce @NeverInline to kotlinR8TestResources
-                    .addKeepRules("-neverinline class * { void test*State*(...); }"))
+                    .addKeepRules("-neverinline class * { void test*State*(...); }")
+                    .addDontWarnJetBrainsNotNullAnnotation())
         .inspect(
             inspector -> {
               Predicate<DexType> lambdaCheck = createLambdaCheck(inspector);
@@ -156,6 +159,7 @@
                         "-neverinline class * { void test*State*(...); }",
                         "-neverinline class * { void testBigExtraMethod(...); }",
                         "-neverinline class * { void testBigExtraMethodReturningLambda(...); }")
+                    .addDontWarnJetBrainsAnnotations()
                     .noClassInlining())
         .inspect(
             inspector -> {
@@ -197,9 +201,10 @@
                 testBuilder
                     // TODO(jsjeon): Introduce @NeverInline to kotlinR8TestResources
                     .addKeepRules(
-                    "-neverinline class * { void test*State*(...); }",
-                    "-neverinline class * { void testBigExtraMethod(...); }",
-                    "-neverinline class * { void testBigExtraMethodReturningLambda(...); }"))
+                        "-neverinline class * { void test*State*(...); }",
+                        "-neverinline class * { void testBigExtraMethod(...); }",
+                        "-neverinline class * { void testBigExtraMethodReturningLambda(...); }")
+                    .addDontWarnJetBrainsAnnotations())
         .inspect(
             inspector -> {
               Predicate<DexType> lambdaCheck = createLambdaCheck(inspector);
@@ -280,7 +285,10 @@
   public void testDataClass() throws Exception {
     assumeTrue("Only work with -allowaccessmodification", allowAccessModification);
     final String mainClassName = "class_inliner_data_class.MainKt";
-    runTestWithDefaults("class_inliner_data_class", mainClassName)
+    runTestWithDefaults(
+            "class_inliner_data_class",
+            mainClassName,
+            TestShrinkerBuilder::addDontWarnJetBrainsAnnotations)
         .inspect(
             inspector -> {
               ClassSubject clazz = inspector.clazz(mainClassName);
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 ec1793d..e5d0d09 100644
--- a/src/test/java/com/android/tools/r8/kotlin/KotlinClassStaticizerTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/KotlinClassStaticizerTest.java
@@ -74,6 +74,10 @@
     return runTest(
         folder,
         mainClass,
-        testBuilder -> testBuilder.noClassInlining().noClassStaticizing(noClassStaticizing));
+        testBuilder ->
+            testBuilder
+                .addDontWarnJetBrainsNotNullAnnotation()
+                .noClassInlining()
+                .noClassStaticizing(noClassStaticizing));
   }
 }
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 c683b1a..eb1ca57 100644
--- a/src/test/java/com/android/tools/r8/kotlin/KotlinIntrinsicsInlineTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/KotlinIntrinsicsInlineTest.java
@@ -58,6 +58,7 @@
             StringUtils.lines(
                 "-keepclasseswithmembers class " + MAIN + "{", "  public static *** *(...);", "}"))
         .allowAccessModification(allowAccessModification)
+        .addDontWarnJetBrainsNotNullAnnotation()
         .noMinification()
         .setMinApi(parameters.getRuntime())
         .compile()
@@ -102,6 +103,7 @@
                 "-keepclasseswithmembers class " + MAIN + "{",
                 "  public static *** " + methodName + "(...);",
                 "}"))
+        .addDontWarnJetBrainsNotNullAnnotation()
         .allowAccessModification(allowAccessModification)
         .noMinification()
         .setMinApi(parameters.getRuntime())
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 b59d98c..5e25b92 100644
--- a/src/test/java/com/android/tools/r8/kotlin/KotlinUnusedArgumentsInLambdasTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/KotlinUnusedArgumentsInLambdasTest.java
@@ -10,6 +10,7 @@
 import static org.junit.Assert.assertTrue;
 
 import com.android.tools.r8.KotlinCompilerTool.KotlinCompiler;
+import com.android.tools.r8.TestShrinkerBuilder;
 import com.android.tools.r8.ToolHelper.KotlinTargetVersion;
 import com.android.tools.r8.utils.BooleanUtils;
 import com.android.tools.r8.utils.codeinspector.MethodSubject;
@@ -36,7 +37,10 @@
   @Test
   public void testMergingKStyleLambdasAfterUnusedArgumentRemoval() throws Exception {
     final String mainClassName = "unused_arg_in_lambdas_kstyle.MainKt";
-    runTest("unused_arg_in_lambdas_kstyle", mainClassName)
+    runTest(
+            "unused_arg_in_lambdas_kstyle",
+            mainClassName,
+            TestShrinkerBuilder::addDontWarnJetBrainsAnnotations)
         .inspect(
             inspector ->
                 inspector.forAllClasses(
@@ -57,7 +61,10 @@
   @Test
   public void testMergingJStyleLambdasAfterUnusedArgumentRemoval() throws Exception {
     final String mainClassName = "unused_arg_in_lambdas_jstyle.MainKt";
-    runTest("unused_arg_in_lambdas_jstyle", mainClassName)
+    runTest(
+            "unused_arg_in_lambdas_jstyle",
+            mainClassName,
+            TestShrinkerBuilder::addDontWarnJetBrainsAnnotations)
         .inspect(
             inspector ->
                 inspector.forAllClasses(
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 c358325..6a8e196 100644
--- a/src/test/java/com/android/tools/r8/kotlin/KotlinUnusedSingletonTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/KotlinUnusedSingletonTest.java
@@ -11,6 +11,7 @@
 import static org.junit.Assert.assertTrue;
 
 import com.android.tools.r8.KotlinCompilerTool.KotlinCompiler;
+import com.android.tools.r8.TestShrinkerBuilder;
 import com.android.tools.r8.ToolHelper.KotlinTargetVersion;
 import com.android.tools.r8.utils.BooleanUtils;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
@@ -45,7 +46,10 @@
   public void b110196118() throws Exception {
     final String mainClassName = "unused_singleton.MainKt";
     final String moduleName = "unused_singleton.TestModule";
-    runTest("unused_singleton", mainClassName)
+    runTest(
+            "unused_singleton",
+            mainClassName,
+            TestShrinkerBuilder::addDontWarnJetBrainsNotNullAnnotation)
         .inspect(
             inspector -> {
               ClassSubject main = inspector.clazz(mainClassName);
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 e90a8b6..3137d61 100644
--- a/src/test/java/com/android/tools/r8/kotlin/ProcessKotlinReflectionLibTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/ProcessKotlinReflectionLibTest.java
@@ -9,11 +9,11 @@
 import com.android.tools.r8.KotlinTestBase;
 import com.android.tools.r8.R8FullTestBuilder;
 import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestShrinkerBuilder;
 import com.android.tools.r8.ThrowableConsumer;
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.ToolHelper.KotlinTargetVersion;
 import com.android.tools.r8.shaking.ProguardKeepAttributes;
-import com.google.common.collect.ImmutableList;
 import java.util.Collection;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -38,17 +38,11 @@
     this.parameters = parameters;
   }
 
-  private void test(Collection<String> rules) throws Exception {
-    test(rules, null);
-  }
-
-  private void test(
-      Collection<String> rules, ThrowableConsumer<R8FullTestBuilder> consumer) throws Exception {
+  private void test(ThrowableConsumer<R8FullTestBuilder> consumer) throws Exception {
     testForR8(parameters.getBackend())
         .addLibraryFiles(
             ToolHelper.getMostRecentAndroidJar(), ToolHelper.getKotlinStdlibJar(kotlinc))
         .addProgramFiles(ToolHelper.getKotlinReflectJar(kotlinc))
-        .addKeepRules(rules)
         .addKeepAttributes(ProguardKeepAttributes.SIGNATURE)
         .addKeepAttributes(ProguardKeepAttributes.INNER_CLASSES)
         .addKeepAttributes(ProguardKeepAttributes.ENCLOSING_METHOD)
@@ -58,54 +52,61 @@
 
   @Test
   public void testAsIs() throws Exception {
-    test(ImmutableList.of("-dontshrink", "-dontoptimize", "-dontobfuscate"));
+    test(
+        builder ->
+            builder.addDontWarnJetBrains().noMinification().noOptimization().noTreeShaking());
   }
 
   @Test
   public void testDontShrinkAndDontOptimize() throws Exception {
-    test(ImmutableList.of("-dontshrink", "-dontoptimize"));
+    test(builder -> builder.addDontWarnJetBrains().noOptimization().noTreeShaking());
   }
 
   @Test
   public void testDontShrinkAndDontOptimizeDifferently() throws Exception {
-     test(
-         ImmutableList.of("-keep,allowobfuscation class **.*KClasses*"),
-         tb -> {
-           tb.noTreeShaking();
-           tb.addOptionsModification(o -> {
-             // Randomly choose a couple of optimizations.
-             o.enableVerticalClassMerging = false;
-             o.enableClassStaticizer = false;
-             o.outline.enabled = false;
-           });
-         });
+    test(
+        builder ->
+            builder
+                .addKeepRules("-keep,allowobfuscation class **.*KClasses*")
+                .addDontWarnJetBrains()
+                .noTreeShaking()
+                .addOptionsModification(
+                    o -> {
+                      // Randomly choose a couple of optimizations.
+                      o.enableVerticalClassMerging = false;
+                      o.enableClassStaticizer = false;
+                      o.outline.enabled = false;
+                    }));
   }
 
   @Test
   public void testDontShrinkAndDontObfuscate() throws Exception {
-    test(ImmutableList.of("-dontshrink", "-dontobfuscate"));
+    test(builder -> builder.addDontWarnJetBrains().noMinification().noTreeShaking());
   }
 
   @Test
   public void testDontShrink() throws Exception {
-    test(ImmutableList.of("-dontshrink"));
+    test(builder -> builder.addDontWarnJetBrains().noTreeShaking());
   }
 
   @Test
   public void testDontShrinkDifferently() throws Exception {
     test(
-        ImmutableList.of("-keep,allowobfuscation class **.*KClasses*"),
-        tb -> tb.noTreeShaking());
+        builder ->
+            builder
+                .addKeepRules("-keep,allowobfuscation class **.*KClasses*")
+                .addDontWarnJetBrains()
+                .noTreeShaking());
   }
 
   @Test
   public void testDontOptimize() throws Exception {
-    test(ImmutableList.of("-dontoptimize"));
+    test(TestShrinkerBuilder::noOptimization);
   }
 
   @Test
   public void testDontObfuscate() throws Exception {
-    test(ImmutableList.of("-dontobfuscate"));
+    test(TestShrinkerBuilder::noMinification);
   }
 
 }
diff --git a/src/test/java/com/android/tools/r8/kotlin/ProcessKotlinStdlibTest.java b/src/test/java/com/android/tools/r8/kotlin/ProcessKotlinStdlibTest.java
index 7b3fd69..7166aed 100644
--- a/src/test/java/com/android/tools/r8/kotlin/ProcessKotlinStdlibTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/ProcessKotlinStdlibTest.java
@@ -9,6 +9,7 @@
 import com.android.tools.r8.KotlinTestBase;
 import com.android.tools.r8.R8FullTestBuilder;
 import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestShrinkerBuilder;
 import com.android.tools.r8.ThrowableConsumer;
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.ToolHelper.KotlinTargetVersion;
@@ -37,13 +38,12 @@
     this.parameters = parameters;
   }
 
-  private void test(Collection<String> rules, boolean expectInvalidFoo) throws Exception {
-    test(rules, expectInvalidFoo, null);
+  private void test(Collection<String> rules) throws Exception {
+    test(rules, null);
   }
 
   private void test(
       Collection<String> rules,
-      boolean expectInvalidDebugInfo,
       ThrowableConsumer<R8FullTestBuilder> consumer)
       throws Exception {
     testForR8(parameters.getBackend())
@@ -58,56 +58,60 @@
 
   @Test
   public void testAsIs() throws Exception {
-    test(ImmutableList.of("-dontshrink", "-dontoptimize", "-dontobfuscate"), true);
+    test(
+        ImmutableList.of("-dontshrink", "-dontoptimize", "-dontobfuscate"),
+        TestShrinkerBuilder::addDontWarnJetBrainsAnnotations);
   }
 
   @Test
   public void testDontShrinkAndDontOptimize() throws Exception {
-    test(ImmutableList.of("-dontshrink", "-dontoptimize"), true);
+    test(
+        ImmutableList.of("-dontshrink", "-dontoptimize"),
+        TestShrinkerBuilder::addDontWarnJetBrainsAnnotations);
   }
 
   @Test
   public void testDontShrinkAndDontOptimizeDifferently() throws Exception {
     test(
         ImmutableList.of("-keep,allowobfuscation class **.*Exception*"),
-        true,
-        tb -> {
-          tb.noTreeShaking();
-          tb.addOptionsModification(
-              o -> {
-                // Randomly choose a couple of optimizations.
-                o.enableClassInlining = false;
-                o.enableLambdaMerging = false;
-                o.enableValuePropagation = false;
-              });
-        });
+        tb ->
+            tb.addDontWarnJetBrainsAnnotations()
+                .noTreeShaking()
+                .addOptionsModification(
+                    o -> {
+                      // Randomly choose a couple of optimizations.
+                      o.enableClassInlining = false;
+                      o.enableLambdaMerging = false;
+                      o.enableValuePropagation = false;
+                    }));
   }
 
   @Test
   public void testDontShrinkAndDontObfuscate() throws Exception {
-    test(ImmutableList.of("-dontshrink", "-dontobfuscate"), true);
+    test(
+        ImmutableList.of("-dontshrink", "-dontobfuscate"),
+        TestShrinkerBuilder::addDontWarnJetBrainsAnnotations);
   }
 
   @Test
   public void testDontShrink() throws Exception {
-    test(ImmutableList.of("-dontshrink"), true);
+    test(ImmutableList.of("-dontshrink"), TestShrinkerBuilder::addDontWarnJetBrainsAnnotations);
   }
 
   @Test
   public void testDontShrinkDifferently() throws Exception {
     test(
         ImmutableList.of("-keep,allowobfuscation class **.*Exception*"),
-        true,
-        tb -> tb.noTreeShaking());
+        tb -> tb.addDontWarnJetBrainsAnnotations().noTreeShaking());
   }
 
   @Test
   public void testDontOptimize() throws Exception {
-    test(ImmutableList.of("-dontoptimize"), false);
+    test(ImmutableList.of("-dontoptimize"));
   }
 
   @Test
   public void testDontObfuscate() throws Exception {
-    test(ImmutableList.of("-dontobfuscate"), false);
+    test(ImmutableList.of("-dontobfuscate"));
   }
 }
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 cd5d8b1..882bc22 100644
--- a/src/test/java/com/android/tools/r8/kotlin/R8KotlinAccessorTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/R8KotlinAccessorTest.java
@@ -11,6 +11,7 @@
 
 import com.android.tools.r8.KotlinCompilerTool.KotlinCompiler;
 import com.android.tools.r8.R8TestBuilder;
+import com.android.tools.r8.TestShrinkerBuilder;
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.ToolHelper.KotlinTargetVersion;
 import com.android.tools.r8.ToolHelper.ProcessResult;
@@ -83,7 +84,10 @@
     final TestKotlinCompanionClass testedClass = COMPANION_PROPERTY_CLASS;
     String mainClass = addMainToClasspath("properties.CompanionPropertiesKt",
         "companionProperties_usePrimitiveProp");
-    runTest(PROPERTIES_PACKAGE_NAME, mainClass, R8TestBuilder::noClassStaticizing)
+    runTest(
+            PROPERTIES_PACKAGE_NAME,
+            mainClass,
+            builder -> builder.addDontWarnJetBrainsNotNullAnnotation().noClassStaticizing())
         .inspect(
             inspector -> {
               ClassSubject outerClass =
@@ -116,7 +120,10 @@
     final TestKotlinCompanionClass testedClass = COMPANION_PROPERTY_CLASS;
     String mainClass = addMainToClasspath("properties.CompanionPropertiesKt",
         "companionProperties_usePrivateProp");
-    runTest(PROPERTIES_PACKAGE_NAME, mainClass, R8TestBuilder::noClassStaticizing)
+    runTest(
+            PROPERTIES_PACKAGE_NAME,
+            mainClass,
+            builder -> builder.addDontWarnJetBrainsNotNullAnnotation().noClassStaticizing())
         .inspect(
             inspector -> {
               ClassSubject outerClass =
@@ -151,7 +158,10 @@
     final TestKotlinCompanionClass testedClass = COMPANION_PROPERTY_CLASS;
     String mainClass = addMainToClasspath("properties.CompanionPropertiesKt",
         "companionProperties_useInternalProp");
-    runTest(PROPERTIES_PACKAGE_NAME, mainClass, R8TestBuilder::noClassStaticizing)
+    runTest(
+            PROPERTIES_PACKAGE_NAME,
+            mainClass,
+            builder -> builder.addDontWarnJetBrainsNotNullAnnotation().noClassStaticizing())
         .inspect(
             inspector -> {
               ClassSubject outerClass =
@@ -185,7 +195,10 @@
     final TestKotlinCompanionClass testedClass = COMPANION_PROPERTY_CLASS;
     String mainClass = addMainToClasspath("properties.CompanionPropertiesKt",
         "companionProperties_usePublicProp");
-    runTest(PROPERTIES_PACKAGE_NAME, mainClass, R8TestBuilder::noClassStaticizing)
+    runTest(
+            PROPERTIES_PACKAGE_NAME,
+            mainClass,
+            builder -> builder.addDontWarnJetBrainsNotNullAnnotation().noClassStaticizing())
         .inspect(
             inspector -> {
               ClassSubject outerClass =
@@ -219,7 +232,10 @@
     final TestKotlinCompanionClass testedClass = COMPANION_LATE_INIT_PROPERTY_CLASS;
     String mainClass = addMainToClasspath("properties.CompanionLateInitPropertiesKt",
         "companionLateInitProperties_usePrivateLateInitProp");
-    runTest(PROPERTIES_PACKAGE_NAME, mainClass, R8TestBuilder::noClassStaticizing)
+    runTest(
+            PROPERTIES_PACKAGE_NAME,
+            mainClass,
+            builder -> builder.addDontWarnJetBrainsAnnotations().noClassStaticizing())
         .inspect(
             inspector -> {
               ClassSubject outerClass =
@@ -252,7 +268,10 @@
     final TestKotlinCompanionClass testedClass = COMPANION_LATE_INIT_PROPERTY_CLASS;
     String mainClass = addMainToClasspath("properties.CompanionLateInitPropertiesKt",
         "companionLateInitProperties_useInternalLateInitProp");
-    runTest(PROPERTIES_PACKAGE_NAME, mainClass)
+    runTest(
+            PROPERTIES_PACKAGE_NAME,
+            mainClass,
+            TestShrinkerBuilder::addDontWarnJetBrainsAnnotations)
         .inspect(
             inspector -> {
               ClassSubject outerClass =
@@ -281,7 +300,10 @@
     final TestKotlinCompanionClass testedClass = COMPANION_LATE_INIT_PROPERTY_CLASS;
     String mainClass = addMainToClasspath("properties.CompanionLateInitPropertiesKt",
         "companionLateInitProperties_usePublicLateInitProp");
-    runTest(PROPERTIES_PACKAGE_NAME, mainClass)
+    runTest(
+            PROPERTIES_PACKAGE_NAME,
+            mainClass,
+            TestShrinkerBuilder::addDontWarnJetBrainsAnnotations)
         .inspect(
             inspector -> {
               ClassSubject outerClass =
@@ -359,7 +381,7 @@
     String mainClass =
         addMainToClasspath(
             "accessors.PropertyAccessorForInnerClassKt", "noUseOfPropertyAccessorFromInnerClass");
-    runTest("accessors", mainClass)
+    runTest("accessors", mainClass, TestShrinkerBuilder::addDontWarnJetBrainsNotNullAnnotation)
         .inspect(
             inspector -> {
               // Class is removed because the instantiation of the inner class has no side effects.
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 ef13161..3faf486 100644
--- a/src/test/java/com/android/tools/r8/kotlin/R8KotlinDataClassTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/R8KotlinDataClassTest.java
@@ -66,7 +66,10 @@
             "dataclass",
             mainClassName,
             testBuilder ->
-                testBuilder.addKeepRules(extraRules).addOptionsModification(disableClassInliner))
+                testBuilder
+                    .addKeepRules(extraRules)
+                    .addDontWarnJetBrainsAnnotations()
+                    .addOptionsModification(disableClassInliner))
         .inspect(
             inspector -> {
               ClassSubject dataClass = checkClassIsKept(inspector, TEST_DATA_CLASS.getClassName());
@@ -107,7 +110,10 @@
             "dataclass",
             mainClassName,
             testBuilder ->
-                testBuilder.addKeepRules(extraRules).addOptionsModification(disableClassInliner))
+                testBuilder
+                    .addKeepRules(extraRules)
+                    .addDontWarnJetBrainsAnnotations()
+                    .addOptionsModification(disableClassInliner))
         .inspect(
             inspector -> {
               ClassSubject dataClass = checkClassIsKept(inspector, TEST_DATA_CLASS.getClassName());
@@ -147,7 +153,10 @@
             "dataclass",
             mainClassName,
             testBuilder ->
-                testBuilder.addKeepRules(extraRules).addOptionsModification(disableClassInliner))
+                testBuilder
+                    .addKeepRules(extraRules)
+                    .addDontWarnJetBrainsAnnotations()
+                    .addOptionsModification(disableClassInliner))
         .inspect(
             inspector -> {
               ClassSubject dataClass = checkClassIsKept(inspector, TEST_DATA_CLASS.getClassName());
@@ -187,7 +196,10 @@
             "dataclass",
             mainClassName,
             testBuilder ->
-                testBuilder.addKeepRules(extraRules).addOptionsModification(disableClassInliner))
+                testBuilder
+                    .addKeepRules(extraRules)
+                    .addDontWarnJetBrainsAnnotations()
+                    .addOptionsModification(disableClassInliner))
         .inspect(
             inspector -> {
               ClassSubject dataClass = checkClassIsKept(inspector, TEST_DATA_CLASS.getClassName());
@@ -207,7 +219,10 @@
             "dataclass",
             mainClassName,
             testBuilder ->
-                testBuilder.addKeepRules(extraRules).addOptionsModification(disableClassInliner))
+                testBuilder
+                    .addKeepRules(extraRules)
+                    .addDontWarnJetBrainsAnnotations()
+                    .addOptionsModification(disableClassInliner))
         .inspect(
             inspector -> {
               ClassSubject dataClass = checkClassIsKept(inspector, TEST_DATA_CLASS.getClassName());
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 ea12c12..934ee16 100644
--- a/src/test/java/com/android/tools/r8/kotlin/R8KotlinIntrinsicsTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/R8KotlinIntrinsicsTest.java
@@ -47,7 +47,10 @@
             "intrinsics",
             "intrinsics.IntrinsicsKt",
             testBuilder ->
-                testBuilder.addKeepRules(extraRules).noHorizontalClassMerging(Intrinsics.class))
+                testBuilder
+                    .addKeepRules(extraRules)
+                    .addDontWarnJetBrainsAnnotations()
+                    .noHorizontalClassMerging(Intrinsics.class))
         .inspect(
             inspector -> {
               ClassSubject intrinsicsClass =
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 e04585c..d6d2477 100644
--- a/src/test/java/com/android/tools/r8/kotlin/R8KotlinPropertiesTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/R8KotlinPropertiesTest.java
@@ -113,7 +113,10 @@
     runTest(
             PACKAGE_NAME,
             mainClass,
-            testBuilder -> testBuilder.addOptionsModification(disableAggressiveClassOptimizations))
+            testBuilder ->
+                testBuilder
+                    .addDontWarnJetBrainsNotNullAnnotation()
+                    .addOptionsModification(disableAggressiveClassOptimizations))
         .inspect(
             inspector -> {
               checkClassIsRemoved(inspector, MUTABLE_PROPERTY_CLASS.getClassName());
@@ -127,7 +130,10 @@
     runTest(
             PACKAGE_NAME,
             mainClass,
-            testBuilder -> testBuilder.addOptionsModification(disableAggressiveClassOptimizations))
+            testBuilder ->
+                testBuilder
+                    .addDontWarnJetBrainsNotNullAnnotation()
+                    .addOptionsModification(disableAggressiveClassOptimizations))
         .inspect(
             inspector -> {
               ClassSubject classSubject =
@@ -156,7 +162,10 @@
     runTest(
             PACKAGE_NAME,
             mainClass,
-            testBuilder -> testBuilder.addOptionsModification(disableAggressiveClassOptimizations))
+            testBuilder ->
+                testBuilder
+                    .addDontWarnJetBrainsNotNullAnnotation()
+                    .addOptionsModification(disableAggressiveClassOptimizations))
         .inspect(
             inspector -> {
               ClassSubject classSubject =
@@ -184,7 +193,10 @@
     runTest(
             PACKAGE_NAME,
             mainClass,
-            testBuilder -> testBuilder.addOptionsModification(disableAggressiveClassOptimizations))
+            testBuilder ->
+                testBuilder
+                    .addDontWarnJetBrainsNotNullAnnotation()
+                    .addOptionsModification(disableAggressiveClassOptimizations))
         .inspect(
             inspector -> {
               ClassSubject classSubject =
@@ -212,7 +224,10 @@
     runTest(
             PACKAGE_NAME,
             mainClass,
-            testBuilder -> testBuilder.addOptionsModification(disableAggressiveClassOptimizations))
+            testBuilder ->
+                testBuilder
+                    .addDontWarnJetBrainsNotNullAnnotation()
+                    .addOptionsModification(disableAggressiveClassOptimizations))
         .inspect(
             inspector -> {
               ClassSubject classSubject =
@@ -240,7 +255,10 @@
     runTest(
             PACKAGE_NAME,
             mainClass,
-            testBuilder -> testBuilder.addOptionsModification(disableAggressiveClassOptimizations))
+            testBuilder ->
+                testBuilder
+                    .addDontWarnJetBrainsNotNullAnnotation()
+                    .addOptionsModification(disableAggressiveClassOptimizations))
         .inspect(
             inspector -> {
               ClassSubject classSubject =
@@ -283,7 +301,10 @@
     runTest(
             PACKAGE_NAME,
             mainClass,
-            testBuilder -> testBuilder.addOptionsModification(disableAggressiveClassOptimizations))
+            testBuilder ->
+                testBuilder
+                    .addDontWarnJetBrainsAnnotations()
+                    .addOptionsModification(disableAggressiveClassOptimizations))
         .inspect(
             inspector -> {
               ClassSubject classSubject =
@@ -310,7 +331,10 @@
     runTest(
             PACKAGE_NAME,
             mainClass,
-            testBuilder -> testBuilder.addOptionsModification(disableAggressiveClassOptimizations))
+            testBuilder ->
+                testBuilder
+                    .addDontWarnJetBrainsAnnotations()
+                    .addOptionsModification(disableAggressiveClassOptimizations))
         .inspect(
             inspector -> {
               ClassSubject classSubject =
@@ -335,7 +359,10 @@
     runTest(
             PACKAGE_NAME,
             mainClass,
-            testBuilder -> testBuilder.addOptionsModification(disableAggressiveClassOptimizations))
+            testBuilder ->
+                testBuilder
+                    .addDontWarnJetBrainsAnnotations()
+                    .addOptionsModification(disableAggressiveClassOptimizations))
         .inspect(
             inspector -> {
               ClassSubject classSubject =
@@ -358,7 +385,10 @@
     runTest(
             PACKAGE_NAME,
             mainClass,
-            testBuilder -> testBuilder.addOptionsModification(disableAggressiveClassOptimizations))
+            testBuilder ->
+                testBuilder
+                    .addDontWarnJetBrainsAnnotations()
+                    .addOptionsModification(disableAggressiveClassOptimizations))
         .inspect(
             inspector -> {
               ClassSubject classSubject =
@@ -426,7 +456,10 @@
     runTest(
             PACKAGE_NAME,
             mainClass,
-            testBuilder -> testBuilder.addOptionsModification(disableAggressiveClassOptimizations))
+            testBuilder ->
+                testBuilder
+                    .addDontWarnJetBrainsNotNullAnnotation()
+                    .addOptionsModification(disableAggressiveClassOptimizations))
         .inspect(
             inspector -> {
               ClassSubject outerClass =
@@ -459,7 +492,10 @@
     runTest(
             PACKAGE_NAME,
             mainClass,
-            testBuilder -> testBuilder.addOptionsModification(disableAggressiveClassOptimizations))
+            testBuilder ->
+                testBuilder
+                    .addDontWarnJetBrainsNotNullAnnotation()
+                    .addOptionsModification(disableAggressiveClassOptimizations))
         .inspect(
             inspector -> {
               ClassSubject outerClass =
@@ -498,7 +534,10 @@
     runTest(
             PACKAGE_NAME,
             mainClass,
-            testBuilder -> testBuilder.addOptionsModification(disableAggressiveClassOptimizations))
+            testBuilder ->
+                testBuilder
+                    .addDontWarnJetBrainsNotNullAnnotation()
+                    .addOptionsModification(disableAggressiveClassOptimizations))
         .inspect(
             inspector -> {
               ClassSubject outerClass =
@@ -531,7 +570,10 @@
     runTest(
             PACKAGE_NAME,
             mainClass,
-            testBuilder -> testBuilder.addOptionsModification(disableAggressiveClassOptimizations))
+            testBuilder ->
+                testBuilder
+                    .addDontWarnJetBrainsNotNullAnnotation()
+                    .addOptionsModification(disableAggressiveClassOptimizations))
         .inspect(
             inspector -> {
               ClassSubject outerClass =
@@ -566,7 +608,10 @@
     runTest(
             PACKAGE_NAME,
             mainClass,
-            testBuilder -> testBuilder.addOptionsModification(disableAggressiveClassOptimizations))
+            testBuilder ->
+                testBuilder
+                    .addDontWarnJetBrainsAnnotations()
+                    .addOptionsModification(disableAggressiveClassOptimizations))
         .inspect(
             inspector -> {
               ClassSubject outerClass =
@@ -603,7 +648,10 @@
     runTest(
             PACKAGE_NAME,
             mainClass,
-            testBuilder -> testBuilder.addOptionsModification(disableAggressiveClassOptimizations))
+            testBuilder ->
+                testBuilder
+                    .addDontWarnJetBrainsAnnotations()
+                    .addOptionsModification(disableAggressiveClassOptimizations))
         .inspect(
             inspector -> {
               ClassSubject outerClass =
@@ -631,7 +679,10 @@
     runTest(
             PACKAGE_NAME,
             mainClass,
-            testBuilder -> testBuilder.addOptionsModification(disableAggressiveClassOptimizations))
+            testBuilder ->
+                testBuilder
+                    .addDontWarnJetBrainsAnnotations()
+                    .addOptionsModification(disableAggressiveClassOptimizations))
         .inspect(
             inspector -> {
               ClassSubject outerClass =
@@ -659,7 +710,10 @@
     runTest(
             PACKAGE_NAME,
             mainClass,
-            testBuilder -> testBuilder.addOptionsModification(disableAggressiveClassOptimizations))
+            testBuilder ->
+                testBuilder
+                    .addDontWarnJetBrainsNotNullAnnotation()
+                    .addOptionsModification(disableAggressiveClassOptimizations))
         .inspect(
             inspector -> {
               ClassSubject objectClass = checkClassIsKept(inspector, testedClass.getClassName());
@@ -697,7 +751,10 @@
     runTest(
             PACKAGE_NAME,
             mainClass,
-            testBuilder -> testBuilder.addOptionsModification(disableAggressiveClassOptimizations))
+            testBuilder ->
+                testBuilder
+                    .addDontWarnJetBrainsNotNullAnnotation()
+                    .addOptionsModification(disableAggressiveClassOptimizations))
         .inspect(
             inspector -> {
               ClassSubject objectClass = checkClassIsKept(inspector, testedClass.getClassName());
@@ -729,7 +786,10 @@
     runTest(
             PACKAGE_NAME,
             mainClass,
-            testBuilder -> testBuilder.addOptionsModification(disableAggressiveClassOptimizations))
+            testBuilder ->
+                testBuilder
+                    .addDontWarnJetBrainsNotNullAnnotation()
+                    .addOptionsModification(disableAggressiveClassOptimizations))
         .inspect(
             inspector -> {
               ClassSubject objectClass = checkClassIsKept(inspector, testedClass.getClassName());
@@ -768,7 +828,10 @@
     runTest(
             PACKAGE_NAME,
             mainClass,
-            testBuilder -> testBuilder.addOptionsModification(disableAggressiveClassOptimizations))
+            testBuilder ->
+                testBuilder
+                    .addDontWarnJetBrainsNotNullAnnotation()
+                    .addOptionsModification(disableAggressiveClassOptimizations))
         .inspect(
             inspector -> {
               ClassSubject objectClass = checkClassIsKept(inspector, testedClass.getClassName());
@@ -807,7 +870,10 @@
     runTest(
             PACKAGE_NAME,
             mainClass,
-            testBuilder -> testBuilder.addOptionsModification(disableAggressiveClassOptimizations))
+            testBuilder ->
+                testBuilder
+                    .addDontWarnJetBrainsAnnotations()
+                    .addOptionsModification(disableAggressiveClassOptimizations))
         .inspect(
             inspector -> {
               ClassSubject objectClass = checkClassIsKept(inspector, testedClass.getClassName());
@@ -839,7 +905,10 @@
     runTest(
             PACKAGE_NAME,
             mainClass,
-            testBuilder -> testBuilder.addOptionsModification(disableAggressiveClassOptimizations))
+            testBuilder ->
+                testBuilder
+                    .addDontWarnJetBrainsAnnotations()
+                    .addOptionsModification(disableAggressiveClassOptimizations))
         .inspect(
             inspector -> {
               ClassSubject objectClass = checkClassIsKept(inspector, testedClass.getClassName());
@@ -865,7 +934,10 @@
     runTest(
             PACKAGE_NAME,
             mainClass,
-            testBuilder -> testBuilder.addOptionsModification(disableAggressiveClassOptimizations))
+            testBuilder ->
+                testBuilder
+                    .addDontWarnJetBrainsAnnotations()
+                    .addOptionsModification(disableAggressiveClassOptimizations))
         .inspect(
             inspector -> {
               ClassSubject objectClass = checkClassIsKept(inspector, testedClass.getClassName());
@@ -891,7 +963,10 @@
     runTest(
             PACKAGE_NAME,
             mainClass,
-            testBuilder -> testBuilder.addOptionsModification(disableAggressiveClassOptimizations))
+            testBuilder ->
+                testBuilder
+                    .addDontWarnJetBrainsNotNullAnnotation()
+                    .addOptionsModification(disableAggressiveClassOptimizations))
         .inspect(
             inspector -> {
               ClassSubject objectClass = checkClassIsKept(inspector, testedClass.getClassName());
@@ -922,7 +997,10 @@
     runTest(
             PACKAGE_NAME,
             mainClass,
-            testBuilder -> testBuilder.addOptionsModification(disableAggressiveClassOptimizations))
+            testBuilder ->
+                testBuilder
+                    .addDontWarnJetBrainsNotNullAnnotation()
+                    .addOptionsModification(disableAggressiveClassOptimizations))
         .inspect(
             inspector -> {
               ClassSubject objectClass = checkClassIsKept(inspector, testedClass.getClassName());
@@ -953,7 +1031,10 @@
     runTest(
             PACKAGE_NAME,
             mainClass,
-            testBuilder -> testBuilder.addOptionsModification(disableAggressiveClassOptimizations))
+            testBuilder ->
+                testBuilder
+                    .addDontWarnJetBrainsNotNullAnnotation()
+                    .addOptionsModification(disableAggressiveClassOptimizations))
         .inspect(
             inspector -> {
               ClassSubject objectClass = checkClassIsKept(inspector, testedClass.getClassName());
@@ -986,7 +1067,10 @@
     runTest(
             PACKAGE_NAME,
             mainClass,
-            testBuilder -> testBuilder.addOptionsModification(disableAggressiveClassOptimizations))
+            testBuilder ->
+                testBuilder
+                    .addDontWarnJetBrainsNotNullAnnotation()
+                    .addOptionsModification(disableAggressiveClassOptimizations))
         .inspect(
             inspector -> {
               ClassSubject objectClass = checkClassIsKept(inspector, testedClass.getClassName());
@@ -1018,7 +1102,10 @@
     runTest(
             PACKAGE_NAME,
             mainClass,
-            testBuilder -> testBuilder.addOptionsModification(disableAggressiveClassOptimizations))
+            testBuilder ->
+                testBuilder
+                    .addDontWarnJetBrainsAnnotations()
+                    .addOptionsModification(disableAggressiveClassOptimizations))
         .inspect(
             inspector -> {
               ClassSubject fileClass = checkClassIsKept(inspector, testedClass.getClassName());
@@ -1049,7 +1136,10 @@
     runTest(
             PACKAGE_NAME,
             mainClass,
-            testBuilder -> testBuilder.addOptionsModification(disableAggressiveClassOptimizations))
+            testBuilder ->
+                testBuilder
+                    .addDontWarnJetBrainsAnnotations()
+                    .addOptionsModification(disableAggressiveClassOptimizations))
         .inspect(
             inspector -> {
               ClassSubject objectClass = checkClassIsKept(inspector, testedClass.getClassName());
@@ -1077,7 +1167,10 @@
     runTest(
             PACKAGE_NAME,
             mainClass,
-            testBuilder -> testBuilder.addOptionsModification(disableAggressiveClassOptimizations))
+            testBuilder ->
+                testBuilder
+                    .addDontWarnJetBrainsAnnotations()
+                    .addOptionsModification(disableAggressiveClassOptimizations))
         .inspect(
             inspector -> {
               ClassSubject objectClass = checkClassIsKept(inspector, testedClass.getClassName());
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 70fd115..64e3a80 100644
--- a/src/test/java/com/android/tools/r8/kotlin/SimplifyIfNotNullKotlinTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/SimplifyIfNotNullKotlinTest.java
@@ -43,7 +43,10 @@
 
     final String mainClassName = ex1.getClassName();
     final String extraRules = neverInlineMethod(mainClassName, testMethodSignature);
-    runTest(FOLDER, mainClassName, testBuilder -> testBuilder.addKeepRules(extraRules))
+    runTest(
+            FOLDER,
+            mainClassName,
+            testBuilder -> testBuilder.addKeepRules(extraRules).addDontWarnJetBrainsAnnotations())
         .inspect(
             inspector -> {
               ClassSubject clazz = checkClassIsKept(inspector, ex1.getClassName());
@@ -67,7 +70,10 @@
 
     final String mainClassName = ex2.getClassName();
     final String extraRules = neverInlineMethod(mainClassName, testMethodSignature);
-    runTest(FOLDER, mainClassName, testBuilder -> testBuilder.addKeepRules(extraRules))
+    runTest(
+            FOLDER,
+            mainClassName,
+            testBuilder -> testBuilder.addKeepRules(extraRules).addDontWarnJetBrainsAnnotations())
         .inspect(
             inspector -> {
               ClassSubject clazz = checkClassIsKept(inspector, ex2.getClassName());
@@ -91,7 +97,10 @@
 
     final String mainClassName = ex3.getClassName();
     final String extraRules = neverInlineMethod(mainClassName, testMethodSignature);
-    runTest(FOLDER, mainClassName, testBuilder -> testBuilder.addKeepRules(extraRules))
+    runTest(
+            FOLDER,
+            mainClassName,
+            testBuilder -> testBuilder.addKeepRules(extraRules).addDontWarnJetBrainsAnnotations())
         .inspect(
             inspector -> {
               ClassSubject clazz = checkClassIsKept(inspector, ex3.getClassName());
diff --git a/src/test/java/com/android/tools/r8/kotlin/TestKotlinDataClass.java b/src/test/java/com/android/tools/r8/kotlin/TestKotlinDataClass.java
index 62cda72..0426d41 100644
--- a/src/test/java/com/android/tools/r8/kotlin/TestKotlinDataClass.java
+++ b/src/test/java/com/android/tools/r8/kotlin/TestKotlinDataClass.java
@@ -36,18 +36,20 @@
   }
 
   public MemberNaming.MethodSignature getCopySignature() {
-    List<String> propertiesTypes = properties.values().stream()
-        .sorted(Comparator.comparingInt(p -> p.getIndex()))
-        .map(p -> p.getType())
-        .collect(Collectors.toList());
+    List<String> propertiesTypes =
+        properties.values().stream()
+            .sorted(Comparator.comparingInt(KotlinProperty::getIndex))
+            .map(KotlinProperty::getType)
+            .collect(Collectors.toList());
     return new MemberNaming.MethodSignature("copy", className, propertiesTypes);
   }
 
   public MemberNaming.MethodSignature getCopyDefaultSignature() {
-    List<String> propertiesTypes = properties.values().stream()
-        .sorted(Comparator.comparingInt(p -> p.getIndex()))
-        .map(p -> p.getType())
-        .collect(Collectors.toList());
+    List<String> propertiesTypes =
+        properties.values().stream()
+            .sorted(Comparator.comparingInt(KotlinProperty::getIndex))
+            .map(KotlinProperty::getType)
+            .collect(Collectors.toList());
 
     List<String> copyDefaultParameterTypes = new ArrayList<>(propertiesTypes.size() + 3);
     copyDefaultParameterTypes.add(className);
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 9ae5aed..4a05b76 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
@@ -80,10 +80,14 @@
             .addKeepAllAttributes()
             // The BASE_LIBRARY contains proguard rules that do not match.
             .allowUnusedProguardConfigurationRules()
-            .addKeepRules(
-                "-dontwarn reactor.blockhound.integration.BlockHoundIntegration",
-                "-dontwarn org.junit.runners.model.Statement",
-                "-dontwarn org.junit.rules.TestRule")
+            .addDontWarn(
+                "edu.umd.cs.findbugs.annotations.SuppressFBWarnings",
+                "reactor.blockhound.BlockHound$Builder",
+                "reactor.blockhound.integration.BlockHoundIntegration",
+                "org.junit.rules.TestRule",
+                "org.junit.runner.Description",
+                "org.junit.runners.model.Statement",
+                "org.junit.runners.model.TestTimedOutException")
             .compile()
             .inspect(inspector -> assertEqualMetadata(new CodeInspector(BASE_LIBRARY), inspector))
             .writeToZip();
diff --git a/src/test/java/com/android/tools/r8/kotlin/lambda/KStyleKotlinLambdaMergingWithEnumUnboxingTest.java b/src/test/java/com/android/tools/r8/kotlin/lambda/KStyleKotlinLambdaMergingWithEnumUnboxingTest.java
index b9baae8..aea188c 100644
--- a/src/test/java/com/android/tools/r8/kotlin/lambda/KStyleKotlinLambdaMergingWithEnumUnboxingTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/lambda/KStyleKotlinLambdaMergingWithEnumUnboxingTest.java
@@ -55,6 +55,7 @@
         .addHorizontallyMergedLambdaClassesInspector(
             inspector -> inspector.assertMerged(Lambda1.class, Lambda2.class))
         .addEnumUnboxingInspector(inspector -> inspector.assertUnboxed(EnumUnboxingCandidate.class))
+        .addDontWarnJetBrainsNotNullAnnotation()
         .enableInliningAnnotations()
         .enableNeverClassInliningAnnotations()
         .enableNoHorizontalClassMergingAnnotations()
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 1b6d4c0..4f78df2 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
@@ -57,6 +57,7 @@
         .addLibraryFiles(ToolHelper.getKotlinStdlibJar(kotlinc))
         .addProgramFiles(ktClasses)
         .addKeepMainRule("**.B143165163Kt")
+        .addDontWarnJetBrainsNotNullAnnotation()
         .setMinApi(parameters.getApiLevel())
         .compile()
         .addRunClasspathFiles(ToolHelper.getKotlinStdlibJar(kotlinc))
@@ -80,6 +81,7 @@
         .addProgramFiles(ToolHelper.getKotlinStdlibJar(kotlinc))
         .addProgramFiles(ktClasses)
         .addKeepMainRule("**.B143165163Kt")
+        .addDontWarnJetBrainsNotNullAnnotation()
         .allowDiagnosticWarningMessages()
         .setMinApi(parameters.getApiLevel())
         .compile()
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 e7c4762..9cee958 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
@@ -45,6 +45,7 @@
         .setMode(CompilationMode.DEBUG)
         .addProgramFiles(compiledJars.getForConfiguration(kotlinc, KotlinTargetVersion.JAVA_6))
         .addProgramFiles(getJavaJarFile(FOLDER))
+        .addDontWarnJetBrainsNotNullAnnotation()
         .setMinApi(parameters.getApiLevel())
         .addKeepMainRule(MAIN_CLASS)
         .allowDiagnosticWarningMessages()
diff --git a/src/test/java/com/android/tools/r8/kotlin/lambda/KotlinLambdaMergingTest.java b/src/test/java/com/android/tools/r8/kotlin/lambda/KotlinLambdaMergingTest.java
index b9e0546..5a23bd3 100644
--- a/src/test/java/com/android/tools/r8/kotlin/lambda/KotlinLambdaMergingTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/lambda/KotlinLambdaMergingTest.java
@@ -306,6 +306,7 @@
                     .addKeepRules(
                         "-keepunusedarguments class * extends kotlin.jvm.internal.Lambda {"
                             + " invoke(int, short); }")
+                    .addDontWarnJetBrainsNotNullAnnotation()
                     .addOptionsModification(this::configure))
         .inspect(
             inspector -> {
@@ -356,7 +357,10 @@
     runTest(
             "lambdas_kstyle_captures",
             mainClassName,
-            testBuilder -> testBuilder.addOptionsModification(this::configure))
+            testBuilder ->
+                testBuilder
+                    .addDontWarnJetBrainsAnnotations()
+                    .addOptionsModification(this::configure))
         .inspect(
             inspector -> {
               if (enableUnusedInterfaceRemoval) {
@@ -390,7 +394,10 @@
     runTest(
             "lambdas_kstyle_generics",
             mainClassName,
-            testBuilder -> testBuilder.addOptionsModification(this::configure))
+            testBuilder ->
+                testBuilder
+                    .addDontWarnJetBrainsAnnotations()
+                    .addOptionsModification(this::configure))
         .inspect(
             inspector -> {
               if (enableUnusedInterfaceRemoval) {
@@ -422,6 +429,7 @@
             mainClassName,
             testBuilder ->
                 testBuilder
+                    .addDontWarnJetBrainsAnnotations()
                     .addKeepAttributeInnerClassesAndEnclosingMethod()
                     .addOptionsModification(this::configure))
         .inspect(
@@ -458,6 +466,7 @@
             // KEEP_SIGNATURE_INNER_ENCLOSING,
             testBuilder ->
                 testBuilder
+                    .addDontWarnJetBrainsAnnotations()
                     .addKeepAttributeInnerClassesAndEnclosingMethod()
                     .addKeepAttributeSignature()
                     .addOptionsModification(this::configure))
@@ -494,7 +503,10 @@
     runTest(
             "lambdas_jstyle_trivial",
             mainClassName,
-            testBuilder -> testBuilder.addOptionsModification(this::configure))
+            testBuilder ->
+                testBuilder
+                    .addDontWarnJetBrainsAnnotations()
+                    .addOptionsModification(this::configure))
         .inspect(
             inspector -> {
               Verifier verifier = new Verifier(inspector);
@@ -545,7 +557,10 @@
             "lambdas_singleton",
             mainClassName,
             testBuilder ->
-                testBuilder.addOptionsModification(this::configure).noHorizontalClassMerging())
+                testBuilder
+                    .addDontWarnJetBrainsAnnotations()
+                    .addOptionsModification(this::configure)
+                    .noHorizontalClassMerging())
         .inspect(
             inspector -> {
               Verifier verifier = new Verifier(inspector);
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 b1b38b9..b8fb366 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
@@ -35,11 +35,13 @@
         "reprocess_merged_lambdas_kstyle",
         mainClassName,
         testBuilder ->
-            testBuilder.addOptionsModification(
-                options -> {
-                  options.enableInlining = true;
-                  options.enableClassInlining = true;
-                  options.enableLambdaMerging = true;
-                }));
+            testBuilder
+                .addDontWarnJetBrainsNotNullAnnotation()
+                .addOptionsModification(
+                    options -> {
+                      options.enableInlining = true;
+                      options.enableClassInlining = true;
+                      options.enableLambdaMerging = true;
+                    }));
   }
 }
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 1dcdcfb..5032ea2 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
@@ -35,7 +35,8 @@
         "lambdas_jstyle_runnable",
         mainClassName,
         testBuilder ->
-            testBuilder.addOptionsModification(
-                options -> options.inliningInstructionAllowance = 3));
+            testBuilder
+                .addDontWarnJetBrainsAnnotations()
+                .addOptionsModification(options -> options.inliningInstructionAllowance = 3));
   }
 }
diff --git a/src/test/java/com/android/tools/r8/kotlin/lambda/b148525512/B148525512.java b/src/test/java/com/android/tools/r8/kotlin/lambda/b148525512/B148525512.java
index 0dd8a62..e9f4e46 100644
--- a/src/test/java/com/android/tools/r8/kotlin/lambda/b148525512/B148525512.java
+++ b/src/test/java/com/android/tools/r8/kotlin/lambda/b148525512/B148525512.java
@@ -139,6 +139,7 @@
                         .setProgramConsumer(new ArchiveConsumer(featureCode, false))
                         .build())
             .allowDiagnosticWarningMessages()
+            .addDontWarnJetBrainsNotNullAnnotation()
             .compile()
             .assertAllWarningMessagesMatch(
                 equalTo("Resource 'META-INF/MANIFEST.MF' already exists."))
diff --git a/src/test/java/com/android/tools/r8/kotlin/lambda/b159688129/LambdaGroupGCLimitTest.java b/src/test/java/com/android/tools/r8/kotlin/lambda/b159688129/LambdaGroupGCLimitTest.java
index 57cdae7..c33d03b 100644
--- a/src/test/java/com/android/tools/r8/kotlin/lambda/b159688129/LambdaGroupGCLimitTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/lambda/b159688129/LambdaGroupGCLimitTest.java
@@ -94,6 +94,7 @@
                     inspector.assertNoClassesMerged();
                   }
                 })
+            .addDontWarnJetBrainsNotNullAnnotation()
             .compile();
     Path path = compileResult.writeToZip();
     compileResult
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 c261c25..663c081 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
@@ -71,6 +71,7 @@
             options -> options.horizontalClassMergerOptions().disableKotlinLambdaMerging())
         .setMinApi(parameters.getApiLevel())
         .addKeepMainRule(PKG_NAME + ".SimpleKt")
+        .addDontWarnJetBrainsNotNullAnnotation()
         .applyIf(
             splitGroup,
             b ->
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataPrimitiveTypeRewriteTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataPrimitiveTypeRewriteTest.java
index 31b489a..b4fd614 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataPrimitiveTypeRewriteTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataPrimitiveTypeRewriteTest.java
@@ -86,6 +86,7 @@
             .addKeepAllClassesRuleWithAllowObfuscation()
             .addKeepRules("-keep class " + PKG_LIB + ".LibKt { *; }")
             .addKeepRules("-keep class kotlin.Metadata { *; }")
+            .addDontWarnJetBrainsAnnotations()
             .applyIf(keepUnit, b -> b.addKeepRules("-keep class kotlin.Unit { *; }"))
             .addKeepAttributes(ProguardKeepAttributes.RUNTIME_VISIBLE_ANNOTATIONS)
             .allowDiagnosticWarningMessages()
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteAllowAccessModificationTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteAllowAccessModificationTest.java
index ea9ed33..b6d7701 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteAllowAccessModificationTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteAllowAccessModificationTest.java
@@ -104,7 +104,6 @@
             .addKeepRules("-keepclassmembers,allowaccessmodification class **.Lib$Comp { *; }")
             .addKeepRules("-keep,allowaccessmodification,allowobfuscation class **.Lib$Comp { *; }")
             .addKeepRules("-keep,allowaccessmodification,allowobfuscation class **.LibKt { *; }")
-            .addKeepRules("-allowaccessmodification")
             .addApplyMapping(
                 StringUtils.lines(
                     PKG_LIB + ".Lib -> " + PKG_LIB + ".LibReference:",
@@ -115,6 +114,9 @@
                     "  void staticPrivate() -> staticPrivateReference",
                     "  void staticInternal() -> staticInternalReference"))
             .addKeepRuntimeVisibleAnnotations()
+            .addDontWarnJetBrainsNotNullAnnotation()
+            .addDontWarnKotlin()
+            .allowAccessModification()
             .compile()
             .inspect(this::inspect)
             .writeToZip();
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteAnnotationTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteAnnotationTest.java
index 428103f..a285149 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteAnnotationTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteAnnotationTest.java
@@ -121,6 +121,8 @@
                 ProguardKeepAttributes.RUNTIME_VISIBLE_ANNOTATIONS,
                 ProguardKeepAttributes.RUNTIME_VISIBLE_PARAMETER_ANNOTATIONS,
                 ProguardKeepAttributes.RUNTIME_VISIBLE_TYPE_ANNOTATIONS)
+            .addDontWarnJetBrainsNotNullAnnotation()
+            .addDontWarnKotlin()
             .compile()
             .inspect(this::inspect)
             .writeToZip();
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteAnonymousTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteAnonymousTest.java
index 0ff40ff..8db1944 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteAnonymousTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteAnonymousTest.java
@@ -76,6 +76,7 @@
                 ProguardKeepAttributes.SIGNATURE,
                 ProguardKeepAttributes.INNER_CLASSES,
                 ProguardKeepAttributes.ENCLOSING_METHOD)
+            .addDontWarnJetBrainsNotNullAnnotation()
             .compile()
             .inspect(this::inspect)
             .writeToZip();
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteBoxedTypesTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteBoxedTypesTest.java
index 2c2ae83..4e7d224 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteBoxedTypesTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteBoxedTypesTest.java
@@ -101,6 +101,7 @@
                 ProguardKeepAttributes.SIGNATURE,
                 ProguardKeepAttributes.INNER_CLASSES,
                 ProguardKeepAttributes.ENCLOSING_METHOD)
+            .addDontWarnJetBrainsAnnotations()
             .compile()
             .inspect(this::inspect)
             .writeToZip();
@@ -157,6 +158,7 @@
                 ProguardKeepAttributes.SIGNATURE,
                 ProguardKeepAttributes.INNER_CLASSES,
                 ProguardKeepAttributes.ENCLOSING_METHOD)
+            .addDontWarnJetBrainsAnnotations()
             .compile()
             .writeToZip();
     Path main =
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteCrossinlineAnonFunctionTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteCrossinlineAnonFunctionTest.java
index 56fd483..065d63f 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteCrossinlineAnonFunctionTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteCrossinlineAnonFunctionTest.java
@@ -68,6 +68,8 @@
             .addProgramFiles(libJars.getForConfiguration(kotlinc, targetVersion))
             .addKeepAllClassesRule()
             .addKeepAllAttributes()
+            .addDontWarnJetBrainsNotNullAnnotation()
+            .addDontWarnKotlin()
             .compile()
             .writeToZip();
     Path output =
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteCrossinlineConcreteFunctionTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteCrossinlineConcreteFunctionTest.java
index b8c5ac2..15f68ea 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteCrossinlineConcreteFunctionTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteCrossinlineConcreteFunctionTest.java
@@ -68,6 +68,8 @@
             .addProgramFiles(libJars.getForConfiguration(kotlinc, targetVersion))
             .addKeepAllClassesRule()
             .addKeepAllAttributes()
+            .addDontWarnJetBrainsNotNullAnnotation()
+            .addDontWarnKotlin()
             .compile()
             .writeToZip();
     Path output =
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteDelegatedPropertyTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteDelegatedPropertyTest.java
index d71248e..b43f846 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteDelegatedPropertyTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteDelegatedPropertyTest.java
@@ -71,6 +71,8 @@
             .addProgramFiles(jars.getForConfiguration(kotlinc, targetVersion))
             .addKeepAllClassesRule()
             .addKeepAttributes(ProguardKeepAttributes.RUNTIME_VISIBLE_ANNOTATIONS)
+            .addDontWarnJetBrainsAnnotations()
+            .addDontWarnKotlin()
             .compile()
             .inspect(
                 inspector ->
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteDependentKeep.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteDependentKeep.java
index 15112f6..2ccbb3a 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteDependentKeep.java
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteDependentKeep.java
@@ -51,6 +51,7 @@
         .addKeepKotlinMetadata()
         .addKeepRules(StringUtils.joinLines("-if class *.Metadata", "-keep class <1>.io.** { *; }"))
         .addKeepAttributes(ProguardKeepAttributes.RUNTIME_VISIBLE_ANNOTATIONS)
+        .addDontWarnJetBrainsAnnotations()
         .compile()
         .inspect(this::inspect);
   }
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteDoNotEmitValuesIfEmpty.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteDoNotEmitValuesIfEmpty.java
index fb4dab9..feca0f8 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteDoNotEmitValuesIfEmpty.java
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteDoNotEmitValuesIfEmpty.java
@@ -53,6 +53,7 @@
         .addKeepAllClassesRule()
         .addKeepKotlinMetadata()
         .addKeepAttributes(ProguardKeepAttributes.RUNTIME_VISIBLE_ANNOTATIONS)
+        .addDontWarnJetBrainsAnnotations()
         .compile()
         .inspect(this::inspectEmptyValuesAreNotPresent);
   }
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteFlexibleUpperBoundTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteFlexibleUpperBoundTest.java
index ecaea79..fbfc10f 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteFlexibleUpperBoundTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteFlexibleUpperBoundTest.java
@@ -88,6 +88,7 @@
                 ProguardKeepAttributes.SIGNATURE,
                 ProguardKeepAttributes.INNER_CLASSES,
                 ProguardKeepAttributes.ENCLOSING_METHOD)
+            .addDontWarnJetBrainsNotNullAnnotation()
             .compile()
             .inspect(this::inspect)
             .writeToZip();
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInClasspathTypeTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInClasspathTypeTest.java
index 09d3af7..c7b9ea6 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInClasspathTypeTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInClasspathTypeTest.java
@@ -94,6 +94,7 @@
             // to be called with Kotlin syntax from other kotlin code.
             .addKeepRules("-keep class **.ImplKt { <methods>; }")
             .addKeepAttributes(ProguardKeepAttributes.RUNTIME_VISIBLE_ANNOTATIONS)
+            .addDontWarnJetBrainsNotNullAnnotation()
             .compile()
             .inspect(this::inspectRenamed)
             .writeToZip();
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInCompanionTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInCompanionTest.java
index d025713..bdcb905 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInCompanionTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInCompanionTest.java
@@ -93,6 +93,7 @@
             // To keep ...$Companion structure
             .addKeepAttributes(ProguardKeepAttributes.INNER_CLASSES)
             .addKeepAttributes(ProguardKeepAttributes.ENCLOSING_METHOD)
+            .addDontWarnJetBrainsNotNullAnnotation()
             .compile()
             .inspect(codeInspector -> inspect(codeInspector, true))
             .writeToZip();
@@ -135,6 +136,7 @@
             // To keep ...$Companion structure
             .addKeepAttributes(ProguardKeepAttributes.INNER_CLASSES)
             .addKeepAttributes(ProguardKeepAttributes.ENCLOSING_METHOD)
+            .addDontWarnJetBrainsNotNullAnnotation()
             .compile()
             .inspect(codeInspector -> inspect(codeInspector, false))
             .writeToZip();
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInExtensionFunctionTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInExtensionFunctionTest.java
index ac363d4..f9124bd 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInExtensionFunctionTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInExtensionFunctionTest.java
@@ -92,6 +92,7 @@
             .addKeepAttributes(ProguardKeepAttributes.SIGNATURE)
             .addKeepAttributes(ProguardKeepAttributes.INNER_CLASSES)
             .addKeepAttributes(ProguardKeepAttributes.ENCLOSING_METHOD)
+            .addDontWarnJetBrainsNotNullAnnotation()
             .compile()
             .inspect(this::inspectMerged)
             .writeToZip();
@@ -146,6 +147,7 @@
             .addKeepAttributes(ProguardKeepAttributes.SIGNATURE)
             .addKeepAttributes(ProguardKeepAttributes.INNER_CLASSES)
             .addKeepAttributes(ProguardKeepAttributes.ENCLOSING_METHOD)
+            .addDontWarnJetBrainsNotNullAnnotation()
             .compile()
             .inspect(this::inspectRenamed)
             .writeToZip();
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInExtensionPropertyTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInExtensionPropertyTest.java
index 327ec34..c78cea1 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInExtensionPropertyTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInExtensionPropertyTest.java
@@ -88,6 +88,7 @@
             // to be called with Kotlin syntax from other kotlin code.
             .addKeepRules("-keep class **.BKt { <methods>; }")
             .addKeepAttributes(ProguardKeepAttributes.RUNTIME_VISIBLE_ANNOTATIONS)
+            .addDontWarnJetBrainsNotNullAnnotation()
             .compile()
             .inspect(this::inspectMerged)
             .writeToZip();
@@ -151,6 +152,7 @@
             // to be called with Kotlin syntax from other kotlin code.
             .addKeepRules("-keep class **.BKt { <methods>; }")
             .addKeepAttributes(ProguardKeepAttributes.RUNTIME_VISIBLE_ANNOTATIONS)
+            .addDontWarnJetBrainsNotNullAnnotation()
             .compile()
             .inspect(this::inspectRenamed)
             .writeToZip();
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInFunctionTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInFunctionTest.java
index 91ed797..5c8468d 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInFunctionTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInFunctionTest.java
@@ -86,6 +86,7 @@
             // Keep the BKt method, which will be called from other kotlin code.
             .addKeepRules("-keep class **.BKt { <methods>; }")
             .addKeepAttributes(ProguardKeepAttributes.RUNTIME_VISIBLE_ANNOTATIONS)
+            .addDontWarnJetBrainsNotNullAnnotation()
             .compile()
             .inspect(this::inspectMerged)
             .writeToZip();
@@ -147,6 +148,7 @@
             // Keep the BKt method, which will be called from other kotlin code.
             .addKeepRules("-keep class **.BKt { <methods>; }")
             .addKeepAttributes(ProguardKeepAttributes.RUNTIME_VISIBLE_ANNOTATIONS)
+            .addDontWarnJetBrainsNotNullAnnotation()
             .compile()
             .inspect(this::inspectRenamed)
             .writeToZip();
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInFunctionWithDefaultValueTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInFunctionWithDefaultValueTest.java
index c9210a1..db348d0 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInFunctionWithDefaultValueTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInFunctionWithDefaultValueTest.java
@@ -87,6 +87,7 @@
             .addKeepAttributes(ProguardKeepAttributes.SIGNATURE)
             .addKeepAttributes(ProguardKeepAttributes.INNER_CLASSES)
             .addKeepAttributes(ProguardKeepAttributes.ENCLOSING_METHOD)
+            .addDontWarnJetBrainsNotNullAnnotation()
             .compile()
             .inspect(this::inspect)
             .writeToZip();
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInFunctionWithVarargTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInFunctionWithVarargTest.java
index a65c4b9..65207dd 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInFunctionWithVarargTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInFunctionWithVarargTest.java
@@ -89,6 +89,7 @@
             .addKeepAttributes(ProguardKeepAttributes.SIGNATURE)
             .addKeepAttributes(ProguardKeepAttributes.INNER_CLASSES)
             .addKeepAttributes(ProguardKeepAttributes.ENCLOSING_METHOD)
+            .addDontWarnJetBrainsNotNullAnnotation()
             .compile()
             .inspect(this::inspect)
             .writeToZip();
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInLibraryTypeTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInLibraryTypeTest.java
index e635091..f3c59d9 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInLibraryTypeTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInLibraryTypeTest.java
@@ -87,7 +87,6 @@
     Path out =
         testForR8(parameters.getBackend())
             // Intentionally not providing baseLibJar as lib file nor classpath file.
-            .addClasspathFiles()
             .addProgramFiles(
                 extLibJarMap.getForConfiguration(kotlinc, targetVersion),
                 appJarMap.getForConfiguration(kotlinc, targetVersion))
@@ -97,6 +96,9 @@
             // Keep the main entry.
             .addKeepMainRule(main)
             .addKeepAttributes(ProguardKeepAttributes.RUNTIME_VISIBLE_ANNOTATIONS)
+            .addDontWarn(PKG + ".**")
+            .addDontWarnJetBrainsNotNullAnnotation()
+            .addDontWarnKotlin()
             .allowDiagnosticWarningMessages()
             // -dontoptimize so that basic code structure is kept.
             .noOptimization()
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInMultifileClassTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInMultifileClassTest.java
index 8adeb48..3949d6e 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInMultifileClassTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInMultifileClassTest.java
@@ -91,6 +91,7 @@
             .addKeepRules("-keep class **.UtilKt")
             .addKeepRules("-keepclassmembers class * { ** comma*Join*(...); }")
             .addKeepAttributes(ProguardKeepAttributes.RUNTIME_VISIBLE_ANNOTATIONS)
+            .addDontWarnJetBrainsNotNullAnnotation()
             .compile()
             .inspect(this::inspectMerged)
             .writeToZip();
@@ -135,6 +136,7 @@
             // Keep yet rename joinOf*(String).
             .addKeepRules("-keepclassmembers,allowobfuscation class * { ** joinOf*(...); }")
             .addKeepAttributes(ProguardKeepAttributes.RUNTIME_VISIBLE_ANNOTATIONS)
+            .addDontWarnJetBrainsNotNullAnnotation()
             .compile()
             .inspect(this::inspectRenamed)
             .writeToZip();
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInParameterTypeTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInParameterTypeTest.java
index a2a85c3..f84fa48 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInParameterTypeTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInParameterTypeTest.java
@@ -78,6 +78,7 @@
             // Keep Itf, but allow minification.
             .addKeepRules("-keep,allowobfuscation class **.Itf")
             .addKeepAttributes(ProguardKeepAttributes.RUNTIME_VISIBLE_ANNOTATIONS)
+            .addDontWarnJetBrainsNotNullAnnotation()
             .compile()
             .inspect(this::inspect)
             .writeToZip();
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInPropertyTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInPropertyTest.java
index d012f10..af4c9b6 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInPropertyTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInPropertyTest.java
@@ -84,6 +84,7 @@
             .addKeepRules("-keep class **.Person { <init>(...); }")
             .addKeepRules("-keepclassmembers class **.Person { *** get*(); }")
             .addKeepAttributes(ProguardKeepAttributes.RUNTIME_VISIBLE_ANNOTATIONS)
+            .addDontWarnJetBrainsNotNullAnnotation()
             .compile()
             .inspect(this::inspectGetterOnly)
             .writeToZip();
@@ -179,6 +180,7 @@
             // Keep LibKt extension methods
             .addKeepRules("-keep class **.LibKt { <methods>; }")
             .addKeepAttributes(ProguardKeepAttributes.RUNTIME_VISIBLE_ANNOTATIONS)
+            .addDontWarnJetBrainsNotNullAnnotation()
             .compile()
             .inspect(this::inspectSetterOnly)
             .writeToZip();
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInPropertyTypeTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInPropertyTypeTest.java
index 9641f64..0113f19 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInPropertyTypeTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInPropertyTypeTest.java
@@ -76,6 +76,7 @@
             // Keep non-private members of Impl
             .addKeepRules("-keep public class **.Impl { !private *; }")
             .addKeepAttributes(ProguardKeepAttributes.RUNTIME_VISIBLE_ANNOTATIONS)
+            .addDontWarnJetBrainsNotNullAnnotation()
             .compile()
             .inspect(this::inspect)
             .writeToZip();
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInReturnTypeTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInReturnTypeTest.java
index 9783f3b..c8144f4 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInReturnTypeTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInReturnTypeTest.java
@@ -78,6 +78,7 @@
             // Keep Itf, but allow minification.
             .addKeepRules("-keep,allowobfuscation class **.Itf")
             .addKeepAttributes(ProguardKeepAttributes.RUNTIME_VISIBLE_ANNOTATIONS)
+            .addDontWarnJetBrainsNotNullAnnotation()
             .compile()
             .inspect(this::inspect)
             .writeToZip();
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInSealedClassNestedTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInSealedClassNestedTest.java
index f33fc30..db927a8 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInSealedClassNestedTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInSealedClassNestedTest.java
@@ -77,6 +77,7 @@
             .addKeepAttributes(ProguardKeepAttributes.RUNTIME_VISIBLE_ANNOTATIONS)
             .addKeepAttributes(
                 ProguardKeepAttributes.INNER_CLASSES, ProguardKeepAttributes.ENCLOSING_METHOD)
+            .addDontWarnJetBrainsAnnotations()
             .compile()
             .writeToZip();
     Path output =
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInSealedClassTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInSealedClassTest.java
index 2dbbc48..ef43f6e 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInSealedClassTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInSealedClassTest.java
@@ -87,6 +87,7 @@
             // Keep the factory object and utils
             .addKeepRules("-keep class **.ExprFactory { *; }")
             .addKeepAttributes(ProguardKeepAttributes.RUNTIME_VISIBLE_ANNOTATIONS)
+            .addDontWarnJetBrainsAnnotations()
             .compile()
             .inspect(this::inspectValid)
             .writeToZip();
@@ -152,6 +153,7 @@
             // Keep the extension function
             .addKeepRules("-keep class **.LibKt { <methods>; }")
             .addKeepAttributes(ProguardKeepAttributes.RUNTIME_VISIBLE_ANNOTATIONS)
+            .addDontWarnJetBrainsNotNullAnnotation()
             .compile()
             .inspect(this::inspectInvalid)
             .writeToZip();
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInTypeAliasTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInTypeAliasTest.java
index edade6e..e680d6e 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInTypeAliasTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInTypeAliasTest.java
@@ -129,6 +129,7 @@
                 ProguardKeepAttributes.SIGNATURE,
                 ProguardKeepAttributes.INNER_CLASSES,
                 ProguardKeepAttributes.ENCLOSING_METHOD)
+            .addDontWarnJetBrainsNotNullAnnotation()
             .compile()
             .inspect(this::inspect)
             .writeToZip();
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInTypeArgumentsTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInTypeArgumentsTest.java
index 788abf2..acdda5b 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInTypeArgumentsTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInTypeArgumentsTest.java
@@ -133,6 +133,7 @@
                 ProguardKeepAttributes.SIGNATURE,
                 ProguardKeepAttributes.INNER_CLASSES,
                 ProguardKeepAttributes.ENCLOSING_METHOD)
+            .addDontWarnJetBrainsNotNullAnnotation()
             .compile()
             .inspect(this::inspect)
             .writeToZip();
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInlinePropertyTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInlinePropertyTest.java
index 133dd37..ac232d7 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInlinePropertyTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInlinePropertyTest.java
@@ -81,6 +81,7 @@
                 ProguardKeepAttributes.SIGNATURE,
                 ProguardKeepAttributes.INNER_CLASSES,
                 ProguardKeepAttributes.ENCLOSING_METHOD)
+            .addDontWarnJetBrainsNotNullAnnotation()
             .compile()
             .inspect(this::inspect)
             .writeToZip();
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteJvmStaticTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteJvmStaticTest.java
index 83b8e62..0558ebc 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteJvmStaticTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteJvmStaticTest.java
@@ -89,6 +89,8 @@
             .addProgramFiles(kotlincLibJar.getForConfiguration(kotlinc, targetVersion))
             .addKeepAllClassesRule()
             .addKeepAttributes(ProguardKeepAttributes.RUNTIME_VISIBLE_ANNOTATIONS)
+            .addDontWarnJetBrainsNotNullAnnotation()
+            .addDontWarnKotlin()
             .compile()
             .inspect(this::inspect)
             .writeToZip();
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteKeepPathTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteKeepPathTest.java
index 40c7573..056b18b 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteKeepPathTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteKeepPathTest.java
@@ -60,6 +60,7 @@
         .applyIf(keepMetadata, TestShrinkerBuilder::addKeepKotlinMetadata)
         .addKeepRuntimeVisibleAnnotations()
         .allowDiagnosticWarningMessages()
+        .addDontWarnJetBrainsAnnotations()
         .compile()
         .assertAllWarningMessagesMatch(equalTo("Resource 'META-INF/MANIFEST.MF' already exists."))
         .inspect(inspector -> inspect(inspector, keepMetadata));
@@ -72,6 +73,7 @@
         .addClasspathFiles(ToolHelper.getKotlinStdlibJar(kotlinc))
         .addKeepRules("-keep class " + LIB_CLASS_NAME)
         .addKeepRuntimeVisibleAnnotations()
+        .addDontWarnJetBrainsNotNullAnnotation()
         .compile()
         .inspect(inspector -> inspect(inspector, true));
   }
@@ -84,6 +86,7 @@
         .addLibraryFiles(ToolHelper.getJava8RuntimeJar())
         .addKeepRules("-keep class " + LIB_CLASS_NAME)
         .addKeepRuntimeVisibleAnnotations()
+        .addDontWarnJetBrainsNotNullAnnotation()
         .compile()
         .inspect(inspector -> inspect(inspector, true));
   }
@@ -94,6 +97,8 @@
         .addProgramFiles(libJars.getForConfiguration(kotlinc, targetVersion))
         .addKeepRules("-keep class " + LIB_CLASS_NAME)
         .addKeepRuntimeVisibleAnnotations()
+        .addDontWarnJetBrainsNotNullAnnotation()
+        .addDontWarnKotlin()
         .compile()
         .inspect(inspector -> inspect(inspector, true));
   }
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteKeepTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteKeepTest.java
index 8c681bd..74159a6 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteKeepTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteKeepTest.java
@@ -47,6 +47,7 @@
         .addKeepKotlinMetadata()
         .addKeepRules("-keep class kotlin.io.** { *; }")
         .addKeepAttributes(ProguardKeepAttributes.RUNTIME_VISIBLE_ANNOTATIONS)
+        .addDontWarnJetBrainsAnnotations()
         .compile()
         .inspect(this::inspect);
   }
@@ -59,6 +60,7 @@
         .addKeepRules("-keep class kotlin.io.** { *; }")
         .addKeepRules("-if class *", "-keep class kotlin.Metadata { *; }")
         .addKeepAttributes(ProguardKeepAttributes.RUNTIME_VISIBLE_ANNOTATIONS)
+        .addDontWarnJetBrainsAnnotations()
         .compile()
         .inspect(this::inspect);
   }
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewritePassThroughTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewritePassThroughTest.java
index 3e62729..0b65ec3 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewritePassThroughTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewritePassThroughTest.java
@@ -44,6 +44,7 @@
         .addKeepAllClassesRule()
         .addKeepKotlinMetadata()
         .addKeepAttributes(ProguardKeepAttributes.RUNTIME_VISIBLE_ANNOTATIONS)
+        .addDontWarnJetBrainsAnnotations()
         .compile()
         .inspect(
             inspector ->
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewritePrunedObjectsTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewritePrunedObjectsTest.java
index 39793cf..3d3386c 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewritePrunedObjectsTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewritePrunedObjectsTest.java
@@ -75,6 +75,7 @@
             .addProgramFiles(libJars.getForConfiguration(kotlinc, targetVersion))
             .addKeepRules("-keep class " + PKG_LIB + ".Sub { <init>(); *** kept(); }")
             .addKeepRuntimeVisibleAnnotations()
+            .addDontWarnKotlinMetadata()
             .noMinification()
             .compile()
             .inspect(this::checkPruned)
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataStripTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataStripTest.java
index 168cbc5..1a2cd73 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
@@ -61,6 +61,7 @@
             .addKeepMainRule(mainClassName)
             .addKeepKotlinMetadata()
             .addKeepAttributes(ProguardKeepAttributes.RUNTIME_VISIBLE_ANNOTATIONS)
+            .addDontWarnJetBrainsAnnotations()
             .allowDiagnosticWarningMessages()
             .setMinApi(parameters.getApiLevel())
             .compile()
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataVersionNumberBumpTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataVersionNumberBumpTest.java
index 9cd2614..e23ac8f 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataVersionNumberBumpTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataVersionNumberBumpTest.java
@@ -56,6 +56,7 @@
         .setMinApi(parameters.getApiLevel())
         .addKeepAllClassesRule()
         .addKeepAttributes(ProguardKeepAttributes.RUNTIME_VISIBLE_ANNOTATIONS)
+        .addDontWarnJetBrainsAnnotations()
         .compile()
         .inspect(inspector -> inspectMetadataVersion(inspector, "1.4.0"));
   }
@@ -68,6 +69,7 @@
         .setMinApi(parameters.getApiLevel())
         .addKeepAllClassesRule()
         .addKeepAttributes(ProguardKeepAttributes.RUNTIME_VISIBLE_ANNOTATIONS)
+        .addDontWarnJetBrainsAnnotations()
         .compile()
         .inspect(inspector -> inspectMetadataVersion(inspector, "1.4.0"));
   }
@@ -80,6 +82,7 @@
         .setMinApi(parameters.getApiLevel())
         .addKeepAllClassesRule()
         .addKeepAttributes(ProguardKeepAttributes.RUNTIME_VISIBLE_ANNOTATIONS)
+        .addDontWarnJetBrainsAnnotations()
         .compile()
         .inspect(inspector -> inspectMetadataVersion(inspector, "1.4.2"));
   }
diff --git a/src/test/java/com/android/tools/r8/kotlin/optimize/switches/KotlinEnumSwitchTest.java b/src/test/java/com/android/tools/r8/kotlin/optimize/switches/KotlinEnumSwitchTest.java
index d7eeae4..fe05714 100644
--- a/src/test/java/com/android/tools/r8/kotlin/optimize/switches/KotlinEnumSwitchTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/optimize/switches/KotlinEnumSwitchTest.java
@@ -46,6 +46,7 @@
               options.enableEnumValueOptimization = enableSwitchMapRemoval;
               options.enableEnumSwitchMapRemoval = enableSwitchMapRemoval;
             })
+        .addDontWarnJetBrainsNotNullAnnotation()
         .setMinApi(parameters.getRuntime())
         .noMinification()
         .compile()
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 53b9d9b..ac7eb23 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
@@ -96,6 +96,7 @@
         .addKeepAllClassesRule()
         .addKeepAttributes(ProguardKeepAttributes.RUNTIME_VISIBLE_ANNOTATIONS)
         .allowDiagnosticWarningMessages()
+        .addDontWarnJetBrains()
         .compile()
         .writeToZip(foo.toPath())
         .assertAllWarningMessagesMatch(equalTo("Resource 'META-INF/MANIFEST.MF' already exists."))
diff --git a/src/test/java/com/android/tools/r8/kotlin/sealed/SealedClassTest.java b/src/test/java/com/android/tools/r8/kotlin/sealed/SealedClassTest.java
index 6b70ca2..92b6b97 100644
--- a/src/test/java/com/android/tools/r8/kotlin/sealed/SealedClassTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/sealed/SealedClassTest.java
@@ -76,6 +76,7 @@
         .allowAccessModification()
         .allowDiagnosticWarningMessages(parameters.isCfRuntime())
         .addKeepMainRule(MAIN)
+        .addDontWarnJetBrainsNotNullAnnotation()
         .compileWithExpectedDiagnostics(
             diagnostics ->
                 diagnostics.assertAllWarningsMatch(
diff --git a/src/test/java/com/android/tools/r8/maindexlist/MainDexDevirtualizerTest.java b/src/test/java/com/android/tools/r8/maindexlist/MainDexDevirtualizerTest.java
new file mode 100644
index 0000000..6cb7ab4
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/maindexlist/MainDexDevirtualizerTest.java
@@ -0,0 +1,178 @@
+// 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.maindexlist;
+
+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.hasItem;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.core.IsNot.not;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.NoVerticalClassMerging;
+import com.android.tools.r8.R8FullTestBuilder;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.ThrowableConsumer;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.utils.Box;
+import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.codeinspector.CheckCastInstructionSubject;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.InstructionSubject;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.function.BiConsumer;
+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 MainDexDevirtualizerTest extends TestBase {
+
+  private final TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  public MainDexDevirtualizerTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    assumeTrue(parameters.isCfRuntime() || !parameters.getDexRuntimeVersion().isDalvik());
+    runTest(
+        testBuilder -> {},
+        (inspector, mainDexClasses) -> {
+          assertTrue(mainDexClasses.isEmpty());
+          // Verify that the call to I.foo in Main.class has been changed to A.foo by checking for a
+          // cast.
+          ClassSubject clazz = inspector.clazz(Main.class);
+          assertThat(clazz, isPresentAndNotRenamed());
+          MethodSubject main = clazz.uniqueMethodWithName("main");
+          assertThat(main, isPresent());
+          List<CheckCastInstructionSubject> checkCasts =
+              main.streamInstructions()
+                  .filter(InstructionSubject::isCheckCast)
+                  .map(InstructionSubject::asCheckCast)
+                  .collect(Collectors.toList());
+          assertEquals(1, checkCasts.size());
+          ClassSubject a = inspector.clazz(A.class);
+          assertThat(a, isPresentAndRenamed());
+          assertEquals(
+              a.getFinalDescriptor(), checkCasts.get(0).getType().getDescriptor().toString());
+        });
+  }
+
+  @Test
+  public void testMainDexClasses() throws Exception {
+    assumeTrue(parameters.isDexRuntime());
+    assumeTrue(parameters.getDexRuntimeVersion().isDalvik());
+    runTest(
+        r8FullTestBuilder ->
+            r8FullTestBuilder.addMainDexListClasses(I.class, Provider.class, Main.class),
+        this::inspect);
+  }
+
+  @Test
+  public void testMainDexTracing() throws Exception {
+    assumeTrue(parameters.isDexRuntime());
+    assumeTrue(parameters.getDexRuntimeVersion().isDalvik());
+    runTest(
+        r8FullTestBuilder -> r8FullTestBuilder.addMainDexClassRules(Main.class, I.class),
+        this::inspect);
+  }
+
+  private void runTest(
+      ThrowableConsumer<R8FullTestBuilder> setMainDexConsumer,
+      BiConsumer<CodeInspector, List<String>> resultConsumer)
+      throws Exception {
+    Box<String> mainDexStringList = new Box<>("");
+    testForR8(parameters.getBackend())
+        .addProgramClasses(I.class, Provider.class, A.class, Main.class)
+        .enableNoVerticalClassMergingAnnotations()
+        .enableInliningAnnotations()
+        .enableNeverClassInliningAnnotations()
+        .addKeepMainRule(Main.class)
+        .addKeepClassRulesWithAllowObfuscation(I.class)
+        .setMinApi(parameters.getApiLevel())
+        .applyIf(
+            parameters.isDexRuntime() && parameters.getDexRuntimeVersion().isDalvik(),
+            builder ->
+                builder.setMainDexListConsumer(ToolHelper.consumeString(mainDexStringList::set)))
+        .apply(setMainDexConsumer)
+        .run(parameters.getRuntime(), Main.class)
+        .apply(
+            result ->
+                resultConsumer.accept(
+                    result.inspector(),
+                    mainDexStringList.get().equals("")
+                        ? new ArrayList<>()
+                        : StringUtils.splitLines(mainDexStringList.get())));
+  }
+
+  private void inspect(CodeInspector inspector, List<String> mainDexClasses) {
+    assertEquals(4, inspector.allClasses().size());
+    assertEquals(3, mainDexClasses.size());
+    inspectClassInMainDex(Main.class, inspector, mainDexClasses);
+    inspectClassInMainDex(I.class, inspector, mainDexClasses);
+    inspectClassInMainDex(Provider.class, inspector, mainDexClasses);
+    ClassSubject aClass = inspector.clazz(A.class);
+    assertThat(aClass, isPresentAndRenamed());
+    assertThat(mainDexClasses, not(hasItem(aClass.getFinalBinaryName() + ".class")));
+  }
+
+  private void inspectClassInMainDex(
+      Class<?> clazz, CodeInspector inspector, List<String> mainDexClasses) {
+    ClassSubject classSubject = inspector.clazz(clazz);
+    assertThat(classSubject, isPresent());
+    assertThat(mainDexClasses, hasItem(classSubject.getFinalBinaryName() + ".class"));
+  }
+
+  @NoVerticalClassMerging
+  public interface I {
+
+    @NeverInline
+    void foo();
+  }
+
+  public static class Provider {
+    @NeverInline
+    public static I getImpl() {
+      return new A(); // <-- We will call-site optimize getImpl() to always return A.
+    }
+  }
+
+  @NeverClassInline
+  public static class A implements I {
+
+    @Override
+    @NeverInline
+    public void foo() {
+      System.out.println("A::foo");
+    }
+  }
+
+  public static class Main {
+
+    public static void main(String[] args) {
+      // The de-virtualizer will try and rebind from I.foo to A.foo.
+      Provider.getImpl().foo();
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/maindexlist/MainDexListFromGenerateMainDexInliningTest.java b/src/test/java/com/android/tools/r8/maindexlist/MainDexListFromGenerateMainDexInliningTest.java
new file mode 100644
index 0000000..94392e6
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/maindexlist/MainDexListFromGenerateMainDexInliningTest.java
@@ -0,0 +1,151 @@
+// 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.maindexlist;
+
+import static com.android.tools.r8.utils.codeinspector.CodeMatchers.invokesMethod;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isAbsent;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.NoHorizontalClassMerging;
+import com.android.tools.r8.NoStaticClassMerging;
+import com.android.tools.r8.R8TestCompileResult;
+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.references.ClassReference;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import java.util.List;
+import org.junit.BeforeClass;
+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 MainDexListFromGenerateMainDexInliningTest extends TestBase {
+
+  private static List<ClassReference> mainDexList;
+
+  private final TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters()
+        .withDexRuntimes()
+        .withApiLevelsEndingAtExcluding(apiLevelWithNativeMultiDexSupport())
+        .build();
+  }
+
+  @BeforeClass
+  public static void setup() throws Exception {
+    mainDexList =
+        testForMainDexListGenerator(getStaticTemp())
+            .addInnerClasses(MainDexListFromGenerateMainDexInliningTest.class)
+            .addLibraryFiles(ToolHelper.getMostRecentAndroidJar())
+            .addMainDexRules(
+                "-keep class " + Main.class.getTypeName() + " {",
+                "  public static void main(java.lang.String[]);",
+                "}")
+            .run()
+            .getMainDexList();
+  }
+
+  public MainDexListFromGenerateMainDexInliningTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void test() throws Exception {
+    // The generated main dex list should contain Main (which is a root) and A (which is a direct
+    // dependency of Main).
+    assertEquals(2, mainDexList.size());
+    assertEquals(A.class.getTypeName(), mainDexList.get(0).getTypeName());
+    assertEquals(Main.class.getTypeName(), mainDexList.get(1).getTypeName());
+
+    R8TestCompileResult compileResult =
+        testForR8(parameters.getBackend())
+            .addInnerClasses(getClass())
+            .addInliningAnnotations()
+            .addKeepClassAndMembersRules(Main.class)
+            .addMainDexListClassReferences(mainDexList)
+            .collectMainDexClasses()
+            .enableInliningAnnotations()
+            .enableNoHorizontalClassMergingAnnotations()
+            .enableNoStaticClassMergingAnnotations()
+            .noMinification()
+            .setMinApi(parameters.getApiLevel())
+            .compile();
+
+    CodeInspector inspector = compileResult.inspector();
+    ClassSubject mainClassSubject = inspector.clazz(Main.class);
+    assertThat(mainClassSubject, isPresent());
+
+    MethodSubject fooMethodSubject = mainClassSubject.uniqueMethodWithName("foo");
+    assertThat(fooMethodSubject, isPresent());
+
+    ClassSubject aClassSubject = inspector.clazz(A.class);
+    // TODO(b/178353726): Should be present, but was inlined.
+    assertThat(aClassSubject, isAbsent());
+
+    MethodSubject barMethodSubject = aClassSubject.uniqueMethodWithName("bar");
+    // TODO(b/178353726): Should be present, but was inlined.
+    assertThat(barMethodSubject, isAbsent());
+
+    ClassSubject bClassSubject = inspector.clazz(B.class);
+    assertThat(bClassSubject, isPresent());
+
+    MethodSubject bazMethodSubject = bClassSubject.uniqueMethodWithName("baz");
+    assertThat(bazMethodSubject, isPresent());
+
+    // TODO(b/178353726): foo() should invoke bar() and bar() should invoke baz().
+    assertThat(fooMethodSubject, invokesMethod(bazMethodSubject));
+
+    // TODO(b/178353726): Main is the only class guaranteed to be in the main dex, but it has a
+    //  direct reference to B.
+    compileResult.inspectMainDexClasses(
+        mainDexClasses -> {
+          assertEquals(1, mainDexClasses.size());
+          assertEquals(mainClassSubject.getFinalName(), mainDexClasses.iterator().next());
+        });
+  }
+
+  static class Main {
+
+    public static void main(String[] args) {
+      System.out.println("Main.main()");
+    }
+
+    static void foo() {
+      // TODO(b/178353726): Should not allow inlining bar into foo(), since that adds B as a direct
+      //  dependence, and we don't include the direct dependencies of main dex list classes.
+      A.bar();
+    }
+  }
+
+  @NoHorizontalClassMerging
+  @NoStaticClassMerging
+  static class A {
+
+    static void bar() {
+      B.baz();
+    }
+  }
+
+  @NoHorizontalClassMerging
+  @NoStaticClassMerging
+  static class B {
+
+    @NeverInline
+    static void baz() {
+      System.out.println("B.baz");
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/maindexlist/MainDexListInliningTest.java b/src/test/java/com/android/tools/r8/maindexlist/MainDexListInliningTest.java
new file mode 100644
index 0000000..82f5e7d
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/maindexlist/MainDexListInliningTest.java
@@ -0,0 +1,92 @@
+// 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.maindexlist;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isAbsent;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.NoHorizontalClassMerging;
+import com.android.tools.r8.R8TestCompileResult;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class MainDexListInliningTest extends TestBase {
+
+  private final TestParameters parameters;
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters()
+        .withDexRuntimes()
+        .withApiLevelsEndingAtExcluding(apiLevelWithNativeMultiDexSupport())
+        .build();
+  }
+
+  public MainDexListInliningTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void test() throws Exception {
+    R8TestCompileResult compileResult =
+        testForR8(parameters.getBackend())
+            .addInnerClasses(getClass())
+            .addKeepMainRule(Main.class)
+            .addMainDexListClasses(Main.class)
+            .collectMainDexClasses()
+            .enableNoHorizontalClassMergingAnnotations()
+            .setMinApi(parameters.getApiLevel())
+            .compile();
+
+    CodeInspector inspector = compileResult.inspector();
+
+    ClassSubject mainClassSubject = inspector.clazz(Main.class);
+    assertThat(mainClassSubject, isPresent());
+
+    // A is absent due to inlining.
+    // TODO(b/178353726): Inlining should be prohibited.
+    ClassSubject aClassSubject = inspector.clazz(A.class);
+    assertThat(aClassSubject, isAbsent());
+
+    // B should be referenced from Main.main.
+    ClassSubject bClassSubject = inspector.clazz(B.class);
+    assertThat(bClassSubject, isPresent());
+
+    compileResult.inspectMainDexClasses(
+        mainDexClasses -> {
+          assertTrue(mainDexClasses.contains(mainClassSubject.getFinalName()));
+          assertFalse(mainDexClasses.contains(bClassSubject.getFinalName()));
+        });
+  }
+
+  static class Main {
+
+    public static void main(String[] args) {
+      // Should be inlined.
+      A.m();
+    }
+  }
+
+  @NoHorizontalClassMerging
+  static class A {
+
+    public static void m() {
+      System.out.println(B.class);
+    }
+  }
+
+  @NoHorizontalClassMerging
+  static class B {}
+}
diff --git a/src/test/java/com/android/tools/r8/maindexlist/MainDexListMergeInRootTest.java b/src/test/java/com/android/tools/r8/maindexlist/MainDexListMergeInRootTest.java
new file mode 100644
index 0000000..7041c75
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/maindexlist/MainDexListMergeInRootTest.java
@@ -0,0 +1,113 @@
+// 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.maindexlist;
+
+import static com.android.tools.r8.DiagnosticsMatcher.diagnosticMessage;
+import static com.android.tools.r8.utils.codeinspector.AssertUtils.assertFailsCompilation;
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.NoHorizontalClassMerging;
+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 MainDexListMergeInRootTest extends TestBase {
+
+  private final TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withDexRuntimes().withAllApiLevels().build();
+  }
+
+  public MainDexListMergeInRootTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testMainDexTracing() {
+    assumeTrue(parameters.getDexRuntimeVersion().isDalvik());
+    assertFailsCompilation(
+        () ->
+            testForR8(parameters.getBackend())
+                .addProgramClasses(OutsideMainDex.class, InsideA.class, InsideB.class, Main.class)
+                .addKeepClassAndMembersRules(Main.class)
+                .setMinApi(parameters.getApiLevel())
+                .enableNeverClassInliningAnnotations()
+                .enableNoHorizontalClassMergingAnnotations()
+                .enableInliningAnnotations()
+                .noMinification()
+                .addMainDexRules(
+                    "-keep class "
+                        + Main.class.getTypeName()
+                        + " { public static void main(***); }")
+                .addOptionsModification(
+                    options -> {
+                      options.testing.checkForNotExpandingMainDexTracingResult = true;
+                    })
+                .compileWithExpectedDiagnostics(
+                    diagnostics -> {
+                      diagnostics.assertErrorsMatch(
+                          diagnosticMessage(
+                              containsString(
+                                  "Class com.android.tools.r8.maindexlist"
+                                      + ".MainDexListMergeInRootTest$OutsideMainDex"
+                                      + " was not a main dex root in the first round")));
+                    }));
+  }
+
+  @NoHorizontalClassMerging
+  @NeverClassInline
+  public static class OutsideMainDex {
+
+    @NeverInline
+    public void print(int i) {
+      System.out.println("OutsideMainDex::print" + i);
+    }
+  }
+
+  @NeverClassInline
+  public static class InsideA {
+
+    public void bar() {
+      System.out.println("A::live");
+    }
+
+    /* Not a traced root */
+    @NeverInline
+    public void foo(int i) {
+      new OutsideMainDex().print(i);
+    }
+  }
+
+  @NeverClassInline
+  public static class InsideB {
+
+    @NeverInline
+    public void foo(int i) {
+      System.out.println("InsideB::live" + i);
+    }
+  }
+
+  public static class Main {
+
+    public static void main(String[] args) {
+      new InsideB().foo(args.length);
+      new InsideA().bar();
+    }
+
+    public void keptToKeepInsideANotLive() {
+      new InsideA().foo(System.currentTimeMillis() > 0 ? 0 : 1);
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/maindexlist/MainDexListNoDirectDependenciesTest.java b/src/test/java/com/android/tools/r8/maindexlist/MainDexListNoDirectDependenciesTest.java
new file mode 100644
index 0000000..8f5d997
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/maindexlist/MainDexListNoDirectDependenciesTest.java
@@ -0,0 +1,86 @@
+// 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.maindexlist;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.R8TestCompileResult;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class MainDexListNoDirectDependenciesTest extends TestBase {
+
+  private final TestParameters parameters;
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters()
+        .withDexRuntimes()
+        .withApiLevelsEndingAtExcluding(apiLevelWithNativeMultiDexSupport())
+        .build();
+  }
+
+  public MainDexListNoDirectDependenciesTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void test() throws Exception {
+    R8TestCompileResult compileResult =
+        testForR8(parameters.getBackend())
+            .addInnerClasses(getClass())
+            .addMainDexListClasses(A.class)
+            .addMainDexClassRules(B.class)
+            .collectMainDexClasses()
+            .noTreeShaking()
+            .setMinApi(parameters.getApiLevel())
+            .compile();
+
+    CodeInspector inspector = compileResult.inspector();
+
+    ClassSubject aClassSubject = inspector.clazz(A.class);
+    ClassSubject referencedFromAClassSubject = inspector.clazz(ReferencedFromA.class);
+    ClassSubject bClassSubject = inspector.clazz(B.class);
+    ClassSubject referencedFromBClassSubject = inspector.clazz(ReferencedFromB.class);
+
+    compileResult.inspectMainDexClasses(
+        mainDexClasses -> {
+          assertTrue(mainDexClasses.contains(aClassSubject.getFinalName()));
+          // It is assumed that the provided main dex list includes its direct dependencies.
+          // Therefore, we explicitly do not include the direct dependencies of the main dex list
+          // classes in the final main dex, since this would lead to the dependencies of the
+          // dependencies being included in the main dex.
+          assertFalse(mainDexClasses.contains(referencedFromAClassSubject.getFinalName()));
+          assertTrue(mainDexClasses.contains(bClassSubject.getFinalName()));
+          assertTrue(mainDexClasses.contains(referencedFromBClassSubject.getFinalName()));
+        });
+  }
+
+  static class A {
+
+    public void m() {
+      System.out.println(ReferencedFromA.class);
+    }
+  }
+
+  static class ReferencedFromA {}
+
+  static class B {
+
+    public void m() {
+      System.out.println(ReferencedFromB.class);
+    }
+  }
+
+  static class ReferencedFromB {}
+}
diff --git a/src/test/java/com/android/tools/r8/maindexlist/MainDexListOutputTest.java b/src/test/java/com/android/tools/r8/maindexlist/MainDexListOutputTest.java
index 7565cf7..1bbcb9e 100644
--- a/src/test/java/com/android/tools/r8/maindexlist/MainDexListOutputTest.java
+++ b/src/test/java/com/android/tools/r8/maindexlist/MainDexListOutputTest.java
@@ -6,6 +6,7 @@
 
 import static com.android.tools.r8.DiagnosticsMatcher.diagnosticMessage;
 import static org.hamcrest.CoreMatchers.containsString;
+import static org.hamcrest.MatcherAssert.assertThat;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
 
@@ -16,6 +17,7 @@
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.synthesis.SyntheticItemsTestUtils;
 import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.FileUtils;
 import com.google.common.collect.ImmutableList;
@@ -59,8 +61,8 @@
     @Override
     public void finished(DiagnosticsHandler handler) {
       String string = builder.toString();
-      assertTrue(string.contains(testClassMainDexName));
-      assertTrue(string.contains("Lambda"));
+      assertThat(string, containsString(testClassMainDexName));
+      assertThat(string, SyntheticItemsTestUtils.containsExternalSyntheticReference());
     }
   }
 
diff --git a/src/test/java/com/android/tools/r8/maindexlist/MainDexTracingTest.java b/src/test/java/com/android/tools/r8/maindexlist/MainDexTracingTest.java
index 52b60fe..ccaa66a 100644
--- a/src/test/java/com/android/tools/r8/maindexlist/MainDexTracingTest.java
+++ b/src/test/java/com/android/tools/r8/maindexlist/MainDexTracingTest.java
@@ -22,11 +22,12 @@
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestCompilerBuilder;
 import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestShrinkerBuilder;
 import com.android.tools.r8.ThrowableConsumer;
 import com.android.tools.r8.ToolHelper;
-import com.android.tools.r8.ir.desugar.LambdaRewriter;
 import com.android.tools.r8.references.Reference;
 import com.android.tools.r8.shaking.WhyAreYouKeepingConsumer;
+import com.android.tools.r8.synthesis.SyntheticItemsTestUtils;
 import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.Box;
 import com.android.tools.r8.utils.DescriptorUtils;
@@ -210,7 +211,12 @@
         Paths.get(EXAMPLE_SRC_DIR, "multidex", "main-dex-rules.txt"),
         Paths.get(EXAMPLE_O_SRC_DIR, "multidex004", "ref-list-1.txt"),
         Paths.get(EXAMPLE_O_SRC_DIR, "multidex004", "ref-list-1.txt"),
-        AndroidApiLevel.I);
+        AndroidApiLevel.I,
+        builder ->
+            builder
+                .applyIf(
+                    backend.isDex(), TestShrinkerBuilder::addDontWarnCompilerSynthesizedAnnotations)
+                .addOptionsModification(options -> options.enableInlining = false));
   }
 
   @Test
@@ -433,13 +439,13 @@
     for (int i = 0; i < refList.length; i++) {
       String reference = refList[i].trim();
       // The main dex list generator does not do any lambda desugaring.
-      if (!isLambda(reference)) {
+      if (!isExternalSyntheticLambda(reference)) {
         if (mainDexGeneratorMainDexList.size() <= i - nonLambdaOffset) {
           fail("Main dex list generator is missing '" + reference + "'");
         }
         String fromList = mainDexGeneratorMainDexList.get(i - nonLambdaOffset);
         String fromConsumer = mainDexGeneratorMainDexListFromConsumer.get(i - nonLambdaOffset);
-        if (isLambda(fromList)) {
+        if (isExternalSyntheticLambda(fromList)) {
           assertEquals(Backend.DEX, backend);
           assertEquals(fromList, fromConsumer);
           nonLambdaOffset--;
@@ -481,8 +487,8 @@
     assertArrayEquals(entriesUnsorted, entriesSorted);
   }
 
-  private boolean isLambda(String mainDexEntry) {
-    return mainDexEntry.contains(LambdaRewriter.LAMBDA_CLASS_NAME_PREFIX);
+  private boolean isExternalSyntheticLambda(String mainDexEntry) {
+    return SyntheticItemsTestUtils.isExternalLambda(Reference.classFromDescriptor(mainDexEntry));
   }
 
   private String mainDexStringToDescriptor(String mainDexString) {
@@ -492,11 +498,8 @@
   }
 
   private void checkSameMainDexEntry(String reference, String computed) {
-    if (isLambda(reference)) {
-      // For lambda classes we check that there is a lambda class for the right containing
-      // class. However, we do not check the hash for the generated lambda class. The hash
-      // changes for different compiler versions because different compiler versions generate
-      // different lambda implementation method names.
+    if (isExternalSyntheticLambda(reference)) {
+      // For synthetic classes we check that the context classes match.
       reference = reference.substring(0, reference.lastIndexOf('$'));
       computed = computed.substring(0, computed.lastIndexOf('$'));
     }
diff --git a/src/test/java/com/android/tools/r8/maindexlist/MainDexWithSynthesizedClassesTest.java b/src/test/java/com/android/tools/r8/maindexlist/MainDexWithSynthesizedClassesTest.java
index d102004..9fa7fe9 100644
--- a/src/test/java/com/android/tools/r8/maindexlist/MainDexWithSynthesizedClassesTest.java
+++ b/src/test/java/com/android/tools/r8/maindexlist/MainDexWithSynthesizedClassesTest.java
@@ -11,7 +11,7 @@
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
-import com.android.tools.r8.ir.desugar.LambdaRewriter;
+import com.android.tools.r8.synthesis.SyntheticItemsTestUtils;
 import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.StringUtils;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
@@ -100,10 +100,11 @@
         inspector.allClasses().stream()
             .anyMatch(
                 clazz ->
-                    clazz.getOriginalName().contains(LambdaRewriter.LAMBDA_CLASS_NAME_PREFIX)
+                    clazz.isSynthesizedJavaLambdaClass()
                         && clazz
-                            .getOriginalName()
-                            .contains("$" + lambdaHolder.getSimpleName() + "$")));
+                            .getOriginalReference()
+                            .equals(
+                                SyntheticItemsTestUtils.syntheticLambdaClass(lambdaHolder, 0))));
   }
 
   static class TestClass {
diff --git a/src/test/java/com/android/tools/r8/maindexlist/warnings/MainDexWarningsTest.java b/src/test/java/com/android/tools/r8/maindexlist/warnings/MainDexWarningsTest.java
index 4f5e54a..22bad85 100644
--- a/src/test/java/com/android/tools/r8/maindexlist/warnings/MainDexWarningsTest.java
+++ b/src/test/java/com/android/tools/r8/maindexlist/warnings/MainDexWarningsTest.java
@@ -57,6 +57,7 @@
         .addKeepMainRule(mainClass)
         // Include main dex rule for class Static.
         .addMainDexClassRules(Main.class, Static.class)
+        .enableForceInliningAnnotations()
         .setMinApi(parameters.getApiLevel())
         .compile()
         .inspect(this::classStaticGone)
@@ -69,9 +70,11 @@
         .setMinApi(AndroidApiLevel.K)
         .addProgramClasses(testClassesWithoutStatic)
         .addKeepMainRule(mainClass)
+        .addDontWarn(Static.class)
         // Include explicit main dex entry for class Static.
         .addMainDexListClasses(Static.class)
         .allowDiagnosticWarningMessages()
+        .enableForceInliningAnnotations()
         .setMinApi(parameters.getApiLevel())
         .compile()
         .inspect(this::classStaticGone)
@@ -91,7 +94,9 @@
         .addMainDexListClasses(Main.class, Static.class)
         // Include main dex rule for class Static2.
         .addMainDexClassRules(Static2.class)
+        .addDontWarn(Static.class)
         .allowDiagnosticWarningMessages()
+        .enableForceInliningAnnotations()
         .setMinApi(parameters.getApiLevel())
         .compile()
         .inspect(this::classStaticGone)
diff --git a/src/test/java/com/android/tools/r8/naming/EnumMinificationKotlinTest.java b/src/test/java/com/android/tools/r8/naming/EnumMinificationKotlinTest.java
index 837459c..8dd68fc 100644
--- a/src/test/java/com/android/tools/r8/naming/EnumMinificationKotlinTest.java
+++ b/src/test/java/com/android/tools/r8/naming/EnumMinificationKotlinTest.java
@@ -64,6 +64,7 @@
             .addProgramFiles(getJavaJarFile(FOLDER))
             .addKeepMainRule(MAIN_CLASS_NAME)
             .addKeepClassRulesWithAllowObfuscation(ENUM_CLASS_NAME)
+            .addDontWarnJetBrainsAnnotations()
             .allowDiagnosticWarningMessages()
             .minification(minify)
             .setMinApi(parameters.getApiLevel())
diff --git a/src/test/java/com/android/tools/r8/naming/FieldNamingObfuscationDictionaryTest.java b/src/test/java/com/android/tools/r8/naming/FieldNamingObfuscationDictionaryTest.java
index 0ab866f..165d0f7 100644
--- a/src/test/java/com/android/tools/r8/naming/FieldNamingObfuscationDictionaryTest.java
+++ b/src/test/java/com/android/tools/r8/naming/FieldNamingObfuscationDictionaryTest.java
@@ -99,6 +99,7 @@
         .addInnerClasses(FieldNamingObfuscationDictionaryTest.class)
         .addKeepRules("-overloadaggressively", "-obfuscationdictionary " + dictionary.toString())
         .addKeepMainRule(Runner.class)
+        .enableInliningAnnotations()
         .enableNeverClassInliningAnnotations()
         .enableNoHorizontalClassMergingAnnotations()
         .setMinApi(parameters.getApiLevel())
diff --git a/src/test/java/com/android/tools/r8/naming/InterfaceConstructorRenamingTest.java b/src/test/java/com/android/tools/r8/naming/InterfaceConstructorRenamingTest.java
index ebd7420..7ec2589 100644
--- a/src/test/java/com/android/tools/r8/naming/InterfaceConstructorRenamingTest.java
+++ b/src/test/java/com/android/tools/r8/naming/InterfaceConstructorRenamingTest.java
@@ -6,7 +6,6 @@
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 
-import com.android.tools.r8.ArchiveClassFileProvider;
 import com.android.tools.r8.R8TestCompileResult;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
@@ -16,10 +15,8 @@
 import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.StringUtils;
 import com.android.tools.r8.utils.codeinspector.MethodSubject;
-import com.google.common.collect.ImmutableSet;
 import java.io.IOException;
 import java.util.Collections;
-import java.util.concurrent.ExecutionException;
 import java.util.function.Function;
 import org.junit.ClassRule;
 import org.junit.Test;
@@ -44,17 +41,11 @@
   @ClassRule public static TemporaryFolder staticTemp = ToolHelper.getTemporaryFolderForTest();
 
   private static R8TestCompileResult compile(Backend backend)
-      throws com.android.tools.r8.CompilationFailedException, IOException, ExecutionException {
+      throws com.android.tools.r8.CompilationFailedException, IOException {
     R8TestCompileResult compileResult =
         testForR8(staticTemp, backend)
             .addProgramClasses(TestInterface.class, TestClass.class)
             .addKeepMainRule(TestClass.class)
-            .addLibraryProvider(
-                new ArchiveClassFileProvider(
-                    ToolHelper.getJava8RuntimeJar(),
-                    name ->
-                        ImmutableSet.of("java/lang/Object", "java/lang/System")
-                            .contains(name.replace(".class", ""))))
             .setMinApi(AndroidApiLevel.B)
             .compile()
             .assertNoMessages();
diff --git a/src/test/java/com/android/tools/r8/naming/IntersectionLambdaTest.java b/src/test/java/com/android/tools/r8/naming/IntersectionLambdaTest.java
index efb5d35..5b8dc11 100644
--- a/src/test/java/com/android/tools/r8/naming/IntersectionLambdaTest.java
+++ b/src/test/java/com/android/tools/r8/naming/IntersectionLambdaTest.java
@@ -51,7 +51,6 @@
         .assertSuccessWithOutputLines(EXPECTED);
   }
 
-  @FunctionalInterface
   @NoVerticalClassMerging
   public interface I {
     void foo();
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 70a9cb2..3d143a1 100644
--- a/src/test/java/com/android/tools/r8/naming/KotlinIntrinsicsIdentifierTest.java
+++ b/src/test/java/com/android/tools/r8/naming/KotlinIntrinsicsIdentifierTest.java
@@ -88,6 +88,7 @@
             .addProgramFiles(compiledJars.getForConfiguration(kotlinc, targetVersion))
             .addProgramFiles(getJavaJarFile(FOLDER))
             .addKeepMainRule(mainClassName)
+            .addDontWarnJetBrainsNotNullAnnotation()
             .allowDiagnosticWarningMessages()
             .minification(minification)
             .compile()
@@ -149,6 +150,7 @@
                     "-" + NoHorizontalClassMergingRule.RULE_NAME + " class **." + targetClassName,
                     "-" + NoStaticClassMergingRule.RULE_NAME + " class **." + targetClassName,
                     "-neverinline class **." + targetClassName + " { <methods>; }"))
+            .addDontWarnJetBrainsNotNullAnnotation()
             .allowDiagnosticWarningMessages()
             .minification(minification)
             .compile()
diff --git a/src/test/java/com/android/tools/r8/naming/applymapping/ApplyMappingAfterVerticalMergingMethodTest.java b/src/test/java/com/android/tools/r8/naming/applymapping/ApplyMappingAfterVerticalMergingMethodTest.java
index 4d000bb..3cb240b 100644
--- a/src/test/java/com/android/tools/r8/naming/applymapping/ApplyMappingAfterVerticalMergingMethodTest.java
+++ b/src/test/java/com/android/tools/r8/naming/applymapping/ApplyMappingAfterVerticalMergingMethodTest.java
@@ -123,7 +123,8 @@
               List<FoundMethodSubject> methods =
                   inspector.clazz(LibrarySubclass.class).allMethods();
               assertEquals(3, methods.size());
-              assertEquals(1, methods.stream().filter(m -> m.isInstanceInitializer()).count());
+              assertEquals(
+                  1, methods.stream().filter(FoundMethodSubject::isInstanceInitializer).count());
               assertEquals(
                   1, methods.stream().filter(m -> m.getFinalName().contains("main")).count());
               assertEquals(
diff --git a/src/test/java/com/android/tools/r8/naming/applymapping/ApplyMappingDesugarLambdaTest.java b/src/test/java/com/android/tools/r8/naming/applymapping/ApplyMappingDesugarLambdaTest.java
index 0d0af87..9db718e 100644
--- a/src/test/java/com/android/tools/r8/naming/applymapping/ApplyMappingDesugarLambdaTest.java
+++ b/src/test/java/com/android/tools/r8/naming/applymapping/ApplyMappingDesugarLambdaTest.java
@@ -104,7 +104,6 @@
     }
   }
 
-  @FunctionalInterface
   public interface I {
 
     void doStuff();
diff --git a/src/test/java/com/android/tools/r8/naming/applymapping/ApplyMappingInnerClassesPreserveTest.java b/src/test/java/com/android/tools/r8/naming/applymapping/ApplyMappingInnerClassesPreserveTest.java
index 6d61f51..2f7c01c 100644
--- a/src/test/java/com/android/tools/r8/naming/applymapping/ApplyMappingInnerClassesPreserveTest.java
+++ b/src/test/java/com/android/tools/r8/naming/applymapping/ApplyMappingInnerClassesPreserveTest.java
@@ -26,7 +26,7 @@
 
   @Parameters(name = "{0}")
   public static TestParametersCollection data() {
-    return getTestParameters().withAllRuntimes().build();
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
   }
 
   public ApplyMappingInnerClassesPreserveTest(TestParameters parameters) {
@@ -41,7 +41,7 @@
             .addProgramClassesAndInnerClasses(InnerLibraryClass.class)
             .addKeepAllClassesRuleWithAllowObfuscation()
             .addKeepClassAndMembersRulesWithAllowObfuscation(InnerLibraryClass.class)
-            .setMinApi(parameters.getRuntime())
+            .setMinApi(parameters.getApiLevel())
             .compile();
     testForR8(parameters.getBackend())
         .addProgramClassesAndInnerClasses(ProgramClassWithSimpleLibraryReference.class)
@@ -49,7 +49,7 @@
         .addKeepMainRule(ProgramClassWithSimpleLibraryReference.class)
         .addApplyMapping(libraryCompileResult.getProguardMap())
         .addKeepAttributes("EnclosingMethod", "InnerClasses")
-        .setMinApi(parameters.getRuntime())
+        .setMinApi(parameters.getApiLevel())
         .noTreeShaking()
         .compile()
         .addRunClasspathFiles(libraryCompileResult.writeToZip())
diff --git a/src/test/java/com/android/tools/r8/naming/applymapping/ApplyMappingInterfaceClInitTest.java b/src/test/java/com/android/tools/r8/naming/applymapping/ApplyMappingInterfaceClInitTest.java
index 6887f59..de95714 100644
--- a/src/test/java/com/android/tools/r8/naming/applymapping/ApplyMappingInterfaceClInitTest.java
+++ b/src/test/java/com/android/tools/r8/naming/applymapping/ApplyMappingInterfaceClInitTest.java
@@ -86,7 +86,6 @@
     void foo();
   }
 
-  @NeverClassInline
   public static class Main implements TestInterface {
 
     public static void main(String[] args) {
@@ -94,7 +93,6 @@
     }
 
     @Override
-    @NeverInline
     public void foo() {
       System.out.println("Hello World!");
     }
diff --git a/src/test/java/com/android/tools/r8/naming/applymapping/ApplyMappingMultipleInterfacesTest.java b/src/test/java/com/android/tools/r8/naming/applymapping/ApplyMappingMultipleInterfacesTest.java
index b56464e..af6c004 100644
--- a/src/test/java/com/android/tools/r8/naming/applymapping/ApplyMappingMultipleInterfacesTest.java
+++ b/src/test/java/com/android/tools/r8/naming/applymapping/ApplyMappingMultipleInterfacesTest.java
@@ -5,7 +5,6 @@
 package com.android.tools.r8.naming.applymapping;
 
 import com.android.tools.r8.CompilationFailedException;
-import com.android.tools.r8.NeverInline;
 import com.android.tools.r8.R8TestCompileResult;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
@@ -46,7 +45,6 @@
 
   public static class ImplementsI2I3 implements I2, I3 {
     @Override
-    @NeverInline
     public String foo(String bar) {
       System.out.print("Hello" + bar);
       return bar;
@@ -55,7 +53,6 @@
 
   public static class ImplementsI3 implements I3 {
     @Override
-    @NeverInline
     public String foo(String bar) {
       System.out.print("Goodbye");
       return bar;
@@ -84,7 +81,7 @@
 
   @Parameters(name = "{0}")
   public static TestParametersCollection data() {
-    return getTestParameters().withAllRuntimes().build();
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
   }
 
   public ApplyMappingMultipleInterfacesTest(TestParameters parameters) {
@@ -98,19 +95,24 @@
         testForR8(parameters.getBackend())
             .addProgramClasses(I2Minified.class, I3Minified.class)
             .addKeepAllClassesRule()
-            .setMinApi(parameters.getRuntime())
+            .setMinApi(parameters.getApiLevel())
             .compile();
     testForR8(parameters.getBackend())
         .addClasspathClasses(I2.class, I3.class)
         .addProgramClasses(MainForImplements.class, ImplementsI2I3.class, ImplementsI3.class)
         .addKeepMainRule(MainForImplements.class)
         .addApplyMapping(
-            I2.class.getTypeName() + " -> " + I2Minified.class.getTypeName() + ":\n" +
-            "  java.lang.String foo(java.lang.String) -> a\n" +
-            I3.class.getTypeName() + " -> " + I3Minified.class.getTypeName() + ":\n" +
-                "  java.lang.String foo(java.lang.String) -> a"
-        )
-        .setMinApi(parameters.getRuntime())
+            I2.class.getTypeName()
+                + " -> "
+                + I2Minified.class.getTypeName()
+                + ":\n"
+                + "  java.lang.String foo(java.lang.String) -> a\n"
+                + I3.class.getTypeName()
+                + " -> "
+                + I3Minified.class.getTypeName()
+                + ":\n"
+                + "  java.lang.String foo(java.lang.String) -> a")
+        .setMinApi(parameters.getApiLevel())
         .compile()
         .addRunClasspathFiles(libraryResult.writeToZip())
         .run(parameters.getRuntime(), MainForImplements.class)
diff --git a/src/test/java/com/android/tools/r8/naming/applymapping/shared/InnerLibraryClass.java b/src/test/java/com/android/tools/r8/naming/applymapping/shared/InnerLibraryClass.java
index 1e751d1..8cfa59b 100644
--- a/src/test/java/com/android/tools/r8/naming/applymapping/shared/InnerLibraryClass.java
+++ b/src/test/java/com/android/tools/r8/naming/applymapping/shared/InnerLibraryClass.java
@@ -4,12 +4,9 @@
 
 package com.android.tools.r8.naming.applymapping.shared;
 
-import com.android.tools.r8.NeverInline;
-
 public class InnerLibraryClass {
 
   public static class LibraryClass {
-    @NeverInline
     public void foo() {
       System.out.println("LibraryClass.foo()");
     }
diff --git a/src/test/java/com/android/tools/r8/naming/applymapping/shared/ProgramClassWithSimpleLibraryReference.java b/src/test/java/com/android/tools/r8/naming/applymapping/shared/ProgramClassWithSimpleLibraryReference.java
index 42b7907..a42654f 100644
--- a/src/test/java/com/android/tools/r8/naming/applymapping/shared/ProgramClassWithSimpleLibraryReference.java
+++ b/src/test/java/com/android/tools/r8/naming/applymapping/shared/ProgramClassWithSimpleLibraryReference.java
@@ -12,7 +12,6 @@
   public static class SubLibraryClass extends LibraryClass {
 
     @Override
-    @NeverInline
     public void foo() {
       System.out.println("SubLibraryClass.foo()");
       super.foo();
diff --git a/src/test/java/com/android/tools/r8/naming/b126592786/B126592786.java b/src/test/java/com/android/tools/r8/naming/b126592786/B126592786.java
index de81c15..b2614a3 100644
--- a/src/test/java/com/android/tools/r8/naming/b126592786/B126592786.java
+++ b/src/test/java/com/android/tools/r8/naming/b126592786/B126592786.java
@@ -44,7 +44,7 @@
     Class<?> mainClass = genericTypeLive ? MainGenericTypeLive.class : MainGenericTypeNotLive.class;
     testForR8(backend)
         .minification(minify)
-        .addProgramClasses(GetClassUtil.class, A.class, GenericType.class, mainClass)
+        .addProgramClasses(GetClassUtil.class, A.class, GenericType.class, mainClass, Marker.class)
         .addKeepMainRule(mainClass)
         .addKeepRules(
             "-keep class " + GetClassUtil.class.getTypeName() + " {",
diff --git a/src/test/java/com/android/tools/r8/naming/b139991218/TestRunner.java b/src/test/java/com/android/tools/r8/naming/b139991218/TestRunner.java
index ef791d3..69f0148 100644
--- a/src/test/java/com/android/tools/r8/naming/b139991218/TestRunner.java
+++ b/src/test/java/com/android/tools/r8/naming/b139991218/TestRunner.java
@@ -64,6 +64,7 @@
               options.testing.validInliningReasons = ImmutableSet.of(Reason.FORCE);
               options.enableClassInlining = false;
             })
+        .addDontWarnJetBrainsAnnotations()
         .setMinApi(parameters.getApiLevel())
         .run(parameters.getRuntime(), Main.class)
         .assertSuccessWithOutput(StringUtils.lines("11", "12"))
diff --git a/src/test/java/com/android/tools/r8/naming/methodparameters/MethodParametersTest.java b/src/test/java/com/android/tools/r8/naming/methodparameters/MethodParametersTest.java
index 30e72d7..a8e337e 100644
--- a/src/test/java/com/android/tools/r8/naming/methodparameters/MethodParametersTest.java
+++ b/src/test/java/com/android/tools/r8/naming/methodparameters/MethodParametersTest.java
@@ -14,6 +14,7 @@
 import com.android.tools.r8.R8TestRunResult;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.ToolHelper.DexVm.Version;
 import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.BooleanUtils;
@@ -58,7 +59,10 @@
             .addKeepClassAndMembersRulesWithAllowObfuscation(MethodParametersTest.class)
             .addKeepMainRule(MethodParametersTest.class)
             .addKeepRules(keepMethodParameters ? "-keepattributes MethodParameters" : "")
-            .setMinApi(AndroidApiLevel.L)
+            .setMinApi(keepMethodParameters ? AndroidApiLevel.O : AndroidApiLevel.L)
+            // java.lang.reflect.Parameter was introduced in API level 26 (O).
+            .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.O))
+            .compile()
             .run(parameters.getRuntime(), MethodParametersTest.class);
     if (keepMethodParameters) {
       checkOutputContainsAll(runResult.getStdOut());
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 3959d84..2836f61 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
@@ -14,6 +14,8 @@
 import com.android.tools.r8.CompilationMode;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.naming.retrace.StackTrace.StackTraceLine;
+import com.android.tools.r8.references.Reference;
+import com.android.tools.r8.synthesis.SyntheticItemsTestUtils;
 import com.android.tools.r8.utils.BooleanUtils;
 import com.google.common.collect.ImmutableList;
 import java.util.Collection;
@@ -57,7 +59,8 @@
   }
 
   private boolean isSynthesizedLambdaFrame(StackTraceLine line) {
-    return line.className.contains("-$$Lambda$");
+    // TODO(141287349): The mapping should not map the external name to the internal name!
+    return SyntheticItemsTestUtils.isInternalLambda(Reference.classFromTypeName(line.className));
   }
 
   private void checkLambdaFrame(StackTrace retracedStackTrace) {
@@ -141,7 +144,6 @@
 }
 
 // Custom consumer functional interface, as java.util.function.Consumer is not present on Android.
-@FunctionalInterface
 interface ConsumerDesugarLambdaRetraceTest<T> {
   void accept(T value);
 }
diff --git a/src/test/java/com/android/tools/r8/naming/retrace/InliningRetraceTest.java b/src/test/java/com/android/tools/r8/naming/retrace/InliningRetraceTest.java
index 2ffe206..344a716 100644
--- a/src/test/java/com/android/tools/r8/naming/retrace/InliningRetraceTest.java
+++ b/src/test/java/com/android/tools/r8/naming/retrace/InliningRetraceTest.java
@@ -84,7 +84,9 @@
 
   @Override
   public void configure(R8TestBuilder<?> builder) {
-    builder.applyIf(mode == CompilationMode.RELEASE, R8TestBuilder::enableForceInliningAnnotations);
+    builder
+        .addForceInliningAnnotations()
+        .applyIf(mode == CompilationMode.RELEASE, R8TestBuilder::enableForceInliningAnnotations);
   }
 
   @Override
diff --git a/src/test/java/com/android/tools/r8/naming/retraceproguard/DesugarLambdaRetraceTest.java b/src/test/java/com/android/tools/r8/naming/retraceproguard/DesugarLambdaRetraceTest.java
index a527d93..458cb07 100644
--- a/src/test/java/com/android/tools/r8/naming/retraceproguard/DesugarLambdaRetraceTest.java
+++ b/src/test/java/com/android/tools/r8/naming/retraceproguard/DesugarLambdaRetraceTest.java
@@ -166,7 +166,6 @@
 }
 
 // Custom consumer functional interface, as java.util.function.Consumer is not present on Android.
-@FunctionalInterface
 interface ConsumerDesugarLambdaRetraceTest<T> {
   void accept(T value);
 }
diff --git a/src/test/java/com/android/tools/r8/naming/retraceproguard/DesugarStaticInterfaceMethodsRetraceTest.java b/src/test/java/com/android/tools/r8/naming/retraceproguard/DesugarStaticInterfaceMethodsRetraceTest.java
index 2034f5d..47240a5 100644
--- a/src/test/java/com/android/tools/r8/naming/retraceproguard/DesugarStaticInterfaceMethodsRetraceTest.java
+++ b/src/test/java/com/android/tools/r8/naming/retraceproguard/DesugarStaticInterfaceMethodsRetraceTest.java
@@ -9,6 +9,7 @@
 
 import com.android.tools.r8.CompilationMode;
 import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.R8TestBuilder;
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.utils.BooleanUtils;
 import com.google.common.collect.ImmutableList;
@@ -33,6 +34,11 @@
   }
 
   @Override
+  public void configure(R8TestBuilder builder) {
+    builder.enableInliningAnnotations();
+  }
+
+  @Override
   public Collection<Class<?>> getClasses() {
     return ImmutableList.of(
         getMainClass(), InterfaceWithStaticMethod1.class, InterfaceWithStaticMethod2.class);
diff --git a/src/test/java/com/android/tools/r8/naming/retraceproguard/RetraceTestBase.java b/src/test/java/com/android/tools/r8/naming/retraceproguard/RetraceTestBase.java
index e94483f..62c4de9 100644
--- a/src/test/java/com/android/tools/r8/naming/retraceproguard/RetraceTestBase.java
+++ b/src/test/java/com/android/tools/r8/naming/retraceproguard/RetraceTestBase.java
@@ -48,12 +48,12 @@
 
   public void runTest(List<String> keepRules, BiConsumer<StackTrace, StackTrace> checker)
       throws Exception {
-
     R8TestRunResult result =
         (compat ? testForR8Compat(backend) : testForR8(backend))
             .setMode(mode)
             .enableProguardTestOptions()
             .addProgramClasses(getClasses())
+            .addForceInliningAnnotations()
             .addKeepMainRule(getMainClass())
             .addKeepRules(keepRules)
             .apply(this::configure)
diff --git a/src/test/java/com/android/tools/r8/regress/b63935662/Regress63935662.java b/src/test/java/com/android/tools/r8/regress/b63935662/Regress63935662.java
index 1151d20..ced781f 100644
--- a/src/test/java/com/android/tools/r8/regress/b63935662/Regress63935662.java
+++ b/src/test/java/com/android/tools/r8/regress/b63935662/Regress63935662.java
@@ -8,6 +8,7 @@
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.AndroidApiLevel;
 import com.google.common.collect.ImmutableList;
 import java.util.List;
 import org.junit.Test;
@@ -21,7 +22,10 @@
 
   @Parameterized.Parameters(name = "{0}")
   public static TestParametersCollection data() {
-    return getTestParameters().withAllRuntimesAndApiLevels().build();
+    return getTestParameters()
+        .withAllRuntimes()
+        .withApiLevelsStartingAtIncluding(AndroidApiLevel.N)
+        .build();
   }
 
   public Regress63935662(TestParameters parameters) {
diff --git a/src/test/java/com/android/tools/r8/repackage/RepackageWithInitClassTest.java b/src/test/java/com/android/tools/r8/repackage/RepackageWithInitClassTest.java
index e548bcf..c8a47ca 100644
--- a/src/test/java/com/android/tools/r8/repackage/RepackageWithInitClassTest.java
+++ b/src/test/java/com/android/tools/r8/repackage/RepackageWithInitClassTest.java
@@ -49,6 +49,7 @@
         .addInnerClasses(getClass())
         .addClassObfuscationDictionary("a")
         .addKeepMainRule(TestClass.class)
+        .addMemberValuePropagationAnnotations()
         .apply(this::configureRepackaging)
         .enableMemberValuePropagationAnnotations(enableMemberValuePropagationAnnotations)
         .setMinApi(parameters.getApiLevel())
diff --git a/src/test/java/com/android/tools/r8/repackage/RepackageWithPackagePrivateFieldTypeTest.java b/src/test/java/com/android/tools/r8/repackage/RepackageWithPackagePrivateFieldTypeTest.java
index 9dd90f3..543dbf8 100644
--- a/src/test/java/com/android/tools/r8/repackage/RepackageWithPackagePrivateFieldTypeTest.java
+++ b/src/test/java/com/android/tools/r8/repackage/RepackageWithPackagePrivateFieldTypeTest.java
@@ -63,7 +63,6 @@
 
   public static class IneligibleForRepackaging {
 
-    @NeverPropagateValue
     private static NonPublicKeptClass FIELD =
         System.currentTimeMillis() > 0 ? new PublicSubClass() : null;
 
diff --git a/src/test/java/com/android/tools/r8/repackage/testclasses/repackagetest/AccessPackagePrivateMethodOnKeptClassIndirect.java b/src/test/java/com/android/tools/r8/repackage/testclasses/repackagetest/AccessPackagePrivateMethodOnKeptClassIndirect.java
index 2319ca4..7190c3f 100644
--- a/src/test/java/com/android/tools/r8/repackage/testclasses/repackagetest/AccessPackagePrivateMethodOnKeptClassIndirect.java
+++ b/src/test/java/com/android/tools/r8/repackage/testclasses/repackagetest/AccessPackagePrivateMethodOnKeptClassIndirect.java
@@ -7,7 +7,6 @@
 import com.android.tools.r8.NeverInline;
 import com.android.tools.r8.NoHorizontalClassMerging;
 import com.android.tools.r8.NoStaticClassMerging;
-import com.android.tools.r8.NoVerticalClassMerging;
 
 @NoHorizontalClassMerging
 @NoStaticClassMerging
@@ -18,7 +17,8 @@
     Helper.test();
   }
 
-  @NoVerticalClassMerging
+  @NoHorizontalClassMerging
+  @NoStaticClassMerging
   public static class Helper {
 
     @NeverInline
diff --git a/src/test/java/com/android/tools/r8/resolution/InvokeDefaultMethodViaStaticTest.java b/src/test/java/com/android/tools/r8/resolution/InvokeDefaultMethodViaStaticTest.java
index 2080b42..37f328e 100644
--- a/src/test/java/com/android/tools/r8/resolution/InvokeDefaultMethodViaStaticTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/InvokeDefaultMethodViaStaticTest.java
@@ -6,7 +6,6 @@
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
-import com.android.tools.r8.TestRunResult;
 import com.android.tools.r8.resolution.invokestaticinterfacedefault.InterfaceDump;
 import com.android.tools.r8.resolution.invokestaticinterfacedefault.MainDump;
 import com.google.common.collect.ImmutableList;
@@ -35,34 +34,20 @@
 
   @Test
   public void testReference() throws Exception {
-    TestRunResult<?> result =
-        testForRuntime(parameters)
-            .addProgramClassFileData(CLASSES)
-            .run(parameters.getRuntime(), "Main");
-    if (parameters.isDexRuntime()
-        && parameters.getApiLevel().isLessThan(apiLevelWithDefaultInterfaceMethodsSupport())) {
-      // TODO(b/167535447): Desugaring should preserve the error.
-      result.assertFailureWithErrorThatThrows(NoSuchMethodError.class);
-    } else {
-      result.assertFailureWithErrorThatThrows(IncompatibleClassChangeError.class);
-    }
+    testForRuntime(parameters)
+        .addProgramClassFileData(CLASSES)
+        .run(parameters.getRuntime(), "Main")
+        .assertFailureWithErrorThatThrows(IncompatibleClassChangeError.class);
   }
 
   @Test
   public void testR8() throws Exception {
-    TestRunResult<?> result =
-        testForR8(parameters.getBackend())
-            .addProgramClassFileData(CLASSES)
-            .addKeepMainRule("Main")
-            .setMinApi(parameters.getApiLevel())
-            .addOptionsModification(o -> o.testing.allowInvokeErrors = true)
-            .run(parameters.getRuntime(), "Main");
-    if (parameters.isDexRuntime()
-        && parameters.getApiLevel().isLessThan(apiLevelWithDefaultInterfaceMethodsSupport())) {
-      // TODO(b/167535447): Desugaring should preserve the error.
-      result.assertFailureWithErrorThatThrows(NoClassDefFoundError.class);
-    } else {
-      result.assertFailureWithErrorThatThrows(IncompatibleClassChangeError.class);
-    }
+    testForR8(parameters.getBackend())
+        .addProgramClassFileData(CLASSES)
+        .addKeepMainRule("Main")
+        .setMinApi(parameters.getApiLevel())
+        .addOptionsModification(o -> o.testing.allowInvokeErrors = true)
+        .run(parameters.getRuntime(), "Main")
+        .assertFailureWithErrorThatThrows(IncompatibleClassChangeError.class);
   }
 }
diff --git a/src/test/java/com/android/tools/r8/resolution/SingleStaticTargetLookupTestRunner.java b/src/test/java/com/android/tools/r8/resolution/SingleStaticTargetLookupTestRunner.java
index fef40af..8edefd8 100644
--- a/src/test/java/com/android/tools/r8/resolution/SingleStaticTargetLookupTestRunner.java
+++ b/src/test/java/com/android/tools/r8/resolution/SingleStaticTargetLookupTestRunner.java
@@ -17,6 +17,7 @@
 import com.android.tools.r8.TestShrinkerBuilder;
 import com.android.tools.r8.utils.StringUtils;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.InstructionSubject;
 import com.android.tools.r8.utils.codeinspector.MethodSubject;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -65,7 +66,8 @@
               assertThat(clazz, isPresent());
               assertThat(main, isPresent());
               assertEquals(keepFoo, staticFoo.isPresent());
-              assertEquals(keepFoo, main.streamInstructions().anyMatch(i -> i.isInvokeStatic()));
+              assertEquals(
+                  keepFoo, main.streamInstructions().anyMatch(InstructionSubject::isInvokeStatic));
             });
   }
 
diff --git a/src/test/java/com/android/tools/r8/resolution/VirtualOverrideOfPrivateStaticMethodWithVirtualParentTest.java b/src/test/java/com/android/tools/r8/resolution/VirtualOverrideOfPrivateStaticMethodWithVirtualParentTest.java
index 5da5b2d..1d6dc16 100644
--- a/src/test/java/com/android/tools/r8/resolution/VirtualOverrideOfPrivateStaticMethodWithVirtualParentTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/VirtualOverrideOfPrivateStaticMethodWithVirtualParentTest.java
@@ -44,6 +44,7 @@
   public static class B extends A implements I {}
 
   public static class C extends B {
+    @Override
     public void f() {
       System.out.println("Called C.f");
     }
diff --git a/src/test/java/com/android/tools/r8/resolution/access/NestInvokeSpecialInterfaceMethodAccessTest.java b/src/test/java/com/android/tools/r8/resolution/access/NestInvokeSpecialInterfaceMethodAccessTest.java
index 3f7568a..425f3b0 100644
--- a/src/test/java/com/android/tools/r8/resolution/access/NestInvokeSpecialInterfaceMethodAccessTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/access/NestInvokeSpecialInterfaceMethodAccessTest.java
@@ -11,14 +11,15 @@
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestRunResult;
+import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexClassAndMethod;
+import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.ResolutionResult;
 import com.android.tools.r8.graph.ResolutionResult.NoSuchMethodResult;
 import com.android.tools.r8.references.Reference;
-import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.transformers.ClassFileTransformer;
 import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.BooleanUtils;
@@ -108,15 +109,15 @@
     Class<?> declaredClass = symbolicReferenceIsDefiningType ? definingClass : A.class;
     Class<?> callerClass = A.class;
 
-    AppView<AppInfoWithLiveness> appView = getAppView();
-    AppInfoWithLiveness appInfo = appView.appInfo();
+    AppView<AppInfoWithClassHierarchy> appView = getAppView();
+    AppInfoWithClassHierarchy appInfo = appView.appInfo();
 
-    DexProgramClass definingClassDefinition = getDexProgramClass(definingClass, appInfo);
-    DexProgramClass declaredClassDefinition = getDexProgramClass(declaredClass, appInfo);
-    DexProgramClass callerClassDefinition = getDexProgramClass(callerClass, appInfo);
+    DexProgramClass definingClassDefinition = getDexProgramClass(definingClass, appView);
+    DexProgramClass declaredClassDefinition = getDexProgramClass(declaredClass, appView);
+    DexProgramClass callerClassDefinition = getDexProgramClass(callerClass, appView);
 
-    DexMethod method = getTargetMethodSignature(declaredClass, appInfo);
-    assertCallingClassCallsTarget(callerClass, appInfo, method);
+    DexMethod method = getTargetMethodSignature(declaredClass, appView.dexItemFactory());
+    assertCallingClassCallsTarget(callerClass, appView, method);
 
     // Resolve the method from the point of the declared holder.
     assertEquals(method.holder, declaredClassDefinition.type);
@@ -136,7 +137,7 @@
     DexClassAndMethod targetSuper =
         resolutionResult.lookupInvokeSuperTarget(callerClassDefinition, appInfo);
     if (inSameNest) {
-      assertEquals(definingClassDefinition.type, targetSpecial.getHolderType());
+      assertEquals(definingClassDefinition.getType(), targetSpecial.getHolderType());
       assertEquals(targetSpecial.getReference(), targetSuper.getReference());
     } else {
       assertNull(targetSpecial);
@@ -145,30 +146,30 @@
   }
 
   private void assertCallingClassCallsTarget(
-      Class<?> callerClass, AppInfoWithLiveness appInfo, DexMethod target) {
-    CodeInspector inspector = new CodeInspector(appInfo.app());
+      Class<?> callerClass, AppView<?> appView, DexMethod target) {
+    CodeInspector inspector = new CodeInspector(appView.appInfo().app());
     MethodSubject foo = inspector.clazz(callerClass).uniqueMethodWithName("foo");
     assertTrue(
         foo.streamInstructions().anyMatch(i -> i.isInvokeSpecial() && i.getMethod() == target));
   }
 
-  private DexMethod getTargetMethodSignature(Class<?> declaredClass, AppInfoWithLiveness appInfo) {
+  private DexMethod getTargetMethodSignature(
+      Class<?> declaredClass, DexItemFactory dexItemFactory) {
     return buildMethod(
         Reference.method(Reference.classFromClass(declaredClass), "bar", ImmutableList.of(), null),
-        appInfo.dexItemFactory());
+        dexItemFactory);
   }
 
-  private DexProgramClass getDexProgramClass(Class<?> clazz, AppInfoWithLiveness appInfo) {
-    return appInfo.definitionFor(buildType(clazz, appInfo.dexItemFactory())).asProgramClass();
+  private DexProgramClass getDexProgramClass(Class<?> clazz, AppView<?> appView) {
+    return appView.definitionFor(buildType(clazz, appView.dexItemFactory())).asProgramClass();
   }
 
-  private AppView<AppInfoWithLiveness> getAppView() throws Exception {
-    return computeAppViewWithLiveness(
+  private AppView<AppInfoWithClassHierarchy> getAppView() throws Exception {
+    return computeAppViewWithClassHierachy(
         buildClasses(getClasses())
             .addClassProgramData(getTransformedClasses())
             .addLibraryFile(TestBase.runtimeJar(parameters.getBackend()))
-            .build(),
-        Main.class);
+            .build());
   }
 
   @Test
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 364014c..07487ec 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,6 +171,10 @@
         .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));
   }
diff --git a/src/test/java/com/android/tools/r8/resolution/access/NestInvokeSpecialMethodAccessTest.java b/src/test/java/com/android/tools/r8/resolution/access/NestInvokeSpecialMethodAccessTest.java
index 5a3d01e..c6a2dbf 100644
--- a/src/test/java/com/android/tools/r8/resolution/access/NestInvokeSpecialMethodAccessTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/access/NestInvokeSpecialMethodAccessTest.java
@@ -11,6 +11,7 @@
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestRunResult;
+import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexClassAndMethod;
@@ -18,7 +19,6 @@
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.ResolutionResult;
 import com.android.tools.r8.references.Reference;
-import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.transformers.ClassFileTransformer;
 import com.android.tools.r8.utils.BooleanUtils;
 import com.android.tools.r8.utils.OptionalBool;
@@ -82,16 +82,16 @@
     Class<?> declaredClass = A.class;
     Class<?> callerClass = B.class;
 
-    AppView<AppInfoWithLiveness> appView = getAppView();
-    AppInfoWithLiveness appInfo = appView.appInfo();
+    AppView<AppInfoWithClassHierarchy> appView = getAppView();
+    AppInfoWithClassHierarchy appInfo = appView.appInfo();
 
-    DexClass definingClassDefinition = getDexProgramClass(definingClass, appInfo);
-    DexClass declaredClassDefinition = getDexProgramClass(declaredClass, appInfo);
-    DexProgramClass callerClassDefinition = getDexProgramClass(callerClass, appInfo);
+    DexClass definingClassDefinition = getDexProgramClass(definingClass, appView);
+    DexClass declaredClassDefinition = getDexProgramClass(declaredClass, appView);
+    DexProgramClass callerClassDefinition = getDexProgramClass(callerClass, appView);
 
-    DexMethod method = getTargetMethodSignature(declaredClass, appInfo);
+    DexMethod method = getTargetMethodSignature(declaredClass, appView);
 
-    assertCallingClassCallsTarget(callerClass, appInfo, method);
+    assertCallingClassCallsTarget(callerClass, appView, method);
 
     // Resolve the method from the point of the declared holder.
     assertEquals(method.holder, declaredClassDefinition.type);
@@ -121,29 +121,28 @@
   }
 
   private void assertCallingClassCallsTarget(
-      Class<?> callerClass, AppInfoWithLiveness appInfo, DexMethod target) {
-    CodeInspector inspector = new CodeInspector(appInfo.app());
+      Class<?> callerClass, AppView<?> appView, DexMethod target) {
+    CodeInspector inspector = new CodeInspector(appView.appInfo().app());
     MethodSubject foo = inspector.clazz(callerClass).uniqueMethodWithName("foo");
     assertTrue(
         foo.streamInstructions().anyMatch(i -> i.isInvokeSpecial() && i.getMethod() == target));
   }
 
-  private DexMethod getTargetMethodSignature(Class<?> declaredClass, AppInfoWithLiveness appInfo) {
+  private DexMethod getTargetMethodSignature(Class<?> declaredClass, AppView<?> appView) {
     return buildMethod(
         Reference.method(Reference.classFromClass(declaredClass), "bar", ImmutableList.of(), null),
-        appInfo.dexItemFactory());
+        appView.dexItemFactory());
   }
 
-  private DexProgramClass getDexProgramClass(Class<?> definingClass, AppInfoWithLiveness appInfo) {
-    return appInfo
-        .definitionFor(buildType(definingClass, appInfo.dexItemFactory()))
+  private DexProgramClass getDexProgramClass(Class<?> definingClass, AppView<?> appView) {
+    return appView
+        .definitionFor(buildType(definingClass, appView.dexItemFactory()))
         .asProgramClass();
   }
 
-  private AppView<AppInfoWithLiveness> getAppView() throws Exception {
-    return computeAppViewWithLiveness(
-        buildClasses(getClasses()).addClassProgramData(getTransformedClasses()).build(),
-        Main.class);
+  private AppView<AppInfoWithClassHierarchy> getAppView() throws Exception {
+    return computeAppViewWithClassHierachy(
+        buildClasses(getClasses()).addClassProgramData(getTransformedClasses()).build());
   }
 
   @Test
diff --git a/src/test/java/com/android/tools/r8/resolution/access/NestInvokeSpecialMethodAccessWithIntermediateTest.java b/src/test/java/com/android/tools/r8/resolution/access/NestInvokeSpecialMethodAccessWithIntermediateTest.java
index b291f91..ce6f4d7 100644
--- a/src/test/java/com/android/tools/r8/resolution/access/NestInvokeSpecialMethodAccessWithIntermediateTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/access/NestInvokeSpecialMethodAccessWithIntermediateTest.java
@@ -11,6 +11,7 @@
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestRunResult;
+import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexClassAndMethod;
@@ -19,7 +20,6 @@
 import com.android.tools.r8.graph.ResolutionResult;
 import com.android.tools.r8.graph.ResolutionResult.IllegalAccessOrNoSuchMethodResult;
 import com.android.tools.r8.references.Reference;
-import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.transformers.ClassFileTransformer;
 import com.android.tools.r8.utils.BooleanUtils;
 import com.android.tools.r8.utils.DescriptorUtils;
@@ -107,16 +107,16 @@
     Class<?> declaredClass = symbolicReferenceIsDefiningType ? definingClass : B.class;
     Class<?> callerClass = C.class;
 
-    AppView<AppInfoWithLiveness> appView = getAppView();
-    AppInfoWithLiveness appInfo = appView.appInfo();
+    AppView<AppInfoWithClassHierarchy> appView = getAppView();
+    AppInfoWithClassHierarchy appInfo = appView.appInfo();
 
-    DexClass definingClassDefinition = getDexProgramClass(definingClass, appInfo);
-    DexClass declaredClassDefinition = getDexProgramClass(declaredClass, appInfo);
-    DexProgramClass callerClassDefinition = getDexProgramClass(callerClass, appInfo);
+    DexClass definingClassDefinition = getDexProgramClass(definingClass, appView);
+    DexClass declaredClassDefinition = getDexProgramClass(declaredClass, appView);
+    DexProgramClass callerClassDefinition = getDexProgramClass(callerClass, appView);
 
-    DexMethod method = getTargetMethodSignature(declaredClass, appInfo);
+    DexMethod method = getTargetMethodSignature(declaredClass, appView);
 
-    assertCallingClassCallsTarget(callerClass, appInfo, method);
+    assertCallingClassCallsTarget(callerClass, appView, method);
 
     // Resolve the method from the point of the declared holder.
     assertEquals(method.holder, declaredClassDefinition.type);
@@ -153,27 +153,26 @@
   }
 
   private void assertCallingClassCallsTarget(
-      Class<?> callerClass, AppInfoWithLiveness appInfo, DexMethod target) {
-    CodeInspector inspector = new CodeInspector(appInfo.app());
+      Class<?> callerClass, AppView<?> appView, DexMethod target) {
+    CodeInspector inspector = new CodeInspector(appView.appInfo().app());
     MethodSubject foo = inspector.clazz(callerClass).uniqueMethodWithName("foo");
     assertTrue(
         foo.streamInstructions().anyMatch(i -> i.isInvokeSpecial() && i.getMethod() == target));
   }
 
-  private DexMethod getTargetMethodSignature(Class<?> declaredClass, AppInfoWithLiveness appInfo) {
+  private DexMethod getTargetMethodSignature(Class<?> declaredClass, AppView<?> appView) {
     return buildMethod(
         Reference.method(Reference.classFromClass(declaredClass), "bar", ImmutableList.of(), null),
-        appInfo.dexItemFactory());
+        appView.dexItemFactory());
   }
 
-  private DexProgramClass getDexProgramClass(Class<?> clazz, AppInfoWithLiveness appInfo) {
-    return appInfo.definitionFor(buildType(clazz, appInfo.dexItemFactory())).asProgramClass();
+  private DexProgramClass getDexProgramClass(Class<?> clazz, AppView<?> appView) {
+    return appView.definitionFor(buildType(clazz, appView.dexItemFactory())).asProgramClass();
   }
 
-  private AppView<AppInfoWithLiveness> getAppView() throws Exception {
-    return computeAppViewWithLiveness(
-        buildClasses(getClasses()).addClassProgramData(getTransformedClasses()).build(),
-        Main.class);
+  private AppView<AppInfoWithClassHierarchy> getAppView() throws Exception {
+    return computeAppViewWithClassHierachy(
+        buildClasses(getClasses()).addClassProgramData(getTransformedClasses()).build());
   }
 
   @Test
diff --git a/src/test/java/com/android/tools/r8/resolution/interfacetargets/DefaultMethodAsOverrideWithLambdaTest.java b/src/test/java/com/android/tools/r8/resolution/interfacetargets/DefaultMethodAsOverrideWithLambdaTest.java
index 3215f17..8f800f6 100644
--- a/src/test/java/com/android/tools/r8/resolution/interfacetargets/DefaultMethodAsOverrideWithLambdaTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/interfacetargets/DefaultMethodAsOverrideWithLambdaTest.java
@@ -93,7 +93,6 @@
         .assertSuccessWithOutputLines(EXPECTED);
   }
 
-  @FunctionalInterface
   @NoVerticalClassMerging
   public interface I {
     void foo();
diff --git a/src/test/java/com/android/tools/r8/resolution/interfacetargets/DefaultMethodLambdaTest.java b/src/test/java/com/android/tools/r8/resolution/interfacetargets/DefaultMethodLambdaTest.java
index 77fa9d8..169c255 100644
--- a/src/test/java/com/android/tools/r8/resolution/interfacetargets/DefaultMethodLambdaTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/interfacetargets/DefaultMethodLambdaTest.java
@@ -89,7 +89,6 @@
         .assertSuccessWithOutputLines(EXPECTED);
   }
 
-  @FunctionalInterface
   @NoVerticalClassMerging
   public interface I {
     void foo();
diff --git a/src/test/java/com/android/tools/r8/resolution/interfacetargets/DefaultWithoutTopTest.java b/src/test/java/com/android/tools/r8/resolution/interfacetargets/DefaultWithoutTopTest.java
index 70a350f..16c1401 100644
--- a/src/test/java/com/android/tools/r8/resolution/interfacetargets/DefaultWithoutTopTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/interfacetargets/DefaultWithoutTopTest.java
@@ -86,6 +86,9 @@
         .addProgramClasses(I.class, J.class, Main.class)
         .addProgramClassFileData(setAImplementsIAndJ())
         .addKeepMainRule(Main.class)
+        .enableInliningAnnotations()
+        .enableNeverClassInliningAnnotations()
+        .enableNoVerticalClassMergingAnnotations()
         .setMinApi(parameters.getApiLevel())
         .run(parameters.getRuntime(), Main.class)
         .assertSuccessWithOutputLines(EXPECTED);
@@ -131,6 +134,9 @@
         .addProgramClasses(I.class, J.class, K.class, Main.class)
         .addProgramClassFileData(setAimplementsIandK())
         .addKeepMainRule(Main.class)
+        .enableInliningAnnotations()
+        .enableNeverClassInliningAnnotations()
+        .enableNoVerticalClassMergingAnnotations()
         .setMinApi(parameters.getApiLevel())
         .run(parameters.getRuntime(), Main.class)
         .assertSuccessWithOutputLines(EXPECTED);
diff --git a/src/test/java/com/android/tools/r8/resolution/interfacetargets/LambdaMultipleInterfacesTest.java b/src/test/java/com/android/tools/r8/resolution/interfacetargets/LambdaMultipleInterfacesTest.java
index 5422f9b..958e785 100644
--- a/src/test/java/com/android/tools/r8/resolution/interfacetargets/LambdaMultipleInterfacesTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/interfacetargets/LambdaMultipleInterfacesTest.java
@@ -90,7 +90,6 @@
         .assertSuccessWithOutputLines(EXPECTED);
   }
 
-  @FunctionalInterface
   @NoVerticalClassMerging
   public interface I {
     void foo();
diff --git a/src/test/java/com/android/tools/r8/resolution/virtualtargets/DefaultWithoutTopTest.java b/src/test/java/com/android/tools/r8/resolution/virtualtargets/DefaultWithoutTopTest.java
index 0daaa44..1e23ae3 100644
--- a/src/test/java/com/android/tools/r8/resolution/virtualtargets/DefaultWithoutTopTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/virtualtargets/DefaultWithoutTopTest.java
@@ -86,6 +86,9 @@
         .addProgramClasses(I.class, J.class, Main.class)
         .addProgramClassFileData(setAImplementsIAndJ())
         .addKeepMainRule(Main.class)
+        .enableInliningAnnotations()
+        .enableNeverClassInliningAnnotations()
+        .enableNoVerticalClassMergingAnnotations()
         .setMinApi(parameters.getApiLevel())
         .run(parameters.getRuntime(), Main.class)
         .assertSuccessWithOutputLines(EXPECTED);
diff --git a/src/test/java/com/android/tools/r8/resolution/virtualtargets/KeptTargetsIncompleteLookupTest.java b/src/test/java/com/android/tools/r8/resolution/virtualtargets/KeptTargetsIncompleteLookupTest.java
index 3566d3a..bc35d2b 100644
--- a/src/test/java/com/android/tools/r8/resolution/virtualtargets/KeptTargetsIncompleteLookupTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/virtualtargets/KeptTargetsIncompleteLookupTest.java
@@ -232,7 +232,7 @@
     // ----- Program -----
     // B extends A { } <-- initial
     AppView<AppInfoWithClassHierarchy> appView =
-        computeAppViewWithSubtyping(
+        computeAppViewWithClassHierachy(
             buildClasses(Collections.singletonList(B.class), Arrays.asList(A.class, I.class))
                 .build());
     AppInfoWithClassHierarchy appInfo = appView.appInfo();
diff --git a/src/test/java/com/android/tools/r8/resolution/virtualtargets/PackagePrivateFinalOverrideTest.java b/src/test/java/com/android/tools/r8/resolution/virtualtargets/PackagePrivateFinalOverrideTest.java
index e5904db..00f9b0b 100644
--- a/src/test/java/com/android/tools/r8/resolution/virtualtargets/PackagePrivateFinalOverrideTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/virtualtargets/PackagePrivateFinalOverrideTest.java
@@ -11,8 +11,6 @@
 import static org.junit.Assume.assumeTrue;
 
 import com.android.tools.r8.CompilationFailedException;
-import com.android.tools.r8.NeverClassInline;
-import com.android.tools.r8.NeverInline;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
@@ -101,6 +99,7 @@
     testForR8(parameters.getBackend())
         .addProgramClasses(MyViewModel.class, Main.class, ViewModel.class, ViewModelRunner.class)
         .addKeepMainRule(Main.class)
+        .enableInliningAnnotations()
         .setMinApi(parameters.getApiLevel())
         .run(parameters.getRuntime(), Main.class)
         .assertSuccessWithOutputLines(EXPECTED);
@@ -146,6 +145,7 @@
         .addProgramClasses(MyViewModel.class, ViewModel.class, ViewModelRunner.class)
         .addProgramClassFileData(getModifiedMainWithIllegalInvokeToViewModelClear())
         .addKeepMainRule(Main.class)
+        .enableInliningAnnotations()
         .setMinApi(parameters.getApiLevel())
         .run(parameters.getRuntime(), Main.class)
         .assertFailureWithErrorThatThrows(IllegalAccessError.class);
@@ -198,6 +198,7 @@
         .addProgramClasses(MyViewModel.class, ViewModel.class, Main.class)
         .addProgramClassFileData(getModifiedViewModelRunnerWithDirectMyViewModelTarget())
         .addKeepMainRule(Main.class)
+        .enableInliningAnnotations()
         .setMinApi(parameters.getApiLevel())
         .run(parameters.getRuntime(), Main.class)
         .assertSuccessWithOutputLines(AMBIGUOUS_EXPECTED_OUTPUT);
@@ -238,10 +239,8 @@
         .transform();
   }
 
-  @NeverClassInline
   public static class MyViewModel extends ViewModel {
 
-    @NeverInline
     public void clear() {
       System.out.println("MyViewModel.clear()");
     }
diff --git a/src/test/java/com/android/tools/r8/retrace/InlineWithoutNullCheckTest.java b/src/test/java/com/android/tools/r8/retrace/InlineWithoutNullCheckTest.java
index 3cabc0e..6973f6b 100644
--- a/src/test/java/com/android/tools/r8/retrace/InlineWithoutNullCheckTest.java
+++ b/src/test/java/com/android/tools/r8/retrace/InlineWithoutNullCheckTest.java
@@ -12,7 +12,6 @@
 
 import com.android.tools.r8.AlwaysInline;
 import com.android.tools.r8.NeverInline;
-import com.android.tools.r8.NeverPropagateValue;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
@@ -110,6 +109,7 @@
     testForR8(parameters.getBackend())
         .addInnerClasses(InlineWithoutNullCheckTest.class)
         .addKeepMainRule(TestClassForInlineMethod.class)
+        .enableAlwaysInliningAnnotations()
         .enableInliningAnnotations()
         .addKeepAttributes(ProguardKeepAttributes.SOURCE_FILE)
         .setMinApi(parameters.getApiLevel())
@@ -137,6 +137,7 @@
     testForR8(parameters.getBackend())
         .addInnerClasses(InlineWithoutNullCheckTest.class)
         .addKeepMainRule(TestClassForInlineField.class)
+        .enableAlwaysInliningAnnotations()
         .enableInliningAnnotations()
         .addKeepAttributes(ProguardKeepAttributes.SOURCE_FILE)
         .setMinApi(parameters.getApiLevel())
@@ -165,6 +166,7 @@
     testForR8(parameters.getBackend())
         .addInnerClasses(InlineWithoutNullCheckTest.class)
         .addKeepMainRule(TestClassForInlineStaticField.class)
+        .enableAlwaysInliningAnnotations()
         .enableInliningAnnotations()
         .addKeepAttributes(ProguardKeepAttributes.SOURCE_FILE)
         .setMinApi(parameters.getApiLevel())
@@ -232,7 +234,7 @@
   }
 
   static class A {
-    @NeverPropagateValue Result result;
+    Result result;
 
     A(Result result) {
       this.result = result;
@@ -260,8 +262,8 @@
   }
 
   static class Result {
-    @NeverPropagateValue int x = 1;
-    @NeverPropagateValue static int y = 1;
+    int x = 1;
+    static int y = 1;
 
     @AlwaysInline
     int methodWhichAccessInstanceMethod() {
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 274b990..94e1e1a 100644
--- a/src/test/java/com/android/tools/r8/retrace/KotlinInlineFunctionInSameFileRetraceTests.java
+++ b/src/test/java/com/android/tools/r8/retrace/KotlinInlineFunctionInSameFileRetraceTests.java
@@ -91,6 +91,7 @@
     testForR8(parameters.getBackend())
         .addProgramFiles(compilationResults.getForConfiguration(kotlinc, targetVersion))
         .addProgramFiles(ToolHelper.getKotlinStdlibJar(kotlinc))
+        .addDontWarnJetBrainsNotNullAnnotation()
         .addKeepAttributes("SourceFile", "LineNumberTable")
         .setMode(CompilationMode.RELEASE)
         .addKeepMainRule(MAIN)
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 d22a979..3827c26 100644
--- a/src/test/java/com/android/tools/r8/retrace/KotlinInlineFunctionRetraceTest.java
+++ b/src/test/java/com/android/tools/r8/retrace/KotlinInlineFunctionRetraceTest.java
@@ -109,6 +109,7 @@
         .allowDiagnosticWarningMessages()
         .setMode(CompilationMode.RELEASE)
         .addKeepMainRule(main)
+        .addDontWarnJetBrainsNotNullAnnotation()
         .setMinApi(parameters.getApiLevel())
         .compile()
         .assertAllWarningMessagesMatch(equalTo("Resource 'META-INF/MANIFEST.MF' already exists."))
@@ -139,6 +140,7 @@
         .allowDiagnosticWarningMessages()
         .setMode(CompilationMode.RELEASE)
         .addKeepMainRule(main)
+        .addDontWarnJetBrainsNotNullAnnotation()
         .setMinApi(parameters.getApiLevel())
         .compile()
         .assertAllWarningMessagesMatch(equalTo("Resource 'META-INF/MANIFEST.MF' already exists."))
@@ -172,6 +174,7 @@
         .allowDiagnosticWarningMessages()
         .setMode(CompilationMode.RELEASE)
         .addKeepMainRule(main)
+        .addDontWarnJetBrainsNotNullAnnotation()
         .setMinApi(parameters.getApiLevel())
         .compile()
         .assertAllWarningMessagesMatch(equalTo("Resource 'META-INF/MANIFEST.MF' already exists."))
@@ -204,6 +207,7 @@
         .allowDiagnosticWarningMessages()
         .setMode(CompilationMode.RELEASE)
         .addKeepMainRule(main)
+        .addDontWarnJetBrainsNotNullAnnotation()
         .setMinApi(parameters.getApiLevel())
         .compile()
         .assertAllWarningMessagesMatch(equalTo("Resource 'META-INF/MANIFEST.MF' already exists."))
diff --git a/src/test/java/com/android/tools/r8/rewrite/ServiceLoaderMultipleCallsTest.java b/src/test/java/com/android/tools/r8/rewrite/ServiceLoaderMultipleCallsTest.java
index 688a96f..fd1a05a 100644
--- a/src/test/java/com/android/tools/r8/rewrite/ServiceLoaderMultipleCallsTest.java
+++ b/src/test/java/com/android/tools/r8/rewrite/ServiceLoaderMultipleCallsTest.java
@@ -4,11 +4,10 @@
 
 package com.android.tools.r8.rewrite;
 
-import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
 import static junit.framework.TestCase.assertEquals;
 import static junit.framework.TestCase.assertNull;
 import static junit.framework.TestCase.assertTrue;
-import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertFalse;
 
 import com.android.tools.r8.CompilationFailedException;
 import com.android.tools.r8.DataEntryResource;
@@ -20,6 +19,7 @@
 import com.android.tools.r8.utils.StringUtils;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.FoundClassSubject;
 import com.android.tools.r8.utils.codeinspector.InstructionSubject;
 import java.io.IOException;
 import java.nio.file.Path;
@@ -109,11 +109,18 @@
             inspector -> {
               // Check that we have actually rewritten the calls to ServiceLoader.load.
               assertEquals(0, getServiceLoaderLoads(inspector, MainRunner.class));
-              // Check that the synthesize service loader class holds two methods, one for each
-              // context.
-              ClassSubject serviceLoaderMethods = inspector.clazz("$$ServiceLoaderMethods");
-              assertThat(serviceLoaderMethods, isPresent());
-              assertEquals(2, serviceLoaderMethods.allMethods().size());
+              // Check the synthesize service loader method is a single shared method.
+              // Due to minification we just check there is only a single synthetic class with a
+              // single static method.
+              boolean found = false;
+              for (FoundClassSubject clazz : inspector.allClasses()) {
+                if (clazz.isSynthetic()) {
+                  assertFalse(found);
+                  found = true;
+                  assertEquals(1, clazz.allMethods().size());
+                  clazz.forAllMethods(m -> assertTrue(m.isStatic()));
+                }
+              }
             });
 
     // Check that we have removed the service configuration from META-INF/services.
diff --git a/src/test/java/com/android/tools/r8/rewrite/enums/EnumInvalidValuesLengthTest.java b/src/test/java/com/android/tools/r8/rewrite/enums/EnumInvalidValuesLengthTest.java
index ead99d1..2636944 100644
--- a/src/test/java/com/android/tools/r8/rewrite/enums/EnumInvalidValuesLengthTest.java
+++ b/src/test/java/com/android/tools/r8/rewrite/enums/EnumInvalidValuesLengthTest.java
@@ -36,6 +36,7 @@
         .addKeepMainRule(EnumInvalidValuesLengthTest.Main.class)
         .addProgramClasses(EnumInvalidValuesLengthTest.Main.class)
         .addProgramClassFileData(transformValues(MyEnum.class))
+        .enableNeverClassInliningAnnotations()
         .setMinApi(parameters.getApiLevel())
         .compile()
         .run(parameters.getRuntime(), EnumInvalidValuesLengthTest.Main.class)
diff --git a/src/test/java/com/android/tools/r8/rewrite/enums/EnumOptimizationTest.java b/src/test/java/com/android/tools/r8/rewrite/enums/EnumOptimizationTest.java
index ae4947a..7733b23 100644
--- a/src/test/java/com/android/tools/r8/rewrite/enums/EnumOptimizationTest.java
+++ b/src/test/java/com/android/tools/r8/rewrite/enums/EnumOptimizationTest.java
@@ -57,6 +57,7 @@
     testForR8(parameters.getBackend())
         .addProgramClassesAndInnerClasses(Ordinals.class)
         .addKeepMainRule(Ordinals.class)
+        .enableForceInliningAnnotations()
         .enableInliningAnnotations()
         .enableSideEffectAnnotations()
         .addOptionsModification(this::configure)
@@ -105,6 +106,7 @@
     testForR8(parameters.getBackend())
         .addProgramClassesAndInnerClasses(Names.class)
         .addKeepMainRule(Names.class)
+        .enableForceInliningAnnotations()
         .enableInliningAnnotations()
         .enableSideEffectAnnotations()
         .addOptionsModification(this::configure)
@@ -150,6 +152,7 @@
     testForR8(parameters.getBackend())
         .addProgramClassesAndInnerClasses(ToStrings.class)
         .addKeepMainRule(ToStrings.class)
+        .enableForceInliningAnnotations()
         .enableInliningAnnotations()
         .enableSideEffectAnnotations()
         .addOptionsModification(this::configure)
diff --git a/src/test/java/com/android/tools/r8/rewrite/enums/EnumSideEffect.java b/src/test/java/com/android/tools/r8/rewrite/enums/EnumSideEffect.java
index a0e59b3..2d1b018 100644
--- a/src/test/java/com/android/tools/r8/rewrite/enums/EnumSideEffect.java
+++ b/src/test/java/com/android/tools/r8/rewrite/enums/EnumSideEffect.java
@@ -39,6 +39,7 @@
         .addKeepMainRule(Main.class)
         .enableInliningAnnotations()
         .addInnerClasses(EnumSideEffect.class)
+        .enableNeverClassInliningAnnotations()
         .setMinApi(parameters.getApiLevel())
         .compile()
         .run(parameters.getRuntime(), Main.class)
diff --git a/src/test/java/com/android/tools/r8/rewrite/serviceloaders/MissingServiceClassTest.java b/src/test/java/com/android/tools/r8/rewrite/serviceloaders/MissingServiceClassTest.java
index ee99ea1..5bcfdb8 100644
--- a/src/test/java/com/android/tools/r8/rewrite/serviceloaders/MissingServiceClassTest.java
+++ b/src/test/java/com/android/tools/r8/rewrite/serviceloaders/MissingServiceClassTest.java
@@ -5,7 +5,6 @@
 package com.android.tools.r8.rewrite.serviceloaders;
 
 import static com.android.tools.r8.DiagnosticsMatcher.diagnosticMessage;
-import static org.hamcrest.CoreMatchers.anyOf;
 import static org.hamcrest.CoreMatchers.containsString;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
@@ -55,10 +54,11 @@
                 AppServices.SERVICE_DIRECTORY_NAME + Service.class.getTypeName(),
                 Origin.unknown()))
         .addOptionsModification(o -> o.dataResourceConsumer = dataResourceConsumer)
+        .addDontWarn(ServiceImpl.class)
         .allowDiagnosticWarningMessages()
         .setMinApi(parameters.getApiLevel())
         .compile()
-        .inspectDiagnosticMessages(this::inspectDiagnosticMessages)
+        .inspectDiagnosticMessages(this::inspectDiagnosticMessagesWithDontWarn)
         .addRunClasspathClasses(Service.class, ServiceImpl.class)
         .run(parameters.getRuntime(), TestClass.class)
         .assertSuccessWithEmptyOutput();
@@ -80,26 +80,39 @@
                 AppServices.SERVICE_DIRECTORY_NAME + Service.class.getTypeName(),
                 Origin.unknown()))
         .run()
-        .inspectDiagnosticMessages(this::inspectDiagnosticMessages);
+        .inspectDiagnosticMessages(this::inspectDiagnosticMessagesWithoutDontWarn);
   }
 
-  private void inspectDiagnosticMessages(TestDiagnosticMessages inspector) {
-    inspector.assertWarningsCount(2);
-    inspector.assertAllWarningsMatch(
-        diagnosticMessage(
-            anyOf(
+  private void inspectDiagnosticMessagesWithDontWarn(TestDiagnosticMessages diagnostics) {
+    diagnostics
+        .assertOnlyWarnings()
+        .assertWarningsMatch(
+            diagnosticMessage(
                 containsString(
                     "Unexpected reference to missing service class: "
                         + AppServices.SERVICE_DIRECTORY_NAME
                         + Service.class.getTypeName()
-                        + "."),
+                        + ".")));
+  }
+
+  private void inspectDiagnosticMessagesWithoutDontWarn(TestDiagnosticMessages inspector) {
+    inspector
+        .assertOnlyWarnings()
+        .assertWarningsMatch(
+            diagnosticMessage(
+                containsString(
+                    "Unexpected reference to missing service class: "
+                        + AppServices.SERVICE_DIRECTORY_NAME
+                        + Service.class.getTypeName()
+                        + ".")),
+            diagnosticMessage(
                 containsString(
                     "Unexpected reference to missing service implementation class in "
                         + AppServices.SERVICE_DIRECTORY_NAME
                         + Service.class.getTypeName()
                         + ": "
                         + ServiceImpl.class.getTypeName()
-                        + "."))));
+                        + ".")));
   }
 
   private void inspectResource(List<String> contents) {
diff --git a/src/test/java/com/android/tools/r8/rewrite/serviceloaders/MissingServiceClassWithFeatureTest.java b/src/test/java/com/android/tools/r8/rewrite/serviceloaders/MissingServiceClassWithFeatureTest.java
index f36c122..be5dfb6 100644
--- a/src/test/java/com/android/tools/r8/rewrite/serviceloaders/MissingServiceClassWithFeatureTest.java
+++ b/src/test/java/com/android/tools/r8/rewrite/serviceloaders/MissingServiceClassWithFeatureTest.java
@@ -38,7 +38,7 @@
         .addProgramClasses(TestClass.class, ServiceImpl.class)
         .addKeepMainRule(TestClass.class)
         .addKeepClassAndMembersRules(FeatureClass.class)
-        .addKeepRules("-dontwarn " + Service.class.getTypeName())
+        .addDontWarn(Service.class.getTypeName())
         .addDataEntryResources(
             DataEntryResource.fromBytes(
                 StringUtils.lines("java.lang.Object").getBytes(),
diff --git a/src/test/java/com/android/tools/r8/rewrite/serviceloaders/MissingServiceImplementationClassTest.java b/src/test/java/com/android/tools/r8/rewrite/serviceloaders/MissingServiceImplementationClassTest.java
index 9039c50..ca17780 100644
--- a/src/test/java/com/android/tools/r8/rewrite/serviceloaders/MissingServiceImplementationClassTest.java
+++ b/src/test/java/com/android/tools/r8/rewrite/serviceloaders/MissingServiceImplementationClassTest.java
@@ -4,15 +4,12 @@
 
 package com.android.tools.r8.rewrite.serviceloaders;
 
-import static com.android.tools.r8.DiagnosticsMatcher.diagnosticMessage;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
-import static org.hamcrest.CoreMatchers.containsString;
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
 
 import com.android.tools.r8.DataEntryResource;
-import com.android.tools.r8.NeverInline;
 import com.android.tools.r8.R8TestCompileResult;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
@@ -59,6 +56,7 @@
     R8TestCompileResult compileResult =
         testForR8(parameters.getBackend())
             .addProgramClasses(TestClass.class, Service.class)
+            .addDontWarn(ServiceImpl.class)
             .addKeepMainRule(TestClass.class)
             .addKeepClassAndMembersRulesWithAllowObfuscation(Service.class)
             .addDataEntryResources(
@@ -72,22 +70,8 @@
                       new DataResourceConsumerForTesting(options.dataResourceConsumer));
                   options.dataResourceConsumer = dataResourceConsumer.get();
                 })
-            .allowDiagnosticWarningMessages()
             .setMinApi(parameters.getApiLevel())
-            .compile()
-            .inspectDiagnosticMessages(
-                inspector -> {
-                  inspector.assertWarningsCount(1);
-                  inspector.assertAllWarningsMatch(
-                      diagnosticMessage(
-                          containsString(
-                              "Unexpected reference to missing service implementation class in "
-                                  + AppServices.SERVICE_DIRECTORY_NAME
-                                  + Service.class.getTypeName()
-                                  + ": "
-                                  + ServiceImpl.class.getTypeName()
-                                  + ".")));
-                });
+            .compile();
 
     CodeInspector inspector = compileResult.inspector();
     ClassSubject serviceClassSubject = inspector.clazz(Service.class);
@@ -171,7 +155,6 @@
   public static class ServiceImpl implements Service {
 
     @Override
-    @NeverInline
     public void greet() {
       System.out.println("Hello world!");
     }
diff --git a/src/test/java/com/android/tools/r8/rewrite/serviceloaders/MissingServiceImplementationClassWithFeatureTest.java b/src/test/java/com/android/tools/r8/rewrite/serviceloaders/MissingServiceImplementationClassWithFeatureTest.java
index b3f120c..a3aea84 100644
--- a/src/test/java/com/android/tools/r8/rewrite/serviceloaders/MissingServiceImplementationClassWithFeatureTest.java
+++ b/src/test/java/com/android/tools/r8/rewrite/serviceloaders/MissingServiceImplementationClassWithFeatureTest.java
@@ -4,8 +4,6 @@
 
 package com.android.tools.r8.rewrite.serviceloaders;
 
-import static com.android.tools.r8.DiagnosticsMatcher.diagnosticMessage;
-import static org.hamcrest.CoreMatchers.containsString;
 
 import com.android.tools.r8.DataEntryResource;
 import com.android.tools.r8.TestBase;
@@ -40,6 +38,7 @@
   public void testR8() throws Exception {
     testForR8(parameters.getBackend())
         .addProgramClasses(TestClass.class, Service.class)
+        .addDontWarn("MissingClass")
         .addKeepMainRule(TestClass.class)
         .addKeepClassAndMembersRules(FeatureClass.class)
         .addDataEntryResources(
@@ -48,20 +47,8 @@
                 AppServices.SERVICE_DIRECTORY_NAME + Service.class.getTypeName(),
                 Origin.unknown()))
         .addFeatureSplit(FeatureClass.class)
-        .allowDiagnosticWarningMessages()
         .setMinApi(parameters.getApiLevel())
-        .compile()
-        .inspectDiagnosticMessages(
-            inspector -> {
-              inspector.assertWarningsCount(1);
-              inspector.assertAllWarningsMatch(
-                  diagnosticMessage(
-                      containsString(
-                          "Unexpected reference to missing service implementation class in "
-                              + AppServices.SERVICE_DIRECTORY_NAME
-                              + Service.class.getTypeName()
-                              + ": MissingClass.")));
-            });
+        .compile();
   }
 
   static class TestClass {
diff --git a/src/test/java/com/android/tools/r8/rewrite/serviceloaders/ServiceWithoutResourceFileAtCompileTimeTest.java b/src/test/java/com/android/tools/r8/rewrite/serviceloaders/ServiceWithoutResourceFileAtCompileTimeTest.java
index 1ea55b8..9f5eada 100644
--- a/src/test/java/com/android/tools/r8/rewrite/serviceloaders/ServiceWithoutResourceFileAtCompileTimeTest.java
+++ b/src/test/java/com/android/tools/r8/rewrite/serviceloaders/ServiceWithoutResourceFileAtCompileTimeTest.java
@@ -69,6 +69,7 @@
         .addClasspathClasses(Service.class)
         .addKeepAllClassesRule()
         .addApplyMapping(compileResult.getProguardMap())
+        .enableInliningAnnotations()
         .setMinApi(parameters.getApiLevel())
         .compile()
         .addRunClasspathFiles(
diff --git a/src/test/java/com/android/tools/r8/rewrite/switches/MaxIntSwitchTest.java b/src/test/java/com/android/tools/r8/rewrite/switches/MaxIntSwitchTest.java
new file mode 100644
index 0000000..0f3f2cc
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/rewrite/switches/MaxIntSwitchTest.java
@@ -0,0 +1,90 @@
+// 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.rewrite.switches;
+
+import com.android.tools.r8.CompilationMode;
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.NeverPropagateValue;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestRunResult;
+import com.android.tools.r8.ToolHelper.DexVm.Version;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class MaxIntSwitchTest extends TestBase {
+
+  private final TestParameters parameters;
+  private final CompilationMode mode;
+
+  @Parameterized.Parameters(name = "{0}, mode = {1}")
+  public static List<Object[]> data() {
+    return buildParameters(
+        getTestParameters().withDexRuntimes().withAllApiLevels().build(), CompilationMode.values());
+  }
+
+  // See b/177790310 for details.
+  public MaxIntSwitchTest(TestParameters parameters, CompilationMode mode) {
+    this.parameters = parameters;
+    this.mode = mode;
+  }
+
+  private void checkResult(TestRunResult<?> result) {
+    if (parameters.getDexRuntimeVersion().isOlderThanOrEqual(Version.V4_0_4)) {
+      result.assertFailureWithErrorThatThrows(AssertionError.class);
+    } else {
+      result.assertSuccessWithOutputLines("good");
+    }
+  }
+
+  @Test
+  public void testD8() throws Exception {
+    testForD8(parameters.getBackend())
+        .addInnerClasses(this.getClass())
+        .setMinApi(parameters.getApiLevel())
+        .setMode(mode)
+        .run(parameters.getRuntime(), TestClass.class)
+        .apply(this::checkResult);
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(this.getClass())
+        .addKeepMainRule(TestClass.class)
+        .enableInliningAnnotations()
+        .enableMemberValuePropagationAnnotations()
+        .setMinApi(parameters.getApiLevel())
+        .setMode(mode)
+        .run(parameters.getRuntime(), TestClass.class)
+        .apply(this::checkResult);
+  }
+
+  static class TestClass {
+    @NeverInline
+    @NeverPropagateValue
+    public static void f(int i) {
+      switch (i) {
+        case 0x7ffffffd:
+          System.out.println("a");
+          break;
+        case 0x7ffffffe:
+          System.out.println("b");
+          break;
+        case 0x7fffffff:
+          System.out.println("good");
+          break;
+        default:
+          throw new AssertionError();
+      }
+    }
+
+    public static void main(String[] args) {
+      f(0x7fffffff);
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/KeepAnnotatedMemberTest.java b/src/test/java/com/android/tools/r8/shaking/KeepAnnotatedMemberTest.java
index b5aba75..993a658 100644
--- a/src/test/java/com/android/tools/r8/shaking/KeepAnnotatedMemberTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/KeepAnnotatedMemberTest.java
@@ -18,6 +18,7 @@
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.FoundClassSubject;
 import com.android.tools.r8.utils.codeinspector.FoundMethodSubject;
 import com.android.tools.r8.utils.codeinspector.MethodSubject;
 import com.android.tools.r8.utils.graphinspector.GraphInspector;
@@ -73,6 +74,9 @@
     testForR8(Backend.CF)
         .addProgramFiles(R8_JAR)
         .addKeepRules("-keep class * { @" + PRESENT_ANNOTATION + " *; }")
+        .addDontWarnGoogle()
+        .addDontWarnJavax()
+        .addDontWarn("org.codehaus.mojo.animal_sniffer.IgnoreJRERequirement")
         .compileWithExpectedDiagnostics(
             diagnostics ->
                 diagnostics.assertErrorsMatch(diagnosticException(AssertionError.class)));
@@ -85,6 +89,9 @@
         .addProgramFiles(R8_JAR)
         .addKeepRules(
             "-keep class *", "-keepclassmembers class * { @" + PRESENT_ANNOTATION + " *; }")
+        .addDontWarnGoogle()
+        .addDontWarnJavax()
+        .addDontWarn("org.codehaus.mojo.animal_sniffer.IgnoreJRERequirement")
         .compile();
   }
 
@@ -103,6 +110,9 @@
     testForR8(Backend.CF)
         .addProgramFiles(R8_JAR)
         .addKeepRules("-keepclasseswithmembers class * { @" + PRESENT_ANNOTATION + " *** *(...); }")
+        .addDontWarnGoogle()
+        .addDontWarnJavax()
+        .addDontWarn("org.codehaus.mojo.animal_sniffer.IgnoreJRERequirement")
         .compile()
         .inspect(
             inspector -> {
@@ -192,6 +202,8 @@
             // TODO(b/132318609): Remove keep annotation once fixed.
             .addKeepClassRules(PRESENT_ANNOTATION)
             .addKeepRules("-keepclassmembers class * { @" + PRESENT_ANNOTATION + " *** *(...); }")
+            .addDontWarnGoogle()
+            .addDontWarnJavaxNullableAnnotation()
             .compile()
             .graphInspector();
 
@@ -208,6 +220,8 @@
                     + "-keepclassmembers class <1> { @"
                     + PRESENT_ANNOTATION
                     + " *** *(...); }")
+            .addDontWarnGoogle()
+            .addDontWarnJavaxNullableAnnotation()
             .compile()
             .graphInspector();
     assertRetainedClassesEqual(referenceInspector, ifThenKeepClassMembersInspector, false, false);
@@ -225,6 +239,8 @@
                     + "-keepclasseswithmembers class <1> { @"
                     + PRESENT_ANNOTATION
                     + " *** *(...); }")
+            .addDontWarnGoogle()
+            .addDontWarnJavaxNullableAnnotation()
             .compile()
             .graphInspector();
     assertRetainedClassesEqual(
@@ -245,6 +261,8 @@
                     + "-keep class <1> { @"
                     + PRESENT_ANNOTATION
                     + " *** <2>(...); }")
+            .addDontWarnGoogle()
+            .addDontWarnJavaxNullableAnnotation()
             .compile()
             .graphInspector();
     // TODO(b/159418523): Should the reference be equal to the result with the conditional rule?
@@ -259,12 +277,12 @@
     Set<String> referenceClasses =
         new TreeSet<>(
             referenceResult.codeInspector().allClasses().stream()
-                .map(c -> c.getOriginalName())
+                .map(FoundClassSubject::getOriginalName)
                 .collect(Collectors.toSet()));
 
     Set<String> conditionalClasses =
         conditionalResult.codeInspector().allClasses().stream()
-            .map(c -> c.getOriginalName())
+            .map(FoundClassSubject::getOriginalName)
             .collect(Collectors.toSet());
     {
       SetView<String> notInReference = Sets.difference(conditionalClasses, referenceClasses);
diff --git a/src/test/java/com/android/tools/r8/shaking/LibraryMethodOverrideInLambdaMarkingTest.java b/src/test/java/com/android/tools/r8/shaking/LibraryMethodOverrideInLambdaMarkingTest.java
index 7d68070..331eb12 100644
--- a/src/test/java/com/android/tools/r8/shaking/LibraryMethodOverrideInLambdaMarkingTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/LibraryMethodOverrideInLambdaMarkingTest.java
@@ -87,14 +87,12 @@
     }
   }
 
-  @FunctionalInterface
   @NoVerticalClassMerging
   interface I {
 
     Iterator<Object> iterator();
   }
 
-  @FunctionalInterface
   @NoVerticalClassMerging
   interface J extends Iterable<Object> {
 
diff --git a/src/test/java/com/android/tools/r8/shaking/MissingInterfaceTest.java b/src/test/java/com/android/tools/r8/shaking/MissingInterfaceTest.java
index 7254ee6..617676f 100644
--- a/src/test/java/com/android/tools/r8/shaking/MissingInterfaceTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/MissingInterfaceTest.java
@@ -35,11 +35,10 @@
   public void test_missingInterface() throws Exception {
     testForR8(parameters.getBackend())
         .addProgramClasses(TestClassForB112849320.class)
+        .addDontWarn(GoingToBeMissed.class)
         .setMinApi(parameters.getApiLevel())
         .addKeepMainRule(TestClassForB112849320.class)
         .addOptionsModification(options -> options.enableInlining = false)
-        .allowDiagnosticWarningMessages(
-            parameters.isCfRuntime() || !parameters.canUseDefaultAndStaticInterfaceMethods())
         .compile()
         .addRunClasspathFiles(
             buildOnDexRuntime(
diff --git a/src/test/java/com/android/tools/r8/shaking/PreserveDesugaredLambdaTest.java b/src/test/java/com/android/tools/r8/shaking/PreserveDesugaredLambdaTest.java
index cd153f2..74b7c8a 100644
--- a/src/test/java/com/android/tools/r8/shaking/PreserveDesugaredLambdaTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/PreserveDesugaredLambdaTest.java
@@ -13,7 +13,6 @@
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
-import com.android.tools.r8.utils.AndroidApiLevel;
 import java.io.IOException;
 import java.util.concurrent.ExecutionException;
 import org.junit.Test;
@@ -68,8 +67,7 @@
         .addClasspathClasses(Interface.class)
         .addLibraryFiles(TestBase.runtimeJar(parameters.getBackend()))
         .addKeepAllClassesRule()
-        .allowDiagnosticWarningMessages(
-            parameters.isDexRuntime() && parameters.getApiLevel().isLessThan(AndroidApiLevel.N))
+        .addDontWarn(A.class)
         .setMinApi(parameters.getApiLevel())
         .compile()
         .addRunClasspathFiles(libraryCompileResult.writeToZip())
@@ -79,8 +77,7 @@
                     codeInspector.allClasses().stream()
                         .anyMatch(
                             c -> {
-                              if (c.getOriginalName()
-                                  .contains("-$$Lambda$PreserveDesugaredLambdaTest$Main")) {
+                              if (c.isSynthesizedJavaLambdaClass()) {
                                 assertThat(c.uniqueMethodWithName("computeTheFoo"), isPresent());
                                 return true;
                               }
diff --git a/src/test/java/com/android/tools/r8/shaking/PrintUsageTest.java b/src/test/java/com/android/tools/r8/shaking/PrintUsageTest.java
index 9c88b7f..3d2e137 100644
--- a/src/test/java/com/android/tools/r8/shaking/PrintUsageTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/PrintUsageTest.java
@@ -7,8 +7,10 @@
 import static org.junit.Assert.assertTrue;
 
 import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.shaking.PrintUsageTest.PrintUsageInspector.ClassSubject;
+import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.ListUtils;
 import com.google.common.collect.ImmutableList;
 import java.io.File;
@@ -38,18 +40,18 @@
 
   private static final String PRINT_USAGE_FILE_SUFFIX = "-print-usage.txt";
 
-  private final Backend backend;
+  private final TestParameters parameters;
   private final String test;
   private final String programFile;
   private final List<String> keepRulesFiles;
   private final Consumer<PrintUsageInspector> inspection;
 
   public PrintUsageTest(
-      Backend backend,
+      TestParameters parameters,
       String test,
       List<String> keepRulesFiles,
       Consumer<PrintUsageInspector> inspection) {
-    this.backend = backend;
+    this.parameters = parameters;
     this.test = test;
     this.programFile = ToolHelper.EXAMPLES_BUILD_DIR + test + ".jar";
     this.keepRulesFiles = keepRulesFiles;
@@ -59,13 +61,19 @@
   @Before
   public void runR8andGetPrintUsage() throws Exception {
     Path out = temp.getRoot().toPath();
-    testForR8(backend)
+    testForR8(parameters.getBackend())
         .addProgramFiles(Paths.get(programFile))
         .addKeepRuleFiles(ListUtils.map(keepRulesFiles, Paths::get))
         .addKeepRules("-printusage " + out.resolve(test + PRINT_USAGE_FILE_SUFFIX))
+        .applyIf(
+            test.equals("shaking12")
+                && parameters.isDexRuntime()
+                && parameters.getApiLevel().isLessThan(AndroidApiLevel.K),
+            builder -> builder.addDontWarn(ReflectiveOperationException.class))
         // Disable inlining to make this test not depend on inlining decisions.
         .addOptionsModification(o -> o.enableInlining = false)
         .enableProguardTestOptions()
+        .setMinApi(parameters.getApiLevel())
         .compile();
   }
 
@@ -79,7 +87,7 @@
     }
   }
 
-  @Parameters(name = "test: {0} keep: {1}")
+  @Parameters(name = "{0}, test: {1} keep: {2}")
   public static Collection<Object[]> data() {
     List<String> tests = Arrays.asList(
         "shaking1", "shaking2", "shaking4", "shaking8", "shaking9", "shaking12");
@@ -93,7 +101,7 @@
     inspections.put("shaking12:keep-rules-printusage.txt", PrintUsageTest::inspectShaking12);
 
     List<Object[]> testCases = new ArrayList<>();
-    for (Backend backend : ToolHelper.getBackends()) {
+    for (TestParameters parameters : getTestParameters().withAllRuntimesAndApiLevels().build()) {
       Set<String> usedInspections = new HashSet<>();
       for (String test : tests) {
         File[] keepFiles = new File(ToolHelper.EXAMPLES_DIR + test)
@@ -103,8 +111,8 @@
           Consumer<PrintUsageInspector> inspection =
               getTestOptionalParameter(inspections, usedInspections, test, keepName);
           if (inspection != null) {
-              testCases.add(
-                  new Object[] {backend, test, ImmutableList.of(keepFile.getPath()), inspection});
+            testCases.add(
+                new Object[] {parameters, test, ImmutableList.of(keepFile.getPath()), inspection});
           }
         }
       }
diff --git a/src/test/java/com/android/tools/r8/shaking/ProguardConfigurationParserTest.java b/src/test/java/com/android/tools/r8/shaking/ProguardConfigurationParserTest.java
index c15fb48..b479f43 100644
--- a/src/test/java/com/android/tools/r8/shaking/ProguardConfigurationParserTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/ProguardConfigurationParserTest.java
@@ -22,6 +22,7 @@
 import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.ToolHelper.ProcessResult;
+import com.android.tools.r8.errors.dontwarn.DontWarnConfiguration;
 import com.android.tools.r8.graph.ClassAccessFlags;
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexType;
@@ -56,6 +57,7 @@
 import java.util.Optional;
 import java.util.function.BiConsumer;
 import java.util.function.Function;
+import java.util.function.Predicate;
 import java.util.function.Supplier;
 import java.util.regex.Matcher;
 import java.util.stream.Collectors;
@@ -268,7 +270,7 @@
   }
 
   private void testDontXXX(
-      String xxx, Function<ProguardConfiguration, ProguardClassFilter> pattern) {
+      String xxx, Function<ProguardConfiguration, Predicate<DexType>> matcherFactory) {
     DexItemFactory dexItemFactory = new DexItemFactory();
     ProguardConfigurationParser parser =
         new ProguardConfigurationParser(dexItemFactory, reporter);
@@ -276,19 +278,20 @@
     parser.parse(createConfigurationForTesting(ImmutableList.of(configuration)));
     verifyParserEndsCleanly();
     ProguardConfiguration config = parser.getConfig();
-    assertFalse(pattern.apply(config).matches(dexItemFactory.createType("Lboobaz;")));
-    assertTrue(pattern.apply(config).matches(dexItemFactory.createType("Lboobar;")));
-    assertFalse(pattern.apply(config).matches(dexItemFactory.createType("Lfoobar;")));
+    Predicate<DexType> matcher = matcherFactory.apply(config);
+    assertFalse(matcher.test(dexItemFactory.createType("Lboobaz;")));
+    assertTrue(matcher.test(dexItemFactory.createType("Lboobar;")));
+    assertFalse(matcher.test(dexItemFactory.createType("Lfoobar;")));
   }
 
   @Test
   public void testDontXXX() {
-    testDontXXX("warn", ProguardConfiguration::getDontWarnPatterns);
-    testDontXXX("note", ProguardConfiguration::getDontNotePatterns);
+    testDontXXX("warn", config -> DontWarnConfiguration.create(config)::matches);
+    testDontXXX("note", config -> config.getDontNotePatterns()::matches);
   }
 
   private void testDontXXXMultiple(
-      String xxx, Function<ProguardConfiguration, ProguardClassFilter> pattern) {
+      String xxx, Function<ProguardConfiguration, Predicate<DexType>> matcherFactory) {
     DexItemFactory dexItemFactory = new DexItemFactory();
     ProguardConfigurationParser parser =
         new ProguardConfigurationParser(dexItemFactory, reporter);
@@ -298,20 +301,21 @@
       parser.parse(createConfigurationForTesting(configuration));
       verifyParserEndsCleanly();
       ProguardConfiguration config = parser.getConfig();
-      assertTrue(pattern.apply(config).matches(dexItemFactory.createType("Lfoo/Bar;")));
-      assertTrue(pattern.apply(config).matches(dexItemFactory.createType("Lfoo/bar7Bar;")));
-      assertTrue(pattern.apply(config).matches(dexItemFactory.createType("Lbar/Foo;")));
+      Predicate<DexType> matcher = matcherFactory.apply(config);
+      assertTrue(matcher.test(dexItemFactory.createType("Lfoo/Bar;")));
+      assertTrue(matcher.test(dexItemFactory.createType("Lfoo/bar7Bar;")));
+      assertTrue(matcher.test(dexItemFactory.createType("Lbar/Foo;")));
     }
   }
 
   @Test
   public void testDontWarnMultiple() {
-    testDontXXXMultiple("warn", ProguardConfiguration::getDontWarnPatterns);
-    testDontXXXMultiple("note", ProguardConfiguration::getDontNotePatterns);
+    testDontXXXMultiple("warn", config -> DontWarnConfiguration.create(config)::matches);
+    testDontXXXMultiple("note", config -> config.getDontNotePatterns()::matches);
   }
 
   private void testDontXXXAllExplicitly(
-      String xxx, Function<ProguardConfiguration, ProguardClassFilter> pattern) {
+      String xxx, Function<ProguardConfiguration, Predicate<DexType>> matcherFactory) {
     DexItemFactory dexItemFactory = new DexItemFactory();
     ProguardConfigurationParser parser =
         new ProguardConfigurationParser(dexItemFactory, reporter);
@@ -319,19 +323,20 @@
     parser.parse(createConfigurationForTesting(ImmutableList.of(dontwarnAll)));
     verifyParserEndsCleanly();
     ProguardConfiguration config = parser.getConfig();
-    assertTrue(pattern.apply(config).matches(dexItemFactory.createType("Lboobaz;")));
-    assertTrue(pattern.apply(config).matches(dexItemFactory.createType("Lboobar;")));
-    assertTrue(pattern.apply(config).matches(dexItemFactory.createType("Lfoobar;")));
+    Predicate<DexType> matcher = matcherFactory.apply(config);
+    assertTrue(matcher.test(dexItemFactory.createType("Lboobaz;")));
+    assertTrue(matcher.test(dexItemFactory.createType("Lboobar;")));
+    assertTrue(matcher.test(dexItemFactory.createType("Lfoobar;")));
   }
 
   @Test
   public void testDontWarnAllExplicitly() {
-    testDontXXXAllExplicitly("warn", ProguardConfiguration::getDontWarnPatterns);
-    testDontXXXAllExplicitly("note", ProguardConfiguration::getDontNotePatterns);
+    testDontXXXAllExplicitly("warn", config -> DontWarnConfiguration.create(config)::matches);
+    testDontXXXAllExplicitly("note", config -> config.getDontNotePatterns()::matches);
   }
 
   private void testDontXXXAllImplicitly(
-      String xxx, Function<ProguardConfiguration, ProguardClassFilter> pattern) {
+      String xxx, Function<ProguardConfiguration, Predicate<DexType>> matcherFactory) {
     DexItemFactory dexItemFactory = new DexItemFactory();
     ProguardConfigurationParser parser =
         new ProguardConfigurationParser(dexItemFactory, reporter);
@@ -339,15 +344,16 @@
     String otherOption = "-keep class *";
     parser.parse(createConfigurationForTesting(ImmutableList.of(dontwarnAll, otherOption)));
     ProguardConfiguration config = parser.getConfig();
-    assertTrue(pattern.apply(config).matches(dexItemFactory.createType("Lboobaz;")));
-    assertTrue(pattern.apply(config).matches(dexItemFactory.createType("Lboobar;")));
-    assertTrue(pattern.apply(config).matches(dexItemFactory.createType("Lfoobar;")));
+    Predicate<DexType> matcher = matcherFactory.apply(config);
+    assertTrue(matcher.test(dexItemFactory.createType("Lboobaz;")));
+    assertTrue(matcher.test(dexItemFactory.createType("Lboobar;")));
+    assertTrue(matcher.test(dexItemFactory.createType("Lfoobar;")));
   }
 
   @Test
   public void testDontWarnAllImplicitly() {
-    testDontXXXAllImplicitly("warn", ProguardConfiguration::getDontWarnPatterns);
-    testDontXXXAllImplicitly("note", ProguardConfiguration::getDontNotePatterns);
+    testDontXXXAllImplicitly("warn", config -> DontWarnConfiguration.create(config)::matches);
+    testDontXXXAllImplicitly("note", config -> config.getDontNotePatterns()::matches);
   }
 
   @Test
diff --git a/src/test/java/com/android/tools/r8/shaking/TreeShakingTest.java b/src/test/java/com/android/tools/r8/shaking/TreeShakingTest.java
index 96c6fb3..2f1a112 100644
--- a/src/test/java/com/android/tools/r8/shaking/TreeShakingTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/TreeShakingTest.java
@@ -152,6 +152,24 @@
       BiConsumer<CodeInspector, CodeInspector> dexComparator,
       List<String> keepRulesFiles,
       Consumer<InternalOptions> optionsConsumer,
+      ThrowableConsumer<R8FullTestBuilder> testBuilderConsumer)
+      throws Exception {
+    runTest(
+        inspection,
+        outputComparator,
+        dexComparator,
+        keepRulesFiles,
+        optionsConsumer,
+        testBuilderConsumer,
+        null);
+  }
+
+  protected void runTest(
+      ThrowingConsumer<CodeInspector, Exception> inspection,
+      BiConsumer<String, String> outputComparator,
+      BiConsumer<CodeInspector, CodeInspector> dexComparator,
+      List<String> keepRulesFiles,
+      Consumer<InternalOptions> optionsConsumer,
       ThrowableConsumer<R8FullTestBuilder> testBuilderConsumer,
       DiagnosticsConsumer diagnosticsConsumer)
       throws Exception {
@@ -179,6 +197,9 @@
                   if (optionsConsumer != null) {
                     optionsConsumer.accept(options);
                   }
+                  if (frontend == Frontend.DEX) {
+                    options.testing.allowDexInputForTesting = true;
+                  }
                 })
             .allowStdoutMessages()
             .applyIf(testBuilderConsumer != null, testBuilderConsumer);
diff --git a/src/test/java/com/android/tools/r8/shaking/annotations/AnnotationShakingBehaviorTest.java b/src/test/java/com/android/tools/r8/shaking/annotations/AnnotationShakingBehaviorTest.java
index 484fb10..9c98c6e 100644
--- a/src/test/java/com/android/tools/r8/shaking/annotations/AnnotationShakingBehaviorTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/annotations/AnnotationShakingBehaviorTest.java
@@ -91,6 +91,8 @@
         .addKeepMainRule(MainWithNewB.class)
         .addKeepClassAndMembersRules(Factory.class)
         .addKeepAttributes("*Annotation*")
+        .enableInliningAnnotations()
+        .enableNeverClassInliningAnnotations()
         .setMinApi(parameters.getApiLevel())
         .run(parameters.getRuntime(), MainWithNewB.class)
         .assertSuccessWithOutputLines("Hello World!")
diff --git a/src/test/java/com/android/tools/r8/shaking/annotations/LibraryAndMissingAnnotationsTest.java b/src/test/java/com/android/tools/r8/shaking/annotations/LibraryAndMissingAnnotationsTest.java
index 4e6adad..904c0cd 100644
--- a/src/test/java/com/android/tools/r8/shaking/annotations/LibraryAndMissingAnnotationsTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/annotations/LibraryAndMissingAnnotationsTest.java
@@ -107,7 +107,9 @@
             .addKeepAttributes("*Annotation*")
             .addLibraryFiles(runtimeJar(parameters))
             .addKeepClassAndMembersRules(Foo.class)
-            .addKeepRules("-dontwarn " + LibraryAndMissingAnnotationsTest.class.getTypeName())
+            .applyIf(
+                builder.isProguardTestBuilder(),
+                ignore -> builder.addDontWarn(LibraryAndMissingAnnotationsTest.class))
             .addKeepMainRule(mainClass)
             .setMinApi(parameters.getApiLevel());
     if (includeOnLibraryPath) {
diff --git a/src/test/java/com/android/tools/r8/shaking/annotations/ProgramAnnotationRemovalTest.java b/src/test/java/com/android/tools/r8/shaking/annotations/ProgramAnnotationRemovalTest.java
index 3396686..13fbb3e 100644
--- a/src/test/java/com/android/tools/r8/shaking/annotations/ProgramAnnotationRemovalTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/annotations/ProgramAnnotationRemovalTest.java
@@ -49,6 +49,7 @@
             .addInnerClasses(ProgramAnnotationRemovalTest.class)
             .addKeepMainRule(TestClass.class)
             .addKeepAttributes("RuntimeVisibleAnnotations")
+            .enableInliningAnnotations()
             .setMinApi(parameters.getApiLevel())
             .compile()
             .run(parameters.getRuntime(), TestClass.class);
diff --git a/src/test/java/com/android/tools/r8/shaking/annotations/ReflectiveAnnotationUseTest.java b/src/test/java/com/android/tools/r8/shaking/annotations/ReflectiveAnnotationUseTest.java
index 9c358e8..bc55c55 100644
--- a/src/test/java/com/android/tools/r8/shaking/annotations/ReflectiveAnnotationUseTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/annotations/ReflectiveAnnotationUseTest.java
@@ -106,6 +106,7 @@
             .addKeepMainRule(MAIN_CLASS_NAME)
             .addKeepRules(KEEP_ANNOTATIONS)
             .addKeepRules("-keep @interface " + ANNOTATION_NAME + " {", "  *;", "}")
+            .addDontWarnJetBrainsAnnotations()
             .allowDiagnosticWarningMessages()
             .minification(minify)
             .setMinApi(parameters.getApiLevel())
@@ -145,6 +146,7 @@
                 "-keep,allowobfuscation @interface " + ANNOTATION_NAME + " {",
                 "  java.lang.String *f2();",
                 "}")
+            .addDontWarnJetBrainsAnnotations()
             .allowDiagnosticWarningMessages()
             .minification(minify)
             .setMinApi(parameters.getApiLevel())
@@ -181,6 +183,7 @@
             .addProgramFiles(getJavaJarFile(FOLDER))
             .addKeepMainRule(MAIN_CLASS_NAME)
             .addKeepRules(KEEP_ANNOTATIONS)
+            .addDontWarnJetBrainsAnnotations()
             .allowDiagnosticWarningMessages()
             .minification(minify)
             .setMinApi(parameters.getApiLevel())
@@ -216,6 +219,7 @@
             .addProgramFiles(compiledJars.getForConfiguration(kotlinc, targetVersion))
             .addProgramFiles(getJavaJarFile(FOLDER))
             .addKeepMainRule(MAIN_CLASS_NAME)
+            .addDontWarnJetBrainsAnnotations()
             .allowDiagnosticWarningMessages()
             .minification(minify)
             .setMinApi(parameters.getApiLevel())
diff --git a/src/test/java/com/android/tools/r8/shaking/annotations/b137392797/B137392797.java b/src/test/java/com/android/tools/r8/shaking/annotations/b137392797/B137392797.java
index 3b9c067..1112ccd 100644
--- a/src/test/java/com/android/tools/r8/shaking/annotations/b137392797/B137392797.java
+++ b/src/test/java/com/android/tools/r8/shaking/annotations/b137392797/B137392797.java
@@ -5,13 +5,11 @@
 package com.android.tools.r8.shaking.annotations.b137392797;
 
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
-import static org.hamcrest.CoreMatchers.containsString;
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.junit.Assert.assertEquals;
 
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.BooleanUtils;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
@@ -64,16 +62,14 @@
             classWireFieldLabel(),
             classTest(defaultEnumValueInAnnotation))
         .addProgramClasses(TestClass.class)
+        .addDontWarnKotlin()
+        .addDontWarnJetBrainsAnnotations()
         .addKeepClassAndMembersRules(
             "com.squareup.wire.WireField", "com.squareup.demo.myapplication.Test")
         .addKeepMainRule(TestClass.class)
         .addKeepAttributes("*Annotation*")
-        .allowDiagnosticWarningMessages(
-            parameters.isDexRuntime() && parameters.getApiLevel().isLessThan(AndroidApiLevel.N))
         .setMinApi(parameters.getApiLevel())
         .compile()
-        .assertAllWarningMessagesMatch(
-            containsString("required for default or static interface methods desugaring"))
         .inspect(this::checkEnumUses)
         .run(parameters.getRuntime(), TestClass.class, "com.squareup.demo.myapplication.Test")
         .assertSuccessWithOutputLines(
diff --git a/src/test/java/com/android/tools/r8/shaking/assumevalues/SynthesizedRulesFromApiLevelTest.java b/src/test/java/com/android/tools/r8/shaking/assumevalues/SynthesizedRulesFromApiLevelTest.java
index 0d9fa5e..b2f0964 100644
--- a/src/test/java/com/android/tools/r8/shaking/assumevalues/SynthesizedRulesFromApiLevelTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/assumevalues/SynthesizedRulesFromApiLevelTest.java
@@ -53,12 +53,12 @@
   }
 
   // Simple mock implementation of class android.os.Build$VERSION with just the SDK_INT field.
-  private Path mockAndroidRuntimeLibrary(int sdkInt) throws Exception {
+  private Path mockAndroidRuntimeLibrary(AndroidApiLevel apiLevel) throws Exception {
     JasminBuilder builder = new JasminBuilder();
     ClassBuilder classBuilder;
 
     classBuilder = builder.addClass("android.os.Build$VERSION");
-    classBuilder.addStaticFinalField("SDK_INT", "I", Integer.toString(sdkInt));
+    classBuilder.addStaticFinalField("SDK_INT", "I", Integer.toString(apiLevel.getLevel()));
 
     classBuilder = builder.addClass("android.os.Native");
     classBuilder.addStaticMethod("method", ImmutableList.of(), "V",
@@ -71,13 +71,12 @@
     return writeToJar(builder);
   }
 
-  private Path buildMockAndroidRuntimeLibrary(AndroidApiLevel apiLevel) throws Exception {
+  private Path buildMockAndroidRuntimeLibrary(Path mockAndroidRuntimeLibrary) throws Exception {
     // Build the mock library containing android.os.Build.VERSION with D8.
     Path library = temp.newFolder().toPath().resolve("library.jar");
     D8.run(
-        D8Command
-            .builder()
-            .addProgramFiles(mockAndroidRuntimeLibrary(apiLevel.getLevel()))
+        D8Command.builder()
+            .addProgramFiles(mockAndroidRuntimeLibrary)
             .setOutput(library, OutputMode.DexIndexed)
             .build());
     return library;
@@ -164,9 +163,11 @@
       throws Exception {
     assertTrue(runtimeApiLevel.getLevel() >= buildApiLevel.getLevel());
     if (backend == Backend.DEX) {
+      Path androidRuntimeLibraryMock = mockAndroidRuntimeLibrary(runtimeApiLevel);
       testForR8(backend)
           .setMinApi(buildApiLevel)
           .addProgramFiles(buildApp(nativeApiLevel))
+          .addClasspathFiles(androidRuntimeLibraryMock)
           .enableProguardTestOptions()
           .addKeepRules("-neverinline class " + compatLibraryClassName + " { *; }")
           .addKeepMainRule(mainClassName)
@@ -177,20 +178,21 @@
               syntheticProguardRules ->
                   checkSynthesizedRuleExpectation(syntheticProguardRules, synthesizedRule))
           .inspect(inspector)
-          .addRunClasspathFiles(ImmutableList.of(buildMockAndroidRuntimeLibrary(runtimeApiLevel)))
+          .addRunClasspathFiles(buildMockAndroidRuntimeLibrary(androidRuntimeLibraryMock))
           .run(mainClassName)
           .assertSuccessWithOutput(expectedOutput);
     } else {
       assert backend == Backend.CF;
+      Path androidRuntimeLibraryMock = mockAndroidRuntimeLibrary(AndroidApiLevel.D);
       testForR8(backend)
           .addProgramFiles(buildApp(nativeApiLevel))
           .addKeepMainRule(mainClassName)
           .addKeepRules(additionalKeepRules)
+          .addDontWarn("android.os.Build$VERSION", "android.os.Native")
           .apply(configuration)
           .compile()
           .inspectSyntheticProguardRules(this::noSynthesizedRules)
-          .addRunClasspathFiles(
-              ImmutableList.of(mockAndroidRuntimeLibrary(AndroidApiLevel.D.getLevel())))
+          .addRunClasspathFiles(androidRuntimeLibraryMock)
           .run(mainClassName)
           .assertSuccessWithOutput(expectedResultForCompat(AndroidApiLevel.D));
     }
diff --git a/src/test/java/com/android/tools/r8/shaking/attributes/KeepAttributesDotsTest.java b/src/test/java/com/android/tools/r8/shaking/attributes/KeepAttributesDotsTest.java
index 1caf4f2..a913463 100644
--- a/src/test/java/com/android/tools/r8/shaking/attributes/KeepAttributesDotsTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/attributes/KeepAttributesDotsTest.java
@@ -47,10 +47,10 @@
   @Test
   public void testProguard() throws ExecutionException, CompilationFailedException, IOException {
     testForProguard()
-        .addProgramClassesAndInnerClasses(Main.class)
+        .addInnerClasses(getClass())
         .addKeepAllClassesRule()
         .addKeepAttributes(keepAttributes)
-        .addKeepRules("-dontwarn com.android.tools.r8.shaking.attributes.*")
+        .addDontWarn(KeepAttributesDotsTest.class)
         .run(TestRuntime.getCheckedInJdk9(), Main.class)
         .assertSuccessWithOutputLines("Hello World!")
         .inspect(this::inspect);
@@ -59,7 +59,7 @@
   @Test
   public void testR8() throws IOException, CompilationFailedException, ExecutionException {
     testForR8(Backend.CF)
-        .addProgramClassesAndInnerClasses(Main.class)
+        .addInnerClasses(getClass())
         .addKeepAttributes(keepAttributes)
         .addKeepAllClassesRule()
         .run(TestRuntime.getCheckedInJdk9(), Main.class)
diff --git a/src/test/java/com/android/tools/r8/shaking/attributes/KeepAttributesTest.java b/src/test/java/com/android/tools/r8/shaking/attributes/KeepAttributesTest.java
index f298046..a314e96 100644
--- a/src/test/java/com/android/tools/r8/shaking/attributes/KeepAttributesTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/attributes/KeepAttributesTest.java
@@ -27,11 +27,11 @@
 @RunWith(Parameterized.class)
 public class KeepAttributesTest extends TestBase {
 
-  private static final Class CLASS = TestKeepAttributes.class;
+  private static final Class<?> CLASS = TestKeepAttributes.class;
 
   @Parameters(name = "{0}")
   public static TestParametersCollection parameters() {
-    return getTestParameters().withAllRuntimes().build();
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
   }
 
   private final TestParameters parameters;
@@ -111,7 +111,8 @@
         .addProgramClassesAndInnerClasses(CLASS)
         .addKeepAllClassesRule()
         .addKeepRules(keepRules)
-        .setMinApi(parameters.getRuntime())
+        .enableSideEffectAnnotations()
+        .setMinApi(parameters.getApiLevel())
         .run(parameters.getRuntime(), CLASS)
         .inspector()
         .clazz(CLASS)
diff --git a/src/test/java/com/android/tools/r8/shaking/b134858535/EventEntity.java b/src/test/java/com/android/tools/r8/shaking/b134858535/EventEntity.java
new file mode 100644
index 0000000..0f92507
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/b134858535/EventEntity.java
@@ -0,0 +1,7 @@
+// Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.shaking.b134858535;
+
+public class EventEntity {}
diff --git a/src/test/java/com/android/tools/r8/shaking/b134858535/EventPublisher$bDump.java b/src/test/java/com/android/tools/r8/shaking/b134858535/EventPublisher$bDump.java
index df725af..4836ae1 100644
--- a/src/test/java/com/android/tools/r8/shaking/b134858535/EventPublisher$bDump.java
+++ b/src/test/java/com/android/tools/r8/shaking/b134858535/EventPublisher$bDump.java
@@ -68,29 +68,33 @@
       {
         AnnotationVisitor annotationVisitor1 = annotationVisitor0.visitArray("d2");
         annotationVisitor1.visit(null, "<anonymous>");
-        annotationVisitor1.visit(null, "Lio/reactivex/Flowable;");
+        annotationVisitor1.visit(null, "Lcom/android/tools/r8/shaking/b134858535/Flowable;");
         annotationVisitor1.visit(null, "Lkotlin/Pair;");
-        annotationVisitor1.visit(null, "Lcom/permutive/android/event/db/model/EventEntity;");
+        annotationVisitor1.visit(null, "Lcom/android/tools/r8/shaking/b134858535/EventEntity;");
         annotationVisitor1.visit(null, "kotlin.jvm.PlatformType");
         annotationVisitor1.visit(
-            null, "Lcom/permutive/android/event/api/model/TrackBatchEventResponse;");
+            null, "Lcom/android/tools/r8/shaking/b134858535/TrackBatchEventResponse;");
         annotationVisitor1.visit(null, "<name for destructuring parameter 0>");
         annotationVisitor1.visit(null, "");
-        annotationVisitor1.visit(null, "Lcom/permutive/android/config/api/model/SdkConfiguration;");
+        annotationVisitor1.visit(
+            null, "Lcom/android/tools/r8/shaking/b134858535/SdkConfiguration;");
         annotationVisitor1.visit(null, "apply");
         annotationVisitor1.visitEnd();
       }
       annotationVisitor0.visitEnd();
     }
     classWriter.visitInnerClass(
-        "com/permutive/android/event/EventPublisher$b", null, null, ACC_FINAL | ACC_STATIC);
+        "com/android/tools/r8/shaking/b134858535/EventPublisher$b",
+        null,
+        null,
+        ACC_FINAL | ACC_STATIC);
 
     {
       fieldVisitor =
           classWriter.visitField(
               ACC_FINAL | ACC_SYNTHETIC,
               "a",
-              "Lcom/permutive/android/event/EventPublisher;",
+              "Lcom/android/tools/r8/shaking/b134858535/EventPublisher;",
               null,
               null);
       fieldVisitor.visitEnd();
diff --git a/src/test/java/com/android/tools/r8/shaking/b134858535/EventPublisherTest.java b/src/test/java/com/android/tools/r8/shaking/b134858535/EventPublisherTest.java
index 8bde97b..6f9c298 100644
--- a/src/test/java/com/android/tools/r8/shaking/b134858535/EventPublisherTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/b134858535/EventPublisherTest.java
@@ -4,32 +4,62 @@
 
 package com.android.tools.r8.shaking.b134858535;
 
+import static com.android.tools.r8.ToolHelper.getKotlinCompilers;
 
 import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.KotlinCompilerTool.KotlinCompiler;
 import com.android.tools.r8.TestBase;
-import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.ToolHelper;
+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 EventPublisherTest extends TestBase {
 
+  private final KotlinCompiler kotlinc;
+  private final TestParameters parameters;
+
+  @Parameters(name = "{1}, {0}")
+  public static List<Object[]> data() {
+    return buildParameters(
+        getKotlinCompilers(),
+        TestBase.getTestParameters().withDexRuntimes().withAllApiLevels().build());
+  }
+
+  public EventPublisherTest(KotlinCompiler kotlinc, TestParameters parameters) {
+    this.kotlinc = kotlinc;
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testPrivateMethodsInLambdaClass() throws CompilationFailedException {
+    // This test only tests if the dump can be compiled without errors.
+    testForR8(parameters.getBackend())
+        .addProgramClasses(
+            Main.class,
+            Interface.class,
+            EventEntity.class,
+            Flowable.class,
+            SdkConfiguration.class,
+            TrackBatchEventResponse.class)
+        .addProgramClassFileData(EventPublisher$bDump.dump())
+        .addProgramFiles(ToolHelper.getKotlinStdlibJar(kotlinc))
+        .addKeepClassRules(Interface.class)
+        .addKeepMainRule(Main.class)
+        .setMinApi(parameters.getApiLevel())
+        .addHorizontallyMergedLambdaClassesInspector(
+            inspector -> inspector.assertClassNotMerged(EventPublisher$b.class))
+        .compile();
+  }
+
   public static class Main {
 
     public static void main(String[] args) {
       new EventPublisher$b().apply("foo");
     }
   }
-
-  @Test
-  public void testPrivateMethodsInLambdaClass() throws CompilationFailedException {
-    // This test only tests if the dump can be compiled without errors.
-    testForR8(Backend.DEX)
-        .addProgramClasses(Main.class, Interface.class)
-        .addProgramClassFileData(EventPublisher$bDump.dump())
-        .addKeepClassRules(Interface.class)
-        .addKeepMainRule(Main.class)
-        .setMinApi(AndroidApiLevel.L)
-        .addHorizontallyMergedLambdaClassesInspector(
-            inspector -> inspector.assertClassNotMerged(EventPublisher$b.class))
-        .compile();
-  }
 }
diff --git a/src/test/java/com/android/tools/r8/shaking/b134858535/Flowable.java b/src/test/java/com/android/tools/r8/shaking/b134858535/Flowable.java
new file mode 100644
index 0000000..928744f
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/b134858535/Flowable.java
@@ -0,0 +1,7 @@
+// Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.shaking.b134858535;
+
+public class Flowable {}
diff --git a/src/test/java/com/android/tools/r8/shaking/b134858535/SdkConfiguration.java b/src/test/java/com/android/tools/r8/shaking/b134858535/SdkConfiguration.java
new file mode 100644
index 0000000..39f22e3
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/b134858535/SdkConfiguration.java
@@ -0,0 +1,7 @@
+// Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.shaking.b134858535;
+
+public class SdkConfiguration {}
diff --git a/src/test/java/com/android/tools/r8/shaking/b134858535/TrackBatchEventResponse.java b/src/test/java/com/android/tools/r8/shaking/b134858535/TrackBatchEventResponse.java
new file mode 100644
index 0000000..ae8dfb6
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/b134858535/TrackBatchEventResponse.java
@@ -0,0 +1,7 @@
+// Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.shaking.b134858535;
+
+public class TrackBatchEventResponse {}
diff --git a/src/test/java/com/android/tools/r8/shaking/clinit/ClassNotInitializedByCheckCastTest.java b/src/test/java/com/android/tools/r8/shaking/clinit/ClassNotInitializedByCheckCastTest.java
index 5a3efa6..484526f 100644
--- a/src/test/java/com/android/tools/r8/shaking/clinit/ClassNotInitializedByCheckCastTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/clinit/ClassNotInitializedByCheckCastTest.java
@@ -5,13 +5,11 @@
 package com.android.tools.r8.shaking.clinit;
 
 import static com.android.tools.r8.utils.codeinspector.Matchers.isAbsent;
-import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
 import static org.hamcrest.MatcherAssert.assertThat;
 
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
-import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -45,15 +43,8 @@
   }
 
   private void inspect(CodeInspector inspector) {
-    if (parameters.isCfRuntime()) {
-      // Check that A.<clinit>() is removed.
-      ClassSubject aClassSubject = inspector.clazz(A.class);
-      assertThat(aClassSubject, isPresent());
-      assertThat(aClassSubject.clinit(), isAbsent());
-    } else {
-      // Check that A is removed.
-      assertThat(inspector.clazz(A.class), isAbsent());
-    }
+    // Check that A is removed.
+    assertThat(inspector.clazz(A.class), isAbsent());
   }
 
   static class TestClass {
diff --git a/src/test/java/com/android/tools/r8/shaking/defaultmethods/DefaultMethodsTest.java b/src/test/java/com/android/tools/r8/shaking/defaultmethods/DefaultMethodsTest.java
index 32a861a..0a54cf8 100644
--- a/src/test/java/com/android/tools/r8/shaking/defaultmethods/DefaultMethodsTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/defaultmethods/DefaultMethodsTest.java
@@ -44,7 +44,10 @@
       throws Exception {
     testForR8(parameters.getBackend())
         .addProgramClasses(
-            InterfaceWithDefaultMethods.class, ClassImplementingInterface.class, TestClass.class)
+            InterfaceWithDefaultMethods.class,
+            ClassImplementingInterface.class,
+            OtherClassImplementingInterface.class,
+            TestClass.class)
         .setMinApi(parameters.getApiLevel())
         .addKeepMainRule(TestClass.class)
         .addKeepRules(additionalKeepRules)
diff --git a/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking12Test.java b/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking12Test.java
index b30a14d..15e18bd 100644
--- a/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking12Test.java
+++ b/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking12Test.java
@@ -9,6 +9,7 @@
 
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.shaking.TreeShakingTest;
+import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.google.common.collect.ImmutableList;
@@ -25,7 +26,13 @@
 
   @Parameters(name = "mode:{0}-{1} minify:{2}")
   public static List<Object[]> data() {
-    return defaultTreeShakingParameters();
+    return buildParameters(
+        Frontend.values(),
+        getTestParameters()
+            .withAllRuntimes()
+            .withApiLevelsStartingAtIncluding(AndroidApiLevel.K)
+            .build(),
+        MinifyMode.values());
   }
 
   public TreeShaking12Test(Frontend frontend, TestParameters parameters, MinifyMode minify) {
diff --git a/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/ForceProguardCompatibilityTest.java b/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/ForceProguardCompatibilityTest.java
index 04e2c9f..4e53505 100644
--- a/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/ForceProguardCompatibilityTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/ForceProguardCompatibilityTest.java
@@ -14,9 +14,11 @@
 import static org.junit.Assume.assumeTrue;
 
 import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.R8CompatTestBuilder;
 import com.android.tools.r8.R8TestCompileResult;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.ThrowableConsumer;
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.naming.MemberNaming.MethodSignature;
 import com.android.tools.r8.shaking.ProguardKeepAttributes;
@@ -68,12 +70,21 @@
   }
 
   private void test(Class<?> mainClass, Class<?> mentionedClass) throws Exception {
+    test(mainClass, mentionedClass, null);
+  }
+
+  private void test(
+      Class<?> mainClass,
+      Class<?> mentionedClass,
+      ThrowableConsumer<R8CompatTestBuilder> configuration)
+      throws Exception {
     CodeInspector inspector =
         testForR8Compat(parameters.getBackend(), forceProguardCompatibility)
             .noMinification()
             .allowAccessModification()
             .addProgramClasses(mainClass, mentionedClass)
             .addKeepMainRule(mainClass)
+            .apply(configuration)
             .setMinApi(parameters.getApiLevel())
             .compile()
             .inspector();
@@ -86,7 +97,12 @@
 
   @Test
   public void testKeepDefaultInitializer() throws Exception {
-    test(TestMain.class, TestMain.MentionedClass.class);
+    test(
+        TestMain.class,
+        TestMain.MentionedClass.class,
+        testBuilder ->
+            testBuilder.addProgramClasses(
+                TestMain.MentionedClassWithAnnotation.class, TestAnnotation.class));
   }
 
   @Test
@@ -215,7 +231,7 @@
     testCheckCast(
         TestMainWithCheckCast.class,
         TestClassWithDefaultConstructor.class,
-        forceProguardCompatibility || parameters.isCfRuntime());
+        forceProguardCompatibility);
     testCheckCast(TestMainWithoutCheckCast.class, TestClassWithDefaultConstructor.class, false);
   }
 
diff --git a/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/keepinterface/CompatKeepInterfaceAsInstantiatedTest.java b/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/keepinterface/CompatKeepInterfaceAsInstantiatedTest.java
index 7f4606c..38a6b4f 100644
--- a/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/keepinterface/CompatKeepInterfaceAsInstantiatedTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/keepinterface/CompatKeepInterfaceAsInstantiatedTest.java
@@ -127,7 +127,9 @@
       throws Exception {
     return builder
         // Add -dontwarn to avoid PG failing since this test runner class is not present.
-        .addKeepRules("-dontwarn " + CompatKeepInterfaceAsInstantiatedTest.class.getTypeName())
+        .applyIf(
+            builder.isProguardTestBuilder(),
+            b -> b.addDontWarn(CompatKeepInterfaceAsInstantiatedTest.class))
         .noMinification()
         .addProgramClasses(main, Foo.class)
         .addKeepMainRule(main)
diff --git a/src/test/java/com/android/tools/r8/shaking/ifrule/membervaluepropagation/IfWithFieldValuePropagationTest.java b/src/test/java/com/android/tools/r8/shaking/ifrule/membervaluepropagation/IfWithFieldValuePropagationTest.java
index 908297c..cc1a987 100644
--- a/src/test/java/com/android/tools/r8/shaking/ifrule/membervaluepropagation/IfWithFieldValuePropagationTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/ifrule/membervaluepropagation/IfWithFieldValuePropagationTest.java
@@ -23,7 +23,7 @@
 
   @Parameterized.Parameters(name = "{0}")
   public static TestParametersCollection data() {
-    return getTestParameters().withAllRuntimes().build();
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
   }
 
   public IfWithFieldValuePropagationTest(TestParameters parameters) {
@@ -48,8 +48,9 @@
         .addRunClasspathFiles(
             testForR8(parameters.getBackend())
                 .addProgramClasses(Library.class)
+                .addClasspathClasses(Layout.class)
                 .addKeepAllClassesRule()
-                .setMinApi(parameters.getRuntime())
+                .setMinApi(parameters.getApiLevel())
                 .compile()
                 .writeToZip())
         .run(parameters.getRuntime(), TestClass.class)
diff --git a/src/test/java/com/android/tools/r8/shaking/ifrule/membervaluepropagation/IfWithMethodValuePropagationTest.java b/src/test/java/com/android/tools/r8/shaking/ifrule/membervaluepropagation/IfWithMethodValuePropagationTest.java
index f795067..e377809 100644
--- a/src/test/java/com/android/tools/r8/shaking/ifrule/membervaluepropagation/IfWithMethodValuePropagationTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/ifrule/membervaluepropagation/IfWithMethodValuePropagationTest.java
@@ -48,6 +48,7 @@
         .addRunClasspathFiles(
             testForR8(parameters.getBackend())
                 .addProgramClasses(Library.class)
+                .addClasspathClasses(Layout.class)
                 .addKeepAllClassesRule()
                 .setMinApi(parameters.getRuntime())
                 .compile()
diff --git a/src/test/java/com/android/tools/r8/shaking/ifrule/verticalclassmerging/MergedFieldTypeTest.java b/src/test/java/com/android/tools/r8/shaking/ifrule/verticalclassmerging/MergedFieldTypeTest.java
index 7ea419c..6b01b51 100644
--- a/src/test/java/com/android/tools/r8/shaking/ifrule/verticalclassmerging/MergedFieldTypeTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/ifrule/verticalclassmerging/MergedFieldTypeTest.java
@@ -11,6 +11,7 @@
 import static org.junit.Assert.assertNotEquals;
 
 import com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.R8FullTestBuilder;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
@@ -78,6 +79,11 @@
     }
 
     @Override
+    public void configure(R8FullTestBuilder builder) {
+      builder.enableNeverClassInliningAnnotations();
+    }
+
+    @Override
     public Class<?> getTestClass() {
       return TestClass.class;
     }
diff --git a/src/test/java/com/android/tools/r8/shaking/interfaces/RedundantImplementsClauseTest.java b/src/test/java/com/android/tools/r8/shaking/interfaces/RedundantImplementsClauseTest.java
index 1d559b7..fae800c 100644
--- a/src/test/java/com/android/tools/r8/shaking/interfaces/RedundantImplementsClauseTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/interfaces/RedundantImplementsClauseTest.java
@@ -43,6 +43,7 @@
         .addKeepMainRule(TestClass.class)
         .enableInliningAnnotations()
         .enableNeverClassInliningAnnotations()
+        .enableNoVerticalClassMergingAnnotations()
         .setMinApi(parameters.getApiLevel())
         .compile()
         .inspect(this::inspect)
diff --git a/src/test/java/com/android/tools/r8/shaking/keepparameternames/KeepParameterNamesTest.java b/src/test/java/com/android/tools/r8/shaking/keepparameternames/KeepParameterNamesTest.java
index 888b231..4740853 100644
--- a/src/test/java/com/android/tools/r8/shaking/keepparameternames/KeepParameterNamesTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/keepparameternames/KeepParameterNamesTest.java
@@ -173,6 +173,9 @@
         .addInnerClasses(KeepParameterNamesTest.class)
         .addKeepMainRule(TestClass.class)
         .addKeepRules("-keep class " + Api.class.getTypeName() + "{ api*(...); }")
+        .enableInliningAnnotations()
+        .enableNeverClassInliningAnnotations()
+        .enableUnusedArgumentAnnotations()
         .minification(enableMinification)
         .apply(this::configureKeepParameterNames)
         .compile()
diff --git a/src/test/java/com/android/tools/r8/shaking/keptgraph/KeptByAnnotatedClassTestRunner.java b/src/test/java/com/android/tools/r8/shaking/keptgraph/KeptByAnnotatedClassTestRunner.java
index 47659e2..3f3c43e 100644
--- a/src/test/java/com/android/tools/r8/shaking/keptgraph/KeptByAnnotatedClassTestRunner.java
+++ b/src/test/java/com/android/tools/r8/shaking/keptgraph/KeptByAnnotatedClassTestRunner.java
@@ -46,7 +46,9 @@
             .enableGraphInspector()
             .enableInliningAnnotations()
             .addProgramClasses(CLASS)
+            .addKeepAnnotation()
             .addKeepRules("-keep @com.android.tools.r8.Keep class * { public *; }")
+            .addInliningAnnotations()
             .run(CLASS)
             .assertSuccessWithOutput(EXPECTED)
             .graphInspector();
diff --git a/src/test/java/com/android/tools/r8/shaking/keptgraph/KeptByAnnotatedMethodTestRunner.java b/src/test/java/com/android/tools/r8/shaking/keptgraph/KeptByAnnotatedMethodTestRunner.java
index 2f3fea1..a5fb706 100644
--- a/src/test/java/com/android/tools/r8/shaking/keptgraph/KeptByAnnotatedMethodTestRunner.java
+++ b/src/test/java/com/android/tools/r8/shaking/keptgraph/KeptByAnnotatedMethodTestRunner.java
@@ -62,6 +62,7 @@
             .enableGraphInspector()
             .enableInliningAnnotations()
             .addProgramClasses(CLASSES)
+            .addKeepAnnotation()
             .addKeepMainRule(CLASS)
             .addKeepRules(keepAnnotatedMethodsRule, keepClassesOfAnnotatedMethodsRule)
             .run(CLASS)
diff --git a/src/test/java/com/android/tools/r8/shaking/keptgraph/KeptByConditionalRuleTestRunner.java b/src/test/java/com/android/tools/r8/shaking/keptgraph/KeptByConditionalRuleTestRunner.java
index c813307..32c8662 100644
--- a/src/test/java/com/android/tools/r8/shaking/keptgraph/KeptByConditionalRuleTestRunner.java
+++ b/src/test/java/com/android/tools/r8/shaking/keptgraph/KeptByConditionalRuleTestRunner.java
@@ -108,7 +108,7 @@
 
     // Check baz is kept by the if rule.
     QueryNode barMethodNode = inspector.method(bazMethod).assertNotRenamed();
-    ifRuleInstances.assertAllMatch(n -> barMethodNode.isKeptBy(n));
+    ifRuleInstances.assertAllMatch(barMethodNode::isKeptBy);
 
     ByteArrayOutputStream baos = new ByteArrayOutputStream();
     whyAreYouKeepingConsumer.printWhyAreYouKeeping(bazMethod, new PrintStream(baos));
diff --git a/src/test/java/com/android/tools/r8/shaking/keptgraph/KeptByFieldReflectionTestRunner.java b/src/test/java/com/android/tools/r8/shaking/keptgraph/KeptByFieldReflectionTestRunner.java
index a43e42c..02d8481 100644
--- a/src/test/java/com/android/tools/r8/shaking/keptgraph/KeptByFieldReflectionTestRunner.java
+++ b/src/test/java/com/android/tools/r8/shaking/keptgraph/KeptByFieldReflectionTestRunner.java
@@ -30,16 +30,17 @@
 
   private static final Class<?> CLASS = KeptByFieldReflectionTest.class;
   private static final Collection<Class<?>> CLASSES = Arrays.asList(CLASS);
+  private static final String TYPE_NAME = CLASS.getTypeName();
 
   private final String EXPECTED_STDOUT = StringUtils.lines("got foo: 42");
 
   private final String EXPECTED_WHYAREYOUKEEPING =
       StringUtils.lines(
-          "int com.android.tools.r8.shaking.keptgraph.KeptByFieldReflectionTest.foo",
+          "int " + TYPE_NAME + ".foo",
           "|- is reflected from:",
-          "|  void com.android.tools.r8.shaking.keptgraph.KeptByFieldReflectionTest.main(java.lang.String[])",
+          "|  void " + TYPE_NAME + ".main(java.lang.String[])",
           "|- is referenced in keep rule:",
-          "|  -keep class com.android.tools.r8.shaking.keptgraph.KeptByFieldReflectionTest { public static void main(java.lang.String[]); }");
+          "|  -keep class " + TYPE_NAME + " { public static void main(java.lang.String[]); }");
 
   private final Backend backend;
 
@@ -67,6 +68,7 @@
         testForR8(backend)
             .enableGraphInspector(consumer)
             .addProgramClasses(CLASSES)
+            .addKeepAnnotation()
             .addKeepMainRule(CLASS)
             .run(CLASS)
             .assertSuccessWithOutput(EXPECTED_STDOUT)
diff --git a/src/test/java/com/android/tools/r8/shaking/keptgraph/KeptByMethodReflectionTestRunner.java b/src/test/java/com/android/tools/r8/shaking/keptgraph/KeptByMethodReflectionTestRunner.java
index c8afcdc..be0fcff 100644
--- a/src/test/java/com/android/tools/r8/shaking/keptgraph/KeptByMethodReflectionTestRunner.java
+++ b/src/test/java/com/android/tools/r8/shaking/keptgraph/KeptByMethodReflectionTestRunner.java
@@ -28,16 +28,17 @@
 
   private static final Class<?> CLASS = KeptByMethodReflectionTest.class;
   private static final Collection<Class<?>> CLASSES = Arrays.asList(CLASS);
+  private static final String TYPE_NAME = CLASS.getTypeName();
 
   private final String EXPECTED_STDOUT = StringUtils.lines("called foo");
 
   private final String EXPECTED_WHYAREYOUKEEPING =
       StringUtils.lines(
-          "void com.android.tools.r8.shaking.keptgraph.KeptByMethodReflectionTest.foo()",
+          "void " + TYPE_NAME + ".foo()",
           "|- is reflected from:",
-          "|  void com.android.tools.r8.shaking.keptgraph.KeptByMethodReflectionTest.main(java.lang.String[])",
+          "|  void " + TYPE_NAME + ".main(java.lang.String[])",
           "|- is referenced in keep rule:",
-          "|  -keep class com.android.tools.r8.shaking.keptgraph.KeptByMethodReflectionTest { public static void main(java.lang.String[]); }");
+          "|  -keep class " + TYPE_NAME + " { public static void main(java.lang.String[]); }");
 
   private final Backend backend;
 
@@ -64,6 +65,7 @@
         testForR8(backend)
             .enableGraphInspector(consumer)
             .addProgramClasses(CLASSES)
+            .addKeepAnnotation()
             .addKeepMainRule(CLASS)
             .run(CLASS)
             .assertSuccessWithOutput(EXPECTED_STDOUT)
diff --git a/src/test/java/com/android/tools/r8/shaking/keptgraph/KeptByTwoRulesTestRunner.java b/src/test/java/com/android/tools/r8/shaking/keptgraph/KeptByTwoRulesTestRunner.java
index 5623924..042679f 100644
--- a/src/test/java/com/android/tools/r8/shaking/keptgraph/KeptByTwoRulesTestRunner.java
+++ b/src/test/java/com/android/tools/r8/shaking/keptgraph/KeptByTwoRulesTestRunner.java
@@ -54,6 +54,7 @@
         testForR8(backend)
             .enableGraphInspector()
             .addProgramClasses(CLASSES)
+            .addKeepAnnotation()
             .addKeepRules(keepPublicRule, keepFooRule)
             .run(CLASS)
             .assertSuccessWithOutput(EXPECTED)
diff --git a/src/test/java/com/android/tools/r8/shaking/keptgraph/KeptViaClassInitializerTestRunner.java b/src/test/java/com/android/tools/r8/shaking/keptgraph/KeptViaClassInitializerTestRunner.java
index 4d385a0..230c0cf 100644
--- a/src/test/java/com/android/tools/r8/shaking/keptgraph/KeptViaClassInitializerTestRunner.java
+++ b/src/test/java/com/android/tools/r8/shaking/keptgraph/KeptViaClassInitializerTestRunner.java
@@ -21,6 +21,7 @@
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.references.MethodReference;
 import com.android.tools.r8.shaking.WhyAreYouKeepingConsumer;
+import com.android.tools.r8.synthesis.SyntheticItemsTestUtils;
 import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.StringUtils;
 import com.android.tools.r8.utils.graphinspector.GraphInspector;
@@ -28,6 +29,7 @@
 import java.io.ByteArrayOutputStream;
 import java.io.PrintStream;
 import java.util.function.Supplier;
+import org.hamcrest.Matcher;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
@@ -110,6 +112,7 @@
             .addProgramClassesAndInnerClasses(Main.class, A.class, T.class)
             .addKeepMethodRules(mainMethod)
             .enableMemberValuePropagationAnnotations()
+            .enableNeverClassInliningAnnotations()
             .setMinApi(AndroidApiLevel.N)
             .apply(
                 b -> {
@@ -129,12 +132,13 @@
     consumer.printWhyAreYouKeeping(classFromClass(A.class), new PrintStream(baos));
     assertThat(baos.toString(), containsString(KEPT_REASON_SUFFIX));
 
-    // TODO(b/124499108): Currently synthetic lambda classes are referenced,
+    // TODO(b/124499108): Currently (internal) synthetic lambda classes are referenced,
     //  should be their originating context.
+    Matcher<String> hasLambda = SyntheticItemsTestUtils.containsInternalSyntheticReference();
     if (parameters.isDexRuntime()) {
-      assertThat(baos.toString(), containsString("-$$Lambda$"));
+      assertThat(baos.toString(), hasLambda);
     } else {
-      assertThat(baos.toString(), not(containsString("-$$Lambda$")));
+      assertThat(baos.toString(), not(hasLambda));
     }
   }
 }
diff --git a/src/test/java/com/android/tools/r8/shaking/proxy/MockitoTest.java b/src/test/java/com/android/tools/r8/shaking/proxy/MockitoTest.java
index cea851e..6f868fc 100644
--- a/src/test/java/com/android/tools/r8/shaking/proxy/MockitoTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/proxy/MockitoTest.java
@@ -5,14 +5,12 @@
 
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresentAndNotRenamed;
-import static org.hamcrest.CoreMatchers.containsString;
 import static org.hamcrest.CoreMatchers.not;
 import static org.hamcrest.MatcherAssert.assertThat;
 
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.ToolHelper;
-import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.BooleanUtils;
 import com.android.tools.r8.utils.FileUtils;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
@@ -53,13 +51,10 @@
         testForR8(parameters.getBackend())
             .addProgramFiles(MOCKITO_INTERFACE_JAR)
             .addKeepRuleFiles(flagToKeepTestRunner)
-            .allowDiagnosticWarningMessages(
-                parameters.isDexRuntime() && parameters.getApiLevel().isLessThan(AndroidApiLevel.N))
+            .addDontWarn("org.junit.**", "org.mockito.**")
             .minification(minify)
             .setMinApi(parameters.getApiLevel())
             .compile()
-            .assertAllWarningMessagesMatch(
-                containsString("required for default or static interface methods desugaring"))
             .inspector();
     ClassSubject itf = inspector.clazz(M_I);
     assertThat(itf, isPresent());
@@ -75,13 +70,10 @@
         testForR8(parameters.getBackend())
             .addProgramFiles(MOCKITO_INTERFACE_JAR)
             .addKeepRuleFiles(flagToKeepInterfaceConditionally)
-            .allowDiagnosticWarningMessages(
-                parameters.isDexRuntime() && parameters.getApiLevel().isLessThan(AndroidApiLevel.N))
+            .addDontWarn("org.junit.**", "org.mockito.**")
             .minification(minify)
             .setMinApi(parameters.getApiLevel())
             .compile()
-            .assertAllWarningMessagesMatch(
-                containsString("required for default or static interface methods desugaring"))
             .inspector();
     ClassSubject itf = inspector.clazz(M_I);
     assertThat(itf, isPresent());
diff --git a/src/test/java/com/android/tools/r8/shaking/testrules/C.java b/src/test/java/com/android/tools/r8/shaking/testrules/C.java
index f402864..497c218 100644
--- a/src/test/java/com/android/tools/r8/shaking/testrules/C.java
+++ b/src/test/java/com/android/tools/r8/shaking/testrules/C.java
@@ -4,14 +4,12 @@
 
 package com.android.tools.r8.shaking.testrules;
 
-import com.android.tools.r8.AssumeMayHaveSideEffects;
 import com.android.tools.r8.NeverInline;
 
 public class C {
 
   private static int i;
 
-  @AssumeMayHaveSideEffects
   @NeverInline
   public static int x() {
     return i;
diff --git a/src/test/java/com/android/tools/r8/shaking/testrules/ForceInlineTest.java b/src/test/java/com/android/tools/r8/shaking/testrules/ForceInlineTest.java
index d4e5972..b669c95 100644
--- a/src/test/java/com/android/tools/r8/shaking/testrules/ForceInlineTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/testrules/ForceInlineTest.java
@@ -42,6 +42,7 @@
     return testForR8(parameters.getBackend())
         .addProgramClasses(Main.class, A.class, B.class, C.class)
         .addKeepRules(proguardConfiguration)
+        .enableInliningAnnotations()
         .enableNoHorizontalClassMergingAnnotations()
         .enableNoStaticClassMergingAnnotations()
         .enableProguardTestOptions()
diff --git a/src/test/java/com/android/tools/r8/shaking/whyareyoukeeping/WhyAreYouKeepingTest.java b/src/test/java/com/android/tools/r8/shaking/whyareyoukeeping/WhyAreYouKeepingTest.java
index eeb280b..477e1f3 100644
--- a/src/test/java/com/android/tools/r8/shaking/whyareyoukeeping/WhyAreYouKeepingTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/whyareyoukeeping/WhyAreYouKeepingTest.java
@@ -79,6 +79,7 @@
         .addKeepRules("-whyareyoukeeping class " + A.class.getTypeName())
         // Redirect the compilers stdout to intercept the '-whyareyoukeeping' output
         .collectStdout()
+        .addInliningAnnotations()
         .compile()
         .assertStdoutThatMatches(equalTo(expected));
   }
@@ -90,6 +91,7 @@
         .addProgramClasses(A.class)
         .addKeepMethodRules(Reference.methodFromMethod(A.class.getMethod("foo")))
         .setKeptGraphConsumer(graphConsumer)
+        .addInliningAnnotations()
         .compile();
 
     ByteArrayOutputStream baos = new ByteArrayOutputStream();
@@ -139,13 +141,13 @@
         .allowUnusedProguardConfigurationRules()
         // Redirect the compilers stdout to intercept the '-whyareyoukeeping' output
         .collectStdout()
+        .addInliningAnnotations()
         .compile()
         .assertNoStdout();
   }
 
   @Test
   public void testNonExistentMethodWhyAreYouKeepingViaProguardConfig() throws Exception {
-    ByteArrayOutputStream baos = new ByteArrayOutputStream();
     String aName = A.class.getTypeName();
     testForR8(backend)
         .addProgramClasses(A.class)
@@ -153,6 +155,7 @@
         .addKeepRules("-whyareyoukeeping class " + aName + " { nonExistentMethod(); }")
         // Redirect the compilers stdout to intercept the '-whyareyoukeeping' output
         .collectStdout()
+        .addInliningAnnotations()
         .compile()
         .assertStdoutThatMatches(not(equalTo("")));
   }
diff --git a/src/test/java/com/android/tools/r8/smali/ConstantFoldingTest.java b/src/test/java/com/android/tools/r8/smali/ConstantFoldingTest.java
index fb0785e..e5a8027 100644
--- a/src/test/java/com/android/tools/r8/smali/ConstantFoldingTest.java
+++ b/src/test/java/com/android/tools/r8/smali/ConstantFoldingTest.java
@@ -31,9 +31,8 @@
 
 public class ConstantFoldingTest extends SmaliTestBase {
 
-  @FunctionalInterface
   public interface TriConsumer<T, U, V> {
-    public void accept(T t, U u, V v);
+    void accept(T t, U u, V v);
   }
 
   private class SmaliBuilderWithCheckers {
diff --git a/src/test/java/com/android/tools/r8/synthesis/SyntheticItemsTestUtils.java b/src/test/java/com/android/tools/r8/synthesis/SyntheticItemsTestUtils.java
new file mode 100644
index 0000000..438d3f9
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/synthesis/SyntheticItemsTestUtils.java
@@ -0,0 +1,77 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.synthesis;
+
+import static org.hamcrest.CoreMatchers.containsString;
+
+import com.android.tools.r8.ir.desugar.InterfaceMethodRewriter;
+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.synthesis.SyntheticNaming.Phase;
+import com.android.tools.r8.synthesis.SyntheticNaming.SyntheticKind;
+import java.lang.reflect.Method;
+import org.hamcrest.Matcher;
+
+public class SyntheticItemsTestUtils {
+
+  public static ClassReference syntheticCompanionClass(Class<?> clazz) {
+    return Reference.classFromDescriptor(
+        InterfaceMethodRewriter.getCompanionClassDescriptor(
+            Reference.classFromClass(clazz).getDescriptor()));
+  }
+
+  private static ClassReference syntheticClass(Class<?> clazz, SyntheticKind kind, int id) {
+    return SyntheticNaming.makeSyntheticReferenceForTest(
+        Reference.classFromClass(clazz), kind, "" + id);
+  }
+
+  public static MethodReference syntheticBackportMethod(Class<?> clazz, int id, Method method) {
+    ClassReference syntheticHolder =
+        syntheticClass(clazz, SyntheticNaming.SyntheticKind.BACKPORT, id);
+    MethodReference originalMethod = Reference.methodFromMethod(method);
+    return Reference.methodFromDescriptor(
+        syntheticHolder.getDescriptor(),
+        SyntheticNaming.INTERNAL_SYNTHETIC_METHOD_PREFIX,
+        originalMethod.getMethodDescriptor());
+  }
+
+  public static ClassReference syntheticLambdaClass(Class<?> clazz, int id) {
+    return syntheticClass(clazz, SyntheticNaming.SyntheticKind.LAMBDA, id);
+  }
+
+  public static MethodReference syntheticLambdaMethod(Class<?> clazz, int id, Method method) {
+    ClassReference syntheticHolder = syntheticLambdaClass(clazz, id);
+    MethodReference originalMethod = Reference.methodFromMethod(method);
+    return Reference.methodFromDescriptor(
+        syntheticHolder.getDescriptor(),
+        originalMethod.getMethodName(),
+        originalMethod.getMethodDescriptor());
+  }
+
+  public static boolean isInternalLambda(ClassReference reference) {
+    return SyntheticNaming.isSynthetic(reference, Phase.INTERNAL, SyntheticKind.LAMBDA);
+  }
+
+  public static boolean isExternalLambda(ClassReference reference) {
+    return SyntheticNaming.isSynthetic(reference, Phase.EXTERNAL, SyntheticKind.LAMBDA);
+  }
+
+  public static boolean isExternalStaticInterfaceCall(ClassReference reference) {
+    return SyntheticNaming.isSynthetic(
+        reference, Phase.EXTERNAL, SyntheticKind.STATIC_INTERFACE_CALL);
+  }
+
+  public static boolean isExternalTwrCloseMethod(ClassReference reference) {
+    return SyntheticNaming.isSynthetic(reference, Phase.EXTERNAL, SyntheticKind.TWR_CLOSE_RESOURCE);
+  }
+
+  public static Matcher<String> containsInternalSyntheticReference() {
+    return containsString(SyntheticNaming.getPhaseSeparator(Phase.INTERNAL));
+  }
+
+  public static Matcher<String> containsExternalSyntheticReference() {
+    return containsString(SyntheticNaming.getPhaseSeparator(Phase.EXTERNAL));
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/utils/StringUtilsTest.java b/src/test/java/com/android/tools/r8/utils/StringUtilsTest.java
index cac07ed..8279af3 100644
--- a/src/test/java/com/android/tools/r8/utils/StringUtilsTest.java
+++ b/src/test/java/com/android/tools/r8/utils/StringUtilsTest.java
@@ -33,9 +33,9 @@
 
   private void assertListEquals(List<String> xs, List<String> ys) {
     assertEquals(
-        StringUtils.join(xs, ", ", BraceType.SQUARE, s -> '"' + StringUtils.toASCIIString(s) + '"'),
-        StringUtils.join(ys, ", ", BraceType.SQUARE, s -> '"' + StringUtils.toASCIIString(s) + '"')
-    );
+        StringUtils.join(", ", xs, s -> '"' + StringUtils.toASCIIString(s) + '"', BraceType.SQUARE),
+        StringUtils.join(
+            ", ", ys, s -> '"' + StringUtils.toASCIIString(s) + '"', BraceType.SQUARE));
   }
 
   @Test
diff --git a/src/test/java/com/android/tools/r8/utils/SyntheticItemsTestUtils.java b/src/test/java/com/android/tools/r8/utils/SyntheticItemsTestUtils.java
deleted file mode 100644
index 9e78bac..0000000
--- a/src/test/java/com/android/tools/r8/utils/SyntheticItemsTestUtils.java
+++ /dev/null
@@ -1,27 +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.utils;
-
-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.synthesis.SyntheticItems;
-import java.lang.reflect.Method;
-
-public class SyntheticItemsTestUtils {
-
-  public static ClassReference syntheticClass(Class<?> clazz, int id) {
-    return Reference.classFromTypeName(
-        clazz.getTypeName() + SyntheticItems.EXTERNAL_SYNTHETIC_CLASS_SEPARATOR + id);
-  }
-
-  public static MethodReference syntheticMethod(Class<?> clazz, int id, Method method) {
-    ClassReference syntheticHolder = syntheticClass(clazz, id);
-    MethodReference originalMethod = Reference.methodFromMethod(method);
-    return Reference.methodFromDescriptor(
-        syntheticHolder.getDescriptor(),
-        SyntheticItems.INTERNAL_SYNTHETIC_METHOD_PREFIX + 0,
-        originalMethod.getMethodDescriptor());
-  }
-}
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentClassSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentClassSubject.java
index ffe8151..a42fa4b 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentClassSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentClassSubject.java
@@ -136,6 +136,11 @@
   }
 
   @Override
+  public ClassReference getOriginalReference() {
+    return null;
+  }
+
+  @Override
   public ClassReference getFinalReference() {
     return null;
   }
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/AssertUtils.java b/src/test/java/com/android/tools/r8/utils/codeinspector/AssertUtils.java
index 87eb583..e9634a5 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/AssertUtils.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/AssertUtils.java
@@ -13,9 +13,14 @@
 
 public class AssertUtils {
 
-  public static <E extends Throwable> void assertFailsCompilation(ThrowingAction<E> action)
-      throws E {
-    assertFailsCompilationIf(true, action);
+  public static void assertFailsCompilation(ThrowingAction<CompilationFailedException> action) {
+    try {
+      assertFailsCompilationIf(true, action);
+      return;
+    } catch (CompilationFailedException e) {
+      // Should have been caught
+    }
+    fail("Should have failed with a CompilationFailedException");
   }
 
   public static <E extends Throwable> void assertFailsCompilation(
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/CfInstructionSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/CfInstructionSubject.java
index 3a9976f..f927e62 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/CfInstructionSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/CfInstructionSubject.java
@@ -285,6 +285,7 @@
         && ((CfInvoke) instruction).getOpcode() == Opcodes.INVOKESPECIAL;
   }
 
+  @Override
   public boolean isInvokeDynamic() {
     return instruction instanceof CfInvokeDynamic;
   }
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/ClassSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/ClassSubject.java
index a2d32eb..20d7466 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/ClassSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/ClassSubject.java
@@ -191,6 +191,8 @@
 
   public abstract String getOriginalBinaryName();
 
+  public abstract ClassReference getOriginalReference();
+
   public abstract ClassReference getFinalReference();
 
   public abstract String getFinalName();
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/DexInstructionSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/DexInstructionSubject.java
index 4b9f733..2ae5f05 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/DexInstructionSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/DexInstructionSubject.java
@@ -209,6 +209,11 @@
     return false;
   }
 
+  @Override
+  public boolean isInvokeDynamic() {
+    return isInvokeCustom();
+  }
+
   public boolean isInvokeCustom() {
     return instruction instanceof InvokeCustom || instruction instanceof InvokeCustomRange;
   }
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/DexTryCatchSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/DexTryCatchSubject.java
index c1fa59a..fea4fbd 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/DexTryCatchSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/DexTryCatchSubject.java
@@ -54,7 +54,7 @@
   @Override
   public Stream<TypeSubject> streamGuards() {
     return Arrays.stream(tryHandler.pairs)
-        .map(pair -> pair.getType())
+        .map(TypeAddrPair::getType)
         .map(type -> new TypeSubject(inspector, type));
   }
 
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/FoundClassSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundClassSubject.java
index a676666..4220b50 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/FoundClassSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundClassSubject.java
@@ -34,6 +34,7 @@
 import com.android.tools.r8.retrace.RetraceTypeResult;
 import com.android.tools.r8.retrace.RetracedField;
 import com.android.tools.r8.retrace.Retracer;
+import com.android.tools.r8.synthesis.SyntheticItemsTestUtils;
 import com.android.tools.r8.utils.DescriptorUtils;
 import com.android.tools.r8.utils.StringUtils;
 import com.android.tools.r8.utils.ZipUtils;
@@ -389,6 +390,11 @@
   }
 
   @Override
+  public ClassReference getOriginalReference() {
+    return Reference.classFromDescriptor(getOriginalDescriptor());
+  }
+
+  @Override
   public ClassReference getFinalReference() {
     return Reference.classFromDescriptor(getFinalDescriptor());
   }
@@ -430,7 +436,9 @@
 
   @Override
   public boolean isSynthesizedJavaLambdaClass() {
-    return dexClass.type.getName().contains("$Lambda$");
+    // TODO(141287349): Make this precise based on the map input.
+    return SyntheticItemsTestUtils.isExternalLambda(getOriginalReference())
+        || SyntheticItemsTestUtils.isExternalLambda(getFinalReference());
   }
 
   @Override
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/InstructionSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/InstructionSubject.java
index 56436e9..8b34260 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/InstructionSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/InstructionSubject.java
@@ -43,6 +43,8 @@
 
   boolean isInvokeSpecial();
 
+  boolean isInvokeDynamic();
+
   DexMethod getMethod();
 
   boolean isNop();
diff --git a/tools/apk_utils.py b/tools/apk_utils.py
old mode 100644
new mode 100755
index 9155657..c3c616b
--- a/tools/apk_utils.py
+++ b/tools/apk_utils.py
@@ -1,12 +1,40 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
 # Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
 # for details. All rights reserved. Use of this source code is governed by a
 # BSD-style license that can be found in the LICENSE file.
 
+import optparse
 import os
 import subprocess
+import sys
 import utils
 
+USAGE = 'usage: %prog [options] <apk>'
+
+def parse_options():
+  parser = optparse.OptionParser(usage=USAGE)
+  parser.add_option('--keystore',
+                    help='keystore file (default ~/.android/app.keystore)',
+                    default='~/.android/app.keystore')
+  parser.add_option('--sign',
+                    help='Sign the passed in apk.',
+                    default=False,
+                    action='store_true')
+  parser.add_option('--use_apksigner',
+                    help='Use apksigner to sign.',
+                    default=False,
+                    action='store_true')
+  parser.add_option('--output',
+                    help='Where to put the signed apk.)',
+                    default=None)
+
+  (options, args) = parser.parse_args()
+  if len(args) != 1:
+    parser.error('Expected <apk> argument, got: ' + ' '.join(args))
+  apk = args[0]
+  return (options, apk)
+
+
 def sign(unsigned_apk, signed_apk, keystore, quiet=False, logging=True):
   utils.Print('Signing (ignore the warnings)', quiet=quiet)
   cmd = ['zip', '-d', unsigned_apk, 'META-INF/*']
@@ -37,3 +65,22 @@
     unsigned_apk
   ]
   utils.RunCmd(cmd, quiet=quiet, logging=logging)
+
+
+def main():
+  (options, apk) = parse_options()
+  if options.sign:
+    if not options.output:
+      print('When signing you must specify an output apk')
+      return 1
+    if not options.keystore:
+      print('When signing you must specify a keystore')
+      return 1
+    if options.use_apksigner:
+      sign_with_apksigner(apk, options.output, options.keystore)
+    else:
+      sign(apk, options.output, options.keystore)
+  return 0
+
+if __name__ == '__main__':
+  sys.exit(main())
diff --git a/tools/r8_release.py b/tools/r8_release.py
index 31bb4c6..179a50f 100755
--- a/tools/r8_release.py
+++ b/tools/r8_release.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
 # Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
 # for details. All rights reserved. Use of this source code is governed by a
 # BSD-style license that can be found in the LICENSE file.
@@ -41,7 +41,7 @@
 
 def prepare_release(args):
   if args.version:
-    print "Cannot manually specify version when making a dev release."
+    print("Cannot manually specify version when making a dev release.")
     sys.exit(1)
 
   def make_release(args):
@@ -57,13 +57,13 @@
           if result:
             break
         if not result or not result.group(1):
-          print 'Failed to find version label matching %s(\d+)-dev'\
-                % R8_DEV_BRANCH
+          print('Failed to find version label matching %s(\d+)-dev'\
+                % R8_DEV_BRANCH)
           sys.exit(1)
         try:
           patch_version = int(result.group(1))
         except ValueError:
-          print 'Failed to convert version to integer: %s' % result.group(1)
+          print('Failed to convert version to integer: %s' % result.group(1))
 
         old_version = '%s.%s-dev' % (R8_DEV_BRANCH, patch_version)
         version = '%s.%s-dev' % (R8_DEV_BRANCH, patch_version + 1)
@@ -74,8 +74,8 @@
         other_diff = version_change_diff(
             merge_diff_output, old_version, "master")
         if not other_diff:
-          print 'Merge point from master (%s)' % commithash, \
-            'is the same as exiting release (%s).' % old_version
+          print('Merge point from master (%s)' % commithash, \
+            'is the same as exiting release (%s).' % old_version)
           sys.exit(1)
 
         if args.dev_pre_cherry_pick:
@@ -102,7 +102,7 @@
         if not args.dry_run:
           input = raw_input('Publish dev release version %s [y/N]:' % version)
           if input != 'y':
-            print 'Aborting dev release for %s' % version
+            print('Aborting dev release for %s' % version)
             sys.exit(1)
 
         maybe_check_call(args, [
@@ -139,25 +139,25 @@
 def validate_version_change_diff(version_diff_output, old_version, new_version):
   invalid = version_change_diff(version_diff_output, old_version, new_version)
   if invalid:
-    print "Unexpected diff:"
-    print "=" * 80
-    print version_diff_output
-    print "=" * 80
+    print("Unexpected diff:")
+    print("=" * 80)
+    print(version_diff_output)
+    print("=" * 80)
     accept_string = 'THE DIFF IS OK!'
     input = raw_input(
       "Accept the additonal diff as part of the release? "
       "Type '%s' to accept: " % accept_string)
     if input != accept_string:
-      print "You did not type '%s'" % accept_string
-      print 'Aborting dev release for %s' % version
+      print("You did not type '%s'" % accept_string)
+      print('Aborting dev release for %s' % version)
       sys.exit(1)
 
 
 def maybe_check_call(args, cmd):
   if args.dry_run:
-    print 'DryRun:', ' '.join(cmd)
+    print('DryRun:', ' '.join(cmd))
   else:
-    print ' '.join(cmd)
+    print(' '.join(cmd))
     return subprocess.check_call(cmd)
 
 
@@ -185,7 +185,7 @@
       if not options.use_existing_work_branch:
         subprocess.check_call(['git', 'commit', '-a', '-m', git_message])
       else:
-        print ('Not committing when --use-existing-work-branch. '
+        print('Not committing when --use-existing-work-branch. '
             + 'Commit message should be:\n\n'
             + git_message
             + '\n')
@@ -194,7 +194,7 @@
       if not options.no_upload and not options.use_existing_work_branch:
         process = subprocess.Popen(['repo', 'upload', '.', '--verify'],
                                    stdin=subprocess.PIPE)
-        return process.communicate(input='y\n')[0]
+        return process.communicate(input=b'y\n')[0]
 
 
 def prepare_aosp(args):
@@ -202,7 +202,7 @@
   assert os.path.exists(args.aosp), "Could not find AOSP path %s" % args.aosp
 
   def release_aosp(options):
-    print "Releasing for AOSP"
+    print("Releasing for AOSP")
     if options.dry_run:
       return 'DryRun: omitting AOSP release for %s' % options.version
 
@@ -228,7 +228,7 @@
     gfile = '/bigstore/r8-releases/raw/%s/r8lib.zip' % args.version
     release_id = gmaven_publisher_stage(options, [gfile])
 
-    print "Staged Release ID " + release_id + ".\n"
+    print("Staged Release ID " + release_id + ".\n")
     gmaven_publisher_stage_redir_test_info(
         release_id, "com.android.tools:r8:%s" % args.version, "r8lib.jar")
 
@@ -236,13 +236,13 @@
     input = raw_input("Continue with publishing [y/N]:")
 
     if input != 'y':
-      print 'Aborting release to Google maven'
+      print('Aborting release to Google maven')
       sys.exit(1)
 
     gmaven_publisher_publish(args, release_id)
 
-    print
-    print "Published. Use the email workflow for approval."
+    print("")
+    print("Published. Use the email workflow for approval.")
 
   return release_maven
 
@@ -275,7 +275,7 @@
                                        % args.studio)
 
   def release_studio(options):
-    print "Releasing for STUDIO"
+    print("Releasing for STUDIO")
     if options.dry_run:
       return 'DryRun: omitting studio release for %s' % options.version
 
@@ -329,7 +329,7 @@
 
 def download_gfile(gfile, dst):
   if not gfile.startswith('/bigstore/r8-releases'):
-    print 'Unexpected gfile prefix for %s' % gfile
+    print('Unexpected gfile prefix for %s' % gfile)
     sys.exit(1)
 
   urllib.urlretrieve(
@@ -348,7 +348,7 @@
     check_no_google3_client(args, args.p4_client)
 
   def release_google3(options):
-    print "Releasing for Google 3"
+    print("Releasing for Google 3")
     if options.dry_run:
       return 'DryRun: omitting g3 release for %s' % options.version
 
@@ -362,7 +362,7 @@
       g4_open('src.jar')
       g4_open('lib.jar')
       g4_open('lib.jar.map')
-      g4_open('retrace.jar')
+      g4_open('retrace_lib.jar')
       g4_open('desugar_jdk_libs_configuration.jar')
       download_file(options.version, 'r8-full-exclude-deps.jar', 'full.jar')
       download_file(options.version, 'r8-src.jar', 'src.jar')
@@ -371,7 +371,7 @@
           options.version, 'r8lib-exclude-deps.jar.map', 'lib.jar.map')
       download_file(options.version, 'desugar_jdk_libs_configuration.jar',
                     'desugar_jdk_libs_configuration.jar')
-      download_file(options.version, 'r8retrace-exclude-deps.jar', 'retrace.jar')
+      download_file(options.version, 'r8retrace-exclude-deps.jar', 'retrace_lib.jar')
       g4_open('METADATA')
       sed(r'[1-9]\.[0-9]{1,2}\.[0-9]{1,3}-dev',
           options.version,
@@ -456,7 +456,7 @@
       if not args.use_existing_work_branch:
         subprocess.check_call(['git', 'commit', '-a', '-m', git_message])
       else:
-        print ('Not committing when --use-existing-work-branch. '
+        print('Not committing when --use-existing-work-branch. '
             + 'Commit message should be:\n\n'
             + git_message
             + '\n')
@@ -497,23 +497,23 @@
         release_id = gmaven_publisher_stage(
             args, [library_gfile, configuration_gfile])
 
-        print "Staged Release ID " + release_id + ".\n"
+        print("Staged Release ID " + release_id + ".\n")
         gmaven_publisher_stage_redir_test_info(
             release_id,
             "com.android.tools:%s:%s" % (DESUGAR_JDK_LIBS, library_version),
             library_jar)
 
-        print
+        print("")
         input = raw_input("Continue with publishing [y/N]:")
 
         if input != 'y':
-          print 'Aborting release to Google maven'
+          print('Aborting release to Google maven')
           sys.exit(1)
 
         gmaven_publisher_publish(args, release_id)
 
-        print
-        print "Published. Use the email workflow for approval."
+        print("")
+        print("Published. Use the email workflow for approval.")
 
   return make_release
 
@@ -524,7 +524,7 @@
   dirs = os.listdir(
     os.path.join('com', 'android', 'tools', DESUGAR_JDK_LIBS_CONFIGURATION))
   if len(dirs) != 1:
-    print 'Unexpected archive content, %s' + dirs
+    print('Unexpected archive content, %s' + dirs)
     sys.exit(1)
 
   version = dirs[0]
@@ -537,14 +537,14 @@
     '%s-%s.pom' % (DESUGAR_JDK_LIBS_CONFIGURATION, version))
   version_from_pom = extract_version_from_pom(pom_file)
   if version != version_from_pom:
-    print 'Version mismatch, %s != %s' % (version, version_from_pom)
+    print('Version mismatch, %s != %s' % (version, version_from_pom))
     sys.exit(1)
 
 def check_no_google3_client(args, client_name):
   if not args.use_existing_work_branch:
     clients = subprocess.check_output('g4 myclients', shell=True)
     if ':%s:' % client_name in clients:
-      print ("Remove the existing '%s' client before continuing " +
+      print("Remove the existing '%s' client before continuing " +
              "(force delete: 'g4 citc -d -f %s'), " +
              "or use option --use-existing-work-branch.") % (client_name, client_name)
       sys.exit(1)
@@ -563,11 +563,11 @@
 
 def gmaven_publisher_stage(args, gfiles):
   if args.dry_run:
-    print 'Dry-run, would have staged %s' % gfiles
+    print('Dry-run, would have staged %s' % gfiles)
     return 'dry-run-release-id'
 
-  print "Staging: %s" % ', '.join(gfiles)
-  print
+  print("Staging: %s" % ', '.join(gfiles))
+  print("")
 
   cmd = [GMAVEN_PUBLISHER, 'stage', '--gfile', ','.join(gfiles)]
   output = subprocess.check_output(cmd)
@@ -579,13 +579,13 @@
 
   matches = GMAVEN_PUBLISH_STAGE_RELEASE_ID_PATTERN.findall(output)
   if matches == None or len(matches) > 1:
-    print ("Could not determine the release ID from the gmaven_publisher " +
-           "output. Expected a line with 'Release ID = <release id>'.")
-    print "Output was:"
-    print output
+    print("Could not determine the release ID from the gmaven_publisher " +
+          "output. Expected a line with 'Release ID = <release id>'.")
+    print("Output was:")
+    print(output)
     sys.exit(1)
 
-  print output
+  print(output)
 
   release_id = matches[0]
   return release_id
@@ -604,7 +604,7 @@
                 + "-Dartifact=%s "
                 + "-Ddest=%s") % (artifact, dst)
 
-  print """To test the staged content with 'redir' run:
+  print("""To test the staged content with 'redir' run:
 
 %s
 
@@ -620,12 +620,12 @@
 
 rm -rf /tmp/maven_repo_local
 %s
-""" % (redir_command, get_command)
+""" % (redir_command, get_command))
 
 
 def gmaven_publisher_publish(args, release_id):
   if args.dry_run:
-    print 'Dry-run, would have published %s' % release_id
+    print('Dry-run, would have published %s' % release_id)
     return
 
   cmd = [GMAVEN_PUBLISHER, 'publish', release_id]
@@ -636,11 +636,11 @@
   for line in diff.splitlines():
     if line.startswith('-R8') and \
         line != "-R8_DEV_BRANCH = '%s'" % old_version:
-      print line
+      print(line)
       invalid_line = line
     elif line.startswith('+R8') and \
         line != "+R8_DEV_BRANCH = '%s'" % new_version:
-      print line
+      print(line)
       invalid_line = line
   return invalid_line
 
@@ -648,15 +648,15 @@
 def validate_branch_change_diff(version_diff_output, old_version, new_version):
   invalid = branch_change_diff(version_diff_output, old_version, new_version)
   if invalid:
-    print
-    print "The diff for the branch change in tools/release.py is not as expected:"
-    print
-    print "=" * 80
-    print version_diff_output
-    print "=" * 80
-    print
-    print "Validate the uploaded CL before landing."
-    print
+    print("")
+    print("The diff for the branch change in tools/release.py is not as expected:")
+    print("")
+    print("=" * 80)
+    print(version_diff_output)
+    print("=" * 80)
+    print("")
+    print("Validate the uploaded CL before landing.")
+    print("")
 
 
 def prepare_branch(args):
@@ -668,7 +668,7 @@
   semver = utils.check_basic_semver_version(
     branch_version, ", release branch version should be x.y", 2)
   if not semver.larger_than(current_semver):
-    print ('New branch version "'
+    print('New branch version "'
       + branch_version
       + '" must be strictly larger than the current "'
       + R8_DEV_BRANCH
@@ -703,14 +703,14 @@
         if not options.dry_run:
           input = raw_input('Create new branch for %s [y/N]:' % branch_version)
           if input != 'y':
-            print 'Aborting new branch for %s' % branch_version
+            print('Aborting new branch for %s' % branch_version)
             sys.exit(1)
 
         maybe_check_call(options, [
           'git', 'push', 'origin', 'HEAD:%s' % branch_version])
         maybe_tag(options, full_version)
 
-        print ('Updating tools/r8_release.py to make new dev releases on %s'
+        print('Updating tools/r8_release.py to make new dev releases on %s'
           % branch_version)
 
         subprocess.check_call(['git', 'new-branch', 'update-release-script'])
@@ -723,7 +723,7 @@
           if result:
             break
         if not result or not result.group(1):
-          print 'Failed to find version label in %s' % THIS_FILE_RELATIVE
+          print('Failed to find version label in %s' % THIS_FILE_RELATIVE)
           sys.exit(1)
 
         # Update this file with the new dev branch.
@@ -742,9 +742,9 @@
 
         maybe_check_call(options, ['git', 'cl', 'upload', '-f', '-m', message])
 
-        print
-        print 'Make sure to send out the branch change CL for review.'
-        print
+        print('')
+        print('Make sure to send out the branch change CL for review.')
+        print('')
 
   return make_branch
 
@@ -826,12 +826,12 @@
       and args.version
       and not 'dev' in args.version
       and args.bug == []):
-    print ("When releasing a release version to Android Studio add the "
+    print("When releasing a release version to Android Studio add the "
            + "list of bugs by using '--bug'")
     sys.exit(1)
 
   if args.version and not 'dev' in args.version and args.google3:
-    print "You should not roll a release version into google 3"
+    print("You should not roll a release version into google 3")
     sys.exit(1)
 
   return args
@@ -843,13 +843,13 @@
 
   if args.new_dev_branch:
     if args.google3 or args.studio or args.aosp:
-      print 'Cannot create a branch and roll at the same time.'
+      print('Cannot create a branch and roll at the same time.')
       sys.exit(1)
     targets_to_run.append(prepare_branch(args))
 
   if args.dev_release:
     if args.google3 or args.studio or args.aosp:
-      print 'Cannot create a dev release and roll at the same time.'
+      print('Cannot create a dev release and roll at the same time.')
       sys.exit(1)
     targets_to_run.append(prepare_release(args))
 
@@ -873,10 +873,10 @@
 
   if args.update_desugar_library_in_studio:
     if not args.studio:
-      print ("--studio required")
+      print("--studio required")
       sys.exit(1)
     if args.bug == []:
-      print ("Update studio mirror of com.android.tools:desugar_jdk_libs "
+      print("Update studio mirror of com.android.tools:desugar_jdk_libs "
              + "requires at least one bug by using '--bug'")
       sys.exit(1)
     targets_to_run.append(update_desugar_library_in_studio(args))
@@ -885,13 +885,13 @@
   for target_closure in targets_to_run:
     final_results.append(target_closure(args))
 
-  print '\n\n**************************************************************'
-  print 'PRINTING SUMMARY'
-  print '**************************************************************\n\n'
+  print('\n\n**************************************************************')
+  print('PRINTING SUMMARY')
+  print('**************************************************************\n\n')
 
   for result in final_results:
     if result is not None:
-      print result
+      print(result)
 
 
 if __name__ == '__main__':
diff --git a/tools/run_on_app.py b/tools/run_on_app.py
index 983ce6d..94eae01 100755
--- a/tools/run_on_app.py
+++ b/tools/run_on_app.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
 # 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.
@@ -215,8 +215,7 @@
 # Please add bug number for disabled permutations and please explicitly
 # do Bug: #BUG in the commit message of disabling to ensure re-enabling
 DISABLED_PERMUTATIONS = [
-  # (app, version, type), e.g., ('gmail', '180826.15', 'deploy'),
-  ('youtube', '15.09', 'deploy'), # b/150267318
+  # (app, version, type), e.g., ('gmail', '180826.15', 'deploy')
 ]
 
 def get_permutations():
@@ -232,7 +231,7 @@
   }
   # Check to ensure that we add all variants here.
   assert len(APPS) == len(data_providers)
-  for app, data in data_providers.iteritems():
+  for app, data in data_providers.items():
     for version in data.VERSIONS:
       for type in data.VERSIONS[version]:
         if (app, version, type) not in DISABLED_PERMUTATIONS:
@@ -276,9 +275,9 @@
   else:
     working = 1024 * 8
   exit_code = 0
-  range = options.find_min_xmx_range_size
+  range = int(options.find_min_xmx_range_size)
   while working - not_working > range:
-    next_candidate = working - ((working - not_working)/2)
+    next_candidate = int(working - ((working - not_working)/2))
     print('working: %s, non_working: %s, next_candidate: %s' %
           (working, not_working, next_candidate))
     extra_args = ['-Xmx%sM' % next_candidate]
diff --git a/tools/toolhelper.py b/tools/toolhelper.py
index 45d84e2..1cfd0f0 100644
--- a/tools/toolhelper.py
+++ b/tools/toolhelper.py
@@ -27,6 +27,10 @@
   cmd.append(jdk.GetJavaExecutable())
   if extra_args:
     cmd.extend(extra_args)
+  agent, args = extract_debug_agent_from_args(args)
+  if agent:
+    cmd.append(
+        '-agentlib:jdwp=transport=dt_socket,server=y,suspend=y,address=*:5005')
   if debug:
     cmd.append('-ea')
   if profile:
@@ -103,3 +107,13 @@
     else:
       args.append(arg)
   return lib, args
+
+def extract_debug_agent_from_args(input_args):
+  agent = False
+  args = []
+  for arg in input_args:
+    if arg in ('--debug-agent', '--debug_agent'):
+      agent = True
+    else:
+      args.append(arg)
+  return agent, args
diff --git a/tools/update_prebuilds_in_android.py b/tools/update_prebuilds_in_android.py
index fe8a3c0..3f30964 100755
--- a/tools/update_prebuilds_in_android.py
+++ b/tools/update_prebuilds_in_android.py
@@ -63,8 +63,8 @@
             ', as' + dest + ' does not exist already')
 
 def copy_jar_targets(root, target_root, jar_targets, maps):
-  srcs = map((lambda t: t[0] + '.jar'), jar_targets)
-  dests = map((lambda t: t[1] + '.jar'), jar_targets)
+  srcs = list(map((lambda t: t[0] + '.jar'), jar_targets))
+  dests = list(map((lambda t: t[1] + '.jar'), jar_targets))
   copy_targets(root, target_root, srcs, dests, maps=maps)
 
 def copy_other_targets(root, target_root):
@@ -88,7 +88,7 @@
 
 def main_download(hash, maps, targets, target_root, version):
   jar_targets = JAR_TARGETS_MAP[targets]
-  final_targets = map((lambda t: t[0] + '.jar'), jar_targets) + OTHER_TARGETS
+  final_targets = list(map((lambda t: t[0] + '.jar'), jar_targets)) + OTHER_TARGETS
   with utils.TempDir() as root:
     for target in final_targets:
       if hash:
diff --git a/tools/utils.py b/tools/utils.py
index ee49000..a55ea58 100644
--- a/tools/utils.py
+++ b/tools/utils.py
@@ -169,13 +169,12 @@
   logger = ProgressLogger(quiet=quiet) if logging else None
   failed = False
   while True:
-    line = process.stdout.readline()
-    if line != b'':
+    line = process.stdout.readline().decode('utf-8')
+    if line != '':
       stripped = line.rstrip()
       stdout.append(stripped)
       if logger:
         logger.log(stripped)
-
       # TODO(christofferqa): r8 should fail with non-zero exit code.
       if ('AssertionError:' in stripped
           or 'CompilationError:' in stripped
@@ -269,7 +268,7 @@
   cmd = ['git', 'rev-parse', 'HEAD']
   PrintCmd(cmd)
   with ChangedWorkingDirectory(checkout):
-    return subprocess.check_output(cmd).strip()
+    return subprocess.check_output(cmd).decode('utf-8').strip()
 
 def makedirs_if_needed(path):
   try:
@@ -512,7 +511,7 @@
 def check_java_version():
   cmd= [jdk.GetJavaExecutable(), '-version']
   output = subprocess.check_output(cmd, stderr = subprocess.STDOUT)
-  m = re.search('openjdk version "([^"]*)"', output)
+  m = re.search('openjdk version "([^"]*)"', output.decode('utf-8'))
   if m is None:
     raise Exception("Can't check java version: no version string in output"
         " of 'java -version': '{}'".format(output))