Revert changes to hygienic synthetics.

This includes:

  Revert "Reland "Hygienic lambda desugaring.""
  Commit 0a22c262e790a2b93a2f9940f14d481736b95b9c.

  Revert "Hygienic desugaring of try-with-resource $closeResource."
  Commit 286bdd43da638250433c62704937a3ab8a28b9c7.

  Revert "Hygienic service loader rewriting."
  Commit f637b908436d2cdeb457ad0bfc666798e9c39f0a.

  Revert "Disable failing test."
  Commit 000bc0cad75eba0f4df6b48997311002f20b24a4.

Change-Id: Ia4edaf2d3784d5cb92a3f3acd4993cc53dedd391
diff --git a/src/main/java/com/android/tools/r8/D8.java b/src/main/java/com/android/tools/r8/D8.java
index b762557..4def5d0 100644
--- a/src/main/java/com/android/tools/r8/D8.java
+++ b/src/main/java/com/android/tools/r8/D8.java
@@ -271,9 +271,6 @@
         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,
@@ -318,9 +315,6 @@
         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/L8.java b/src/main/java/com/android/tools/r8/L8.java
index e611c5a..f610d83 100644
--- a/src/main/java/com/android/tools/r8/L8.java
+++ b/src/main/java/com/android/tools/r8/L8.java
@@ -133,9 +133,6 @@
       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/R8.java b/src/main/java/com/android/tools/r8/R8.java
index 125b84d..9e7a967 100644
--- a/src/main/java/com/android/tools/r8/R8.java
+++ b/src/main/java/com/android/tools/r8/R8.java
@@ -838,21 +838,12 @@
           appView.getSyntheticItems().computeFinalSynthetics(appView);
       if (result != null) {
         if (appView.appInfo().hasLiveness()) {
-          if (result.lens == null) {
-            appViewWithLiveness.setAppInfo(
-                appViewWithLiveness.appInfo().rebuildWithLiveness(result.commit));
-          } else {
-            appViewWithLiveness.rewriteWithLensAndApplication(
-                result.lens, result.commit.getApplication().asDirect());
-          }
-          appViewWithLiveness.pruneItems(result.prunedItems);
+          appViewWithLiveness.setAppInfo(
+              appViewWithLiveness.appInfo().rebuildWithLiveness(result.commit));
         } 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.
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 ccfd713..977dafd 100644
--- a/src/main/java/com/android/tools/r8/code/Instruction.java
+++ b/src/main/java/com/android/tools/r8/code/Instruction.java
@@ -332,9 +332,6 @@
   @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 bbe4a90..ef6b7cd 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.SyntheticNaming;
+import com.android.tools.r8.synthesis.SyntheticItems;
 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 SyntheticNaming.verifyNotInternalSynthetic(type);
+        assert SyntheticItems.verifyNotInternalSynthetic(type);
       }
     }
 
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 7da0bc5..917f0de 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.SyntheticNaming;
+import com.android.tools.r8.synthesis.SyntheticItems;
 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 SyntheticNaming.verifyNotInternalSynthetic(type);
+      assert SyntheticItems.verifyNotInternalSynthetic(type);
       return types.add(type);
     }
 
@@ -790,7 +790,7 @@
 
     @Override
     public boolean addType(DexType type) {
-      assert SyntheticNaming.verifyNotInternalSynthetic(type);
+      assert SyntheticItems.verifyNotInternalSynthetic(type);
       return maybeInsert(type, types, base.types);
     }
 
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 302dff4..cfacdbb 100644
--- a/src/main/java/com/android/tools/r8/graph/AppInfo.java
+++ b/src/main/java/com/android/tools/r8/graph/AppInfo.java
@@ -123,6 +123,11 @@
     }
   }
 
+  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/CfCode.java b/src/main/java/com/android/tools/r8/graph/CfCode.java
index 4d45d55..eb83898 100644
--- a/src/main/java/com/android/tools/r8/graph/CfCode.java
+++ b/src/main/java/com/android/tools/r8/graph/CfCode.java
@@ -247,9 +247,6 @@
   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/DexAnnotation.java b/src/main/java/com/android/tools/r8/graph/DexAnnotation.java
index 3483fbb..bcb7974 100644
--- a/src/main/java/com/android/tools/r8/graph/DexAnnotation.java
+++ b/src/main/java/com/android/tools/r8/graph/DexAnnotation.java
@@ -14,8 +14,6 @@
 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;
@@ -401,18 +399,13 @@
   }
 
   public static DexAnnotation createAnnotationSynthesizedClass(
-      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));
+      DexType synthesizingContext, DexItemFactory dexItemFactory) {
+    DexValueType value = new DexValueType(synthesizingContext);
+    DexAnnotationElement element = new DexAnnotationElement(dexItemFactory.valueString, value);
     return new DexAnnotation(
         VISIBILITY_BUILD,
         new DexEncodedAnnotation(
-            dexItemFactory.annotationSynthesizedClass,
-            new DexAnnotationElement[] {kindElement, typeElement}));
+            dexItemFactory.annotationSynthesizedClass, new DexAnnotationElement[] {element}));
   }
 
   public static boolean hasSynthesizedClassAnnotation(
@@ -420,7 +413,7 @@
     return getSynthesizedClassAnnotationContextType(annotations, factory) != null;
   }
 
-  public static Pair<SyntheticKind, DexType> getSynthesizedClassAnnotationContextType(
+  public static DexType getSynthesizedClassAnnotationContextType(
       DexAnnotationSet annotations, DexItemFactory factory) {
     if (annotations.size() != 1) {
       return null;
@@ -429,31 +422,17 @@
     if (annotation.annotation.type != factory.annotationSynthesizedClass) {
       return null;
     }
-    if (annotation.annotation.elements.length != 2) {
+    if (annotation.annotation.elements.length != 1) {
       return null;
     }
-    assert factory.kindString.isLessThan(factory.valueString);
-    DexAnnotationElement kindElement = annotation.annotation.elements[0];
-    if (kindElement.name != factory.kindString) {
+    DexAnnotationElement element = annotation.annotation.elements[0];
+    if (element.name != factory.valueString) {
       return null;
     }
-    if (!kindElement.value.isDexValueString()) {
+    if (!element.value.isDexValueType()) {
       return null;
     }
-    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());
+    return element.value.asDexValueType().getValue();
   }
 
   public static DexAnnotation createAnnotationSynthesizedClassMap(
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 2236b6c..91203c8 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
@@ -76,12 +76,14 @@
 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.StructuralItem;
-import com.android.tools.r8.utils.structural.StructuralMapping;
+import com.android.tools.r8.utils.structural.RepresentativeMap;
 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;
@@ -93,8 +95,7 @@
 import java.util.function.IntPredicate;
 import org.objectweb.asm.Opcodes;
 
-public class DexEncodedMethod extends DexEncodedMember<DexEncodedMethod, DexMethod>
-    implements StructuralItem<DexEncodedMethod> {
+public class DexEncodedMethod extends DexEncodedMember<DexEncodedMethod, DexMethod> {
 
   public static final String CONFIGURATION_DEBUGGING_PREFIX = "Shaking error: Missing method in ";
 
@@ -337,16 +338,6 @@
     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) {
@@ -365,12 +356,28 @@
             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 code1.asDexCode().acceptCompareTo(code2.asDexCode(), visitor);
+      return visitor.visit(code1.asDexCode(), code2.asDexCode(), DexCode::compareTo);
     }
     throw new Unreachable(
         "Unexpected attempt to compare incompatible synthetic objects: " + code1 + " and " + code2);
@@ -380,7 +387,9 @@
     if (code.isCfCode()) {
       code.asCfCode().acceptHashing(visitor);
     } else {
-      code.asDexCode().acceptHashing(visitor);
+      // TODO(b/158159959): Implement a more precise hashing on code objects.
+      assert code.isDexCode();
+      visitor.visitInt(code.hashCode());
     }
   }
 
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 2591960..e6d824a 100644
--- a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
+++ b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
@@ -4,6 +4,7 @@
 package com.android.tools.r8.graph;
 
 import static com.android.tools.r8.ir.analysis.type.ClassTypeElement.computeLeastUpperBoundOfInterfaces;
+import static com.android.tools.r8.ir.optimize.ServiceLoaderRewriter.SERVICE_LOADER_CLASS_NAME;
 
 import com.android.tools.r8.dex.Constants;
 import com.android.tools.r8.dex.Marker;
@@ -298,7 +299,6 @@
   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);
@@ -369,6 +369,8 @@
       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);
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 7d69899..2a6ebf5 100644
--- a/src/main/java/com/android/tools/r8/graph/DexProgramClass.java
+++ b/src/main/java/com/android/tools/r8/graph/DexProgramClass.java
@@ -21,9 +21,6 @@
 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;
@@ -40,7 +37,7 @@
 import java.util.function.Supplier;
 
 public class DexProgramClass extends DexClass
-    implements ProgramDefinition, Supplier<DexProgramClass>, StructuralItem<DexProgramClass> {
+    implements ProgramDefinition, Supplier<DexProgramClass> {
 
   @FunctionalInterface
   public interface ChecksumSupplier {
@@ -147,36 +144,6 @@
     synthesizedDirectlyFrom.forEach(this::addSynthesizedFrom);
   }
 
-  @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)));
   }
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 d2fb517..27284ea 100644
--- a/src/main/java/com/android/tools/r8/graph/DexType.java
+++ b/src/main/java/com/android/tools/r8/graph/DexType.java
@@ -8,6 +8,7 @@
 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,8 +17,10 @@
 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.shaking.AppInfoWithLiveness;
-import com.android.tools.r8.synthesis.SyntheticNaming;
+import com.android.tools.r8.synthesis.SyntheticItems;
 import com.android.tools.r8.utils.DescriptorUtils;
 import com.android.tools.r8.utils.InternalOptions.OutlineOptions;
 import com.android.tools.r8.utils.structural.CompareToVisitor;
@@ -38,13 +41,7 @@
   // 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",
-          "$r8$twr$utility",
-          "$-DC",
-          "$$ServiceLoaderMethods",
-          "-$$Lambda$");
+      ImmutableList.of("$r8$backportedMethods$utility", "$r8$java8methods$utility", "$-DC");
 
   public final DexString descriptor;
   private String toStringCache = null;
@@ -305,6 +302,12 @@
   }
 
   // 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
@@ -315,7 +318,8 @@
         || name.contains(ENUM_UNBOXING_UTILITY_CLASS_SUFFIX)
         || name.contains(SyntheticArgumentClass.SYNTHETIC_CLASS_SUFFIX)
         // New and hygienic synthesis infrastructure.
-        || SyntheticNaming.isSyntheticName(name)
+        || name.contains(SyntheticItems.INTERNAL_SYNTHETIC_CLASS_SEPARATOR)
+        || name.contains(SyntheticItems.EXTERNAL_SYNTHETIC_CLASS_SEPARATOR)
         // Only generated in core lib.
         || name.contains(EMULATE_LIBRARY_CLASS_NAME_SUFFIX)
         || name.contains(TYPE_WRAPPER_SUFFIX)
@@ -333,9 +337,12 @@
   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_GROUP_CLASS_NAME_PREFIX) // Could collide.
+    return name.contains(LAMBDA_CLASS_NAME_PREFIX) // Could collide.
+        || name.contains(LAMBDA_GROUP_CLASS_NAME_PREFIX) // Could collide.
         || name.contains(OutlineOptions.CLASS_NAME) // Global singleton.
-        || name.contains(NestBasedAccessDesugaring.NEST_CONSTRUCTOR_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.
   }
 
   private boolean oldSynthesizedName(String name) {
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 2328408..0e3d784 100644
--- a/src/main/java/com/android/tools/r8/graph/GraphLens.java
+++ b/src/main/java/com/android/tools/r8/graph/GraphLens.java
@@ -5,6 +5,7 @@
 
 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;
@@ -619,7 +620,7 @@
         // that they can be mapped back to the original program.
         DexField originalField = getOriginalFieldSignature(field.getReference());
         assert originalFields.contains(originalField)
-                || isD8R8SynthesizedField(originalField, appView)
+                || isD8R8SynthesizedField(originalField, dexItemFactory)
             : "Unable to map field `"
                 + field.getReference().toSourceString()
                 + "` back to original program";
@@ -637,16 +638,16 @@
     return true;
   }
 
-  private boolean isD8R8SynthesizedField(DexField field, AppView<?> appView) {
+  private boolean isD8R8SynthesizedField(DexField field, DexItemFactory dexItemFactory) {
     // TODO(b/167947782): Should be a general check to see if the field is D8/R8 synthesized
     //  instead of relying on field names.
-    if (field.match(appView.dexItemFactory().objectMembers.clinitField)) {
+    if (field.match(dexItemFactory.objectMembers.clinitField)) {
       return true;
     }
     if (field.getName().toSourceString().equals(CLASS_ID_FIELD_NAME)) {
       return true;
     }
-    if (appView.getSyntheticItems().isSyntheticClass(field.getHolderType())
+    if (field.getHolderType().toSourceString().contains(LAMBDA_CLASS_NAME_PREFIX)
         && field.getName().toSourceString().equals(LAMBDA_INSTANCE_FIELD_NAME)) {
       return true;
     }
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 c9969a6..e007f4e 100644
--- a/src/main/java/com/android/tools/r8/graph/NestHostClassAttribute.java
+++ b/src/main/java/com/android/tools/r8/graph/NestHostClassAttribute.java
@@ -5,19 +5,12 @@
 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 implements StructuralItem<NestHostClassAttribute> {
+public class NestHostClassAttribute {
 
   private final DexType nestHost;
 
-  private static void specify(StructuralSpecification<NestHostClassAttribute, ?> spec) {
-    spec.withItem(a -> a.nestHost);
-  }
-
   public NestHostClassAttribute(DexType nestHost) {
     this.nestHost = nestHost;
   }
@@ -34,14 +27,4 @@
     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 0d7c19d..f9d1a35 100644
--- a/src/main/java/com/android/tools/r8/graph/NestMemberClassAttribute.java
+++ b/src/main/java/com/android/tools/r8/graph/NestMemberClassAttribute.java
@@ -5,21 +5,14 @@
 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 implements StructuralItem<NestMemberClassAttribute> {
+public class NestMemberClassAttribute {
 
   private final DexType nestMember;
 
-  private static void specify(StructuralSpecification<NestMemberClassAttribute, ?> spec) {
-    spec.withItem(a -> a.nestMember);
-  }
-
   public NestMemberClassAttribute(DexType nestMember) {
     this.nestMember = nestMember;
   }
@@ -36,14 +29,4 @@
     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/TreeFixerBase.java b/src/main/java/com/android/tools/r8/graph/TreeFixerBase.java
index c1d5981..a10db7147 100644
--- a/src/main/java/com/android/tools/r8/graph/TreeFixerBase.java
+++ b/src/main/java/com/android/tools/r8/graph/TreeFixerBase.java
@@ -15,7 +15,7 @@
   private final AppView<?> appView;
   private final DexItemFactory dexItemFactory;
 
-  private final Map<DexType, DexProgramClass> programClassCache = new IdentityHashMap<>();
+  private Map<DexType, DexProgramClass> newProgramClasses = null;
   private final Map<DexType, DexProgramClass> synthesizedFromClasses = new IdentityHashMap<>();
   private final Map<DexProto, DexProto> protoFixupCache = new IdentityHashMap<>();
 
@@ -53,13 +53,13 @@
   }
 
   /** Fixup a collection of classes. */
-  public List<DexProgramClass> fixupClasses(Collection<DexProgramClass> classes) {
-    List<DexProgramClass> newProgramClasses = new ArrayList<>();
+  public Collection<DexProgramClass> fixupClasses(Collection<DexProgramClass> classes) {
+    assert newProgramClasses == null;
+    newProgramClasses = new IdentityHashMap<>();
     for (DexProgramClass clazz : classes) {
-      newProgramClasses.add(
-          programClassCache.computeIfAbsent(clazz.getType(), ignore -> fixupClass(clazz)));
+      newProgramClasses.computeIfAbsent(clazz.getType(), ignore -> fixupClass(clazz));
     }
-    return newProgramClasses;
+    return newProgramClasses.values();
   }
 
   // Should remain private as the correctness of the fixup requires the lazy 'newProgramClasses'.
@@ -70,7 +70,7 @@
             clazz.getOriginKind(),
             clazz.getOrigin(),
             clazz.getAccessFlags(),
-            clazz.superType == null ? null : fixupType(clazz.superType),
+            fixupType(clazz.superType),
             fixupTypeList(clazz.interfaces),
             clazz.getSourceFile(),
             fixupNestHost(clazz.getNestHostClassAttribute()),
@@ -250,6 +250,7 @@
   // 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;
     }
@@ -260,7 +261,7 @@
       //  is no longer in the application?
       Map<DexType, DexProgramClass> classes =
           appView.appInfo().definitionForWithoutExistenceAssert(clazz.getType()) != null
-              ? programClassCache
+              ? newProgramClasses
               : synthesizedFromClasses;
       DexProgramClass newClass =
           classes.computeIfAbsent(clazz.getType(), ignore -> fixupClass(clazz));
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 aa79316..d08eb3e 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
@@ -240,9 +240,7 @@
           new DesugaredLibraryAPIConverter(appView, Mode.GENERATE_CALLBACKS_AND_WRAPPERS);
       this.backportedMethodRewriter = new BackportedMethodRewriter(appView);
       this.twrCloseResourceRewriter =
-          TwrCloseResourceRewriter.enableTwrCloseResourceDesugaring(appView.options())
-              ? new TwrCloseResourceRewriter(appView)
-              : null;
+          enableTwrCloseResourceDesugaring() ? new TwrCloseResourceRewriter(appView, this) : null;
       this.d8NestBasedAccessDesugaring =
           options.shouldDesugarNests() ? new D8NestBasedAccessDesugaring(appView) : null;
       this.lambdaMerger = null;
@@ -276,9 +274,8 @@
             ? new InterfaceMethodRewriter(appView, this)
             : null;
     this.twrCloseResourceRewriter =
-        (TwrCloseResourceRewriter.enableTwrCloseResourceDesugaring(options)
-                && !appView.enableWholeProgramOptimizations())
-            ? new TwrCloseResourceRewriter(appView)
+        (options.desugarState == DesugarState.ON && enableTwrCloseResourceDesugaring())
+            ? new TwrCloseResourceRewriter(appView, this)
             : null;
     this.backportedMethodRewriter =
         (options.desugarState == DesugarState.ON && !appView.enableWholeProgramOptimizations())
@@ -385,6 +382,20 @@
     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());
@@ -398,10 +409,11 @@
     }
   }
 
-  private void synthesizeLambdaClasses(ExecutorService executorService) throws ExecutionException {
+  private void synthesizeLambdaClasses(Builder<?> builder, ExecutorService executorService)
+      throws ExecutionException {
     if (lambdaRewriter != null) {
       assert !appView.enableWholeProgramOptimizations();
-      lambdaRewriter.finalizeLambdaDesugaringForD8(this, executorService);
+      lambdaRewriter.finalizeLambdaDesugaringForD8(builder, this, executorService);
     }
   }
 
@@ -423,16 +435,16 @@
       Flavor includeAllResources,
       ExecutorService executorService)
       throws ExecutionException {
-    assert !appView.getSyntheticItems().hasPendingSyntheticClasses();
     if (interfaceMethodRewriter != null) {
       interfaceMethodRewriter.desugarInterfaceMethods(
           builder, includeAllResources, executorService);
     }
   }
 
-  private void processTwrCloseResourceUtilityMethods() {
+  private void synthesizeTwrCloseResourceUtilityClass(
+      Builder<?> builder, ExecutorService executorService) throws ExecutionException {
     if (twrCloseResourceRewriter != null) {
-      twrCloseResourceRewriter.processSynthesizedMethods(this);
+      twrCloseResourceRewriter.synthesizeUtilityClass(builder, executorService, options);
     }
   }
 
@@ -509,21 +521,9 @@
     builder.setHighestSortingString(highestSortingString);
 
     desugarNestBasedAccess(builder, executor);
-
-    // 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);
-    }
-
+    synthesizeLambdaClasses(builder, executor);
     desugarInterfaceMethods(builder, ExcludeDexResources, executor);
+    synthesizeTwrCloseResourceUtilityClass(builder, executor);
     processSynthesizedJava8UtilityClasses(executor);
     synthesizeRetargetClass(builder, executor);
     synthesizeInvokeSpecialBridges(executor);
@@ -532,10 +532,10 @@
 
     timing.end();
 
-    application = builder.build();
+    DexApplication app = builder.build();
     appView.setAppInfo(
         new AppInfo(
-            appView.appInfo().getSyntheticItems().commit(application),
+            appView.appInfo().getSyntheticItems().commit(app),
             appView.appInfo().getMainDexClasses()));
   }
 
@@ -793,14 +793,6 @@
       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);
@@ -810,6 +802,7 @@
     feedback.updateVisibleOptimizationInfo();
 
     printPhase("Utility classes synthesis");
+    synthesizeTwrCloseResourceUtilityClass(builder, executorService);
     processSynthesizedJava8UtilityClasses(executorService);
     synthesizeRetargetClass(builder, executorService);
     synthesizeEnumUnboxingUtilityMethods(executorService);
@@ -821,9 +814,11 @@
     printPhase("Desugared library API Conversion finalization");
     generateDesugaredLibraryAPIWrappers(builder, executorService);
 
-    if (serviceLoaderRewriter != null) {
+    if (serviceLoaderRewriter != null && serviceLoaderRewriter.getSynthesizedClass() != null) {
+      appView.appInfo().addSynthesizedClass(serviceLoaderRewriter.getSynthesizedClass(), true);
       processSynthesizedServiceLoaderMethods(
-          serviceLoaderRewriter.getServiceLoadMethods(), executorService);
+          serviceLoaderRewriter.getSynthesizedClass(), executorService);
+      builder.addSynthesizedClass(serviceLoaderRewriter.getSynthesizedClass());
     }
 
     // Update optimization info for all synthesized methods at once.
@@ -964,10 +959,11 @@
   }
 
   private void processSynthesizedServiceLoaderMethods(
-      List<ProgramMethod> serviceLoadMethods, ExecutorService executorService)
-      throws ExecutionException {
+      DexProgramClass synthesizedClass, ExecutorService executorService) throws ExecutionException {
     ThreadUtils.processItems(
-        serviceLoadMethods, this::forEachSynthesizedServiceLoaderMethod, executorService);
+        synthesizedClass::forEachProgramMethod,
+        this::forEachSynthesizedServiceLoaderMethod,
+        executorService);
   }
 
   private void forEachSynthesizedServiceLoaderMethod(ProgramMethod method) {
@@ -1478,7 +1474,7 @@
     deadCodeRemover.run(code, timing);
     assert code.isConsistentSSA();
 
-    if (options.desugarState == DesugarState.ON && options.enableTryWithResourcesDesugaring()) {
+    if (options.desugarState == DesugarState.ON && enableTryWithResourcesDesugaring()) {
       timing.begin("Rewrite Throwable suppresed methods");
       codeRewriter.rewriteThrowableAddAndGetSuppressed(code);
       timing.end();
@@ -1565,7 +1561,7 @@
 
     if (twrCloseResourceRewriter != null) {
       timing.begin("Rewrite TWR close");
-      twrCloseResourceRewriter.rewriteIR(code);
+      twrCloseResourceRewriter.rewriteMethodCode(code);
       timing.end();
     }
 
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 a89e3f9..f348214 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
@@ -67,13 +67,6 @@
     registerDesugaredLibraryAPIConverter(method);
   }
 
-  private void registerTwrCloseResourceRewriting(DexMethod method) {
-    if (!needsDesugarging) {
-      needsDesugarging =
-          TwrCloseResourceRewriter.isTwrCloseResourceMethod(method, appView.dexItemFactory());
-    }
-  }
-
   private void registerBackportedMethodRewriting(DexMethod method) {
     if (!needsDesugarging) {
       needsDesugarging = backportedMethodRewriter.needsDesugaring(method);
@@ -106,7 +99,9 @@
 
   @Override
   public void registerInvokeStatic(DexMethod method) {
-    registerTwrCloseResourceRewriting(method);
+    if (!needsDesugarging) {
+      needsDesugarging = TwrCloseResourceRewriter.isSynthesizedCloseResourceMethod(method, appView);
+    }
     registerBackportedMethodRewriting(method);
     registerLibraryRetargeting(method, false);
     registerInterfaceMethodRewriting(method, false);
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 ace471b..40b18c9 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,6 +7,7 @@
 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;
@@ -32,7 +33,6 @@
 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.InternalOptions;
@@ -1406,13 +1406,15 @@
       return appInfo
           .getSyntheticItems()
           .createMethod(
-              SyntheticNaming.SyntheticKind.BACKPORT,
               context,
               appInfo.dexItemFactory(),
               builder ->
                   builder
                       .setProto(getProto(appInfo.dexItemFactory()))
-                      .setAccessFlags(MethodAccessFlags.createPublicStaticSynthetic())
+                      .setAccessFlags(
+                          MethodAccessFlags.fromSharedAccessFlags(
+                              Constants.ACC_PUBLIC | Constants.ACC_STATIC | Constants.ACC_SYNTHETIC,
+                              false))
                       .setCode(
                           methodSig -> generateTemplateMethod(appInfo.app().options, methodSig)));
     }
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 9ea3aa3..1529d2f 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,6 +13,7 @@
 
 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;
@@ -56,7 +57,6 @@
 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.utils.DescriptorUtils;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.IterableUtils;
@@ -406,13 +406,17 @@
             appView
                 .getSyntheticItems()
                 .createMethod(
-                    SyntheticNaming.SyntheticKind.STATIC_INTERFACE_CALL,
                     context.getHolder(),
                     factory,
                     syntheticMethodBuilder ->
                         syntheticMethodBuilder
                             .setProto(invokedMethod.proto)
-                            .setAccessFlags(MethodAccessFlags.createPublicStaticSynthetic())
+                            .setAccessFlags(
+                                MethodAccessFlags.fromSharedAccessFlags(
+                                    Constants.ACC_PUBLIC
+                                        | Constants.ACC_STATIC
+                                        | Constants.ACC_SYNTHETIC,
+                                    false))
                             .setCode(
                                 m ->
                                     ForwardMethodBuilder.builder(factory)
@@ -1124,7 +1128,6 @@
       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)) {
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 966cfec..a50581e 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,6 +8,7 @@
 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;
@@ -19,8 +20,10 @@
 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;
@@ -34,11 +37,18 @@
 import com.android.tools.r8.ir.optimize.info.OptimizationFeedbackSimple;
 import com.android.tools.r8.ir.synthetic.ForwardMethodSourceCode;
 import com.android.tools.r8.ir.synthetic.SynthesizedCode;
-import com.android.tools.r8.synthesis.SyntheticClassBuilder;
+import com.android.tools.r8.origin.SynthesizedOrigin;
 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
@@ -67,27 +77,31 @@
   final DexMethod classConstructor;
   public final DexField lambdaField;
   public final Target target;
-
-  // Considered final but is set after due to circularity in allocation.
-  private DexProgramClass clazz = null;
+  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.
 
   LambdaClass(
-      SyntheticClassBuilder 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 = builder.getFactory();
+    DexItemFactory factory = appView.dexItemFactory();
     DexProto constructorProto = factory.createProto(
         factory.voidType, descriptor.captures.values);
-    this.constructor = factory.createMethod(type, constructorProto, factory.constructorMethodName);
+    this.constructor =
+        factory.createMethod(lambdaClassType, constructorProto, factory.constructorMethodName);
 
     this.target = createTarget(accessedFrom);
 
@@ -95,32 +109,98 @@
     this.classConstructor =
         !stateless
             ? null
-            : factory.createMethod(type, constructorProto, factory.classConstructorMethodName);
+            : factory.createMethod(
+                lambdaClassType, constructorProto, factory.classConstructorMethodName);
     this.lambdaField =
-        !stateless ? null : factory.createField(type, type, rewriter.instanceFieldName);
-
-    // Synthesize the program class one all fields are set.
-    synthesizeLambdaClass(builder);
+        !stateless
+            ? null
+            : factory.createField(lambdaClassType, lambdaClassType, rewriter.instanceFieldName);
   }
 
-  public final DexProgramClass getLambdaProgramClass() {
-    assert clazz != null;
+  // 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);
+    }
     return clazz;
   }
 
-  void setClass(DexProgramClass clazz) {
-    assert this.clazz == null;
-    assert clazz != null;
-    assert type == clazz.type;
-    this.clazz = 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).
 
-  private void synthesizeLambdaClass(SyntheticClassBuilder builder) {
-    builder.setInterfaces(descriptor.interfaces);
-    synthesizeStaticFields(builder);
-    synthesizeInstanceFields(builder);
-    synthesizeDirectMethods(builder);
-    synthesizeVirtualMethods(builder);
+    // 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();
   }
 
   final DexField getCaptureField(int index) {
@@ -136,15 +216,24 @@
     return descriptor.isStateless();
   }
 
-  // Synthesize virtual methods.
-  private void synthesizeVirtualMethods(SyntheticClassBuilder builder) {
-    DexMethod mainMethod =
-        appView.dexItemFactory().createMethod(type, descriptor.erasedProto, descriptor.name);
+  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);
+      }
+    }
+  }
 
-    List<DexEncodedMethod> methods = new ArrayList<>(1 + descriptor.bridges.size());
+  // Synthesize virtual methods.
+  private DexEncodedMethod[] synthesizeVirtualMethods(DexMethod mainMethod) {
+    DexEncodedMethod[] methods = new DexEncodedMethod[1 + descriptor.bridges.size()];
+    int index = 0;
 
     // Synthesize main method.
-    methods.add(
+    methods[index++] =
         new DexEncodedMethod(
             mainMethod,
             MethodAccessFlags.fromSharedAccessFlags(
@@ -153,13 +242,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.add(
+      methods[index++] =
           new DexEncodedMethod(
               bridgeMethod,
               MethodAccessFlags.fromSharedAccessFlags(
@@ -172,18 +261,18 @@
               DexAnnotationSet.empty(),
               ParameterAnnotationsList.empty(),
               LambdaBridgeMethodSourceCode.build(this, bridgeMethod, mainMethod),
-              true));
+              true);
     }
-    builder.setVirtualMethods(methods);
+    return methods;
   }
 
   // Synthesize direct methods.
-  private void synthesizeDirectMethods(SyntheticClassBuilder builder) {
+  private DexEncodedMethod[] synthesizeDirectMethods() {
     boolean stateless = isStateless();
-    List<DexEncodedMethod> methods = new ArrayList<>(stateless ? 2 : 1);
+    DexEncodedMethod[] methods = new DexEncodedMethod[stateless ? 2 : 1];
 
     // Constructor.
-    methods.add(
+    methods[0] =
         new DexEncodedMethod(
             constructor,
             MethodAccessFlags.fromSharedAccessFlags(
@@ -194,11 +283,11 @@
             DexAnnotationSet.empty(),
             ParameterAnnotationsList.empty(),
             LambdaConstructorSourceCode.build(this),
-            true));
+            true);
 
     // Class constructor for stateless lambda classes.
     if (stateless) {
-      methods.add(
+      methods[1] =
           new DexEncodedMethod(
               classConstructor,
               MethodAccessFlags.fromSharedAccessFlags(
@@ -207,50 +296,61 @@
               DexAnnotationSet.empty(),
               ParameterAnnotationsList.empty(),
               LambdaClassConstructorSourceCode.build(this),
-              true));
-      feedback.classInitializerMayBePostponed(methods.get(1));
+              true);
+      feedback.classInitializerMayBePostponed(methods[1]);
     }
-    builder.setDirectMethods(methods);
+    return methods;
   }
 
   // Synthesize instance fields to represent captured values.
-  private void synthesizeInstanceFields(SyntheticClassBuilder builder) {
+  private DexEncodedField[] synthesizeInstanceFields() {
     DexType[] fieldTypes = descriptor.captures.values;
     int fieldCount = fieldTypes.length;
-    List<DexEncodedField> fields = new ArrayList<>(fieldCount);
+    DexEncodedField[] fields = new DexEncodedField[fieldCount];
     for (int i = 0; i < fieldCount; i++) {
       FieldAccessFlags accessFlags =
           FieldAccessFlags.fromSharedAccessFlags(
               Constants.ACC_FINAL | Constants.ACC_SYNTHETIC | Constants.ACC_PUBLIC);
-      fields.add(
+      fields[i] =
           new DexEncodedField(
               getCaptureField(i),
               accessFlags,
               FieldTypeSignature.noSignature(),
               DexAnnotationSet.empty(),
-              null));
+              null);
     }
-    builder.setInstanceFields(fields);
+    return fields;
   }
 
   // Synthesize static fields to represent singleton instance.
-  private void synthesizeStaticFields(SyntheticClassBuilder 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)));
+  private DexEncodedField[] synthesizeStaticFields() {
+    if (!isStateless()) {
+      return DexEncodedField.EMPTY_ARRAY;
     }
+
+    // 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
@@ -543,15 +643,11 @@
                         newMethod.getCode(), callTarget.getArity(), appView);
                     return newMethod;
                   });
-      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;
+
+      assert replacement != null
+          : "Unexpected failure to find direct lambda target for: " + implMethod.qualifiedName();
+
+      return new ProgramMethod(implMethodHolder, replacement);
     }
   }
 
@@ -617,27 +713,11 @@
                     rewriter.forcefullyMoveMethod(encodedMethod.method, callTarget);
                     return newMethod;
                   });
-      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;
+      return new ProgramMethod(implMethodHolder, replacement);
     }
 
     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);
@@ -682,15 +762,6 @@
       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 =
@@ -708,7 +779,11 @@
               AccessorMethodSourceCode.build(LambdaClass.this, callTarget),
               true);
 
-      accessorClass.addDirectMethod(accessorEncodedMethod);
+      // We may arrive here concurrently so we need must update the methods of the class atomically.
+      synchronized (accessorClass) {
+        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 ee459b4..f464fdd 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,6 +16,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.DexApplication.Builder;
 import com.android.tools.r8.graph.DexCallSite;
 import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.graph.DexItemFactory;
@@ -27,10 +28,19 @@
 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.synthesis.SyntheticNaming;
-import com.android.tools.r8.utils.Box;
+import com.android.tools.r8.utils.DescriptorUtils;
 import com.android.tools.r8.utils.collections.BidirectionalManyToOneRepresentativeMap;
 import com.android.tools.r8.utils.collections.BidirectionalOneToOneMap;
 import com.android.tools.r8.utils.collections.SortedProgramMethodSet;
@@ -39,8 +49,9 @@
 import com.google.common.collect.Sets;
 import java.util.ArrayList;
 import java.util.Collection;
-import java.util.Collections;
+import java.util.IdentityHashMap;
 import java.util.List;
+import java.util.ListIterator;
 import java.util.Map;
 import java.util.Set;
 import java.util.concurrent.ExecutionException;
@@ -58,6 +69,7 @@
 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";
@@ -69,10 +81,16 @@
   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.
   // NOTE: synchronize concurrent access on `knownLambdaClasses`.
-  private final List<LambdaClass> knownLambdaClasses = new ArrayList<>();
+  private final Map<DexType, LambdaClass> knownLambdaClasses = new IdentityHashMap<>();
 
   public LambdaRewriter(AppView<?> appView) {
     this.appView = appView;
@@ -120,7 +138,7 @@
           if (descriptor == null) {
             return null;
           }
-          return createLambdaClass(descriptor, method);
+          return getOrCreateLambdaClass(descriptor, method);
         });
   }
 
@@ -191,10 +209,16 @@
   }
 
   /** Generates lambda classes and adds them to the builder. */
-  public void finalizeLambdaDesugaringForD8(IRConverter converter, ExecutorService executorService)
+  public void finalizeLambdaDesugaringForD8(
+      Builder<?> builder, IRConverter converter, ExecutorService executorService)
       throws ExecutionException {
     synthesizeAccessibilityBridgesForLambdaClassesD8(
-        knownLambdaClasses, converter, executorService);
+        knownLambdaClasses.values(), converter, executorService);
+    for (LambdaClass lambdaClass : knownLambdaClasses.values()) {
+      DexProgramClass synthesizedClass = lambdaClass.getOrCreateLambdaClass();
+      appView.appInfo().addSynthesizedClass(synthesizedClass, lambdaClass.addToMainDexList.get());
+      builder.addSynthesizedClass(synthesizedClass);
+    }
     fixup();
     optimizeSynthesizedClasses(converter, executorService);
   }
@@ -202,35 +226,183 @@
   private void optimizeSynthesizedClasses(IRConverter converter, ExecutorService executorService)
       throws ExecutionException {
     converter.optimizeSynthesizedClasses(
-        knownLambdaClasses.stream()
-            .map(LambdaClass::getLambdaProgramClass)
+        knownLambdaClasses.values().stream()
+            .map(LambdaClass::getOrCreateLambdaClass)
             .collect(ImmutableSet.toImmutableSet()),
         executorService);
   }
 
-  // Creates a lambda class corresponding to the lambda descriptor and context.
-  public LambdaClass createLambdaClass(LambdaDescriptor descriptor, ProgramMethod accessedFrom) {
-    Box<LambdaClass> box = new Box<>();
-    DexProgramClass clazz =
-        appView
-            .getSyntheticItems()
-            .createClass(
-                SyntheticNaming.SyntheticKind.LAMBDA,
-                accessedFrom.getHolder(),
-                appView.dexItemFactory(),
-                builder ->
-                    box.set(new LambdaClass(builder, appView, this, accessedFrom, descriptor)));
-    // Immediately set the actual program class on the lambda.
-    LambdaClass lambdaClass = box.get();
-    lambdaClass.setClass(clazz);
-    synchronized (knownLambdaClasses) {
-      knownLambdaClasses.add(lambdaClass);
+  // 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);
     }
     return lambdaClass;
   }
 
-  public Collection<LambdaClass> getKnownLambdaClasses() {
-    return Collections.unmodifiableList(knownLambdaClasses);
+  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 NestedGraphLens fixup() {
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/TwrCloseResourceRewriter.java b/src/main/java/com/android/tools/r8/ir/desugar/TwrCloseResourceRewriter.java
index 98acb2d..f7be74d 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,137 +4,178 @@
 
 package com.android.tools.r8.ir.desugar;
 
-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.graph.AppInfo;
 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.ProgramMethod;
+import com.android.tools.r8.graph.ParameterAnnotationsList;
 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.synthesis.SyntheticNaming.SyntheticKind;
+import com.android.tools.r8.origin.SynthesizedOrigin;
+import com.android.tools.r8.shaking.MainDexClasses;
 import com.android.tools.r8.utils.InternalOptions;
-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;
+import com.google.common.collect.Sets;
+import java.util.Collections;
+import java.util.Set;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
 
-// Try with resources close-resource desugaring.
+// Try with resources outlining processor. Handles $closeResource methods
+// synthesized by java 9 compiler.
 //
-// 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.
+// 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.
 //
 // 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 DexProto twrCloseResourceProto;
-  private final List<ProgramMethod> synthesizedMethods = new ArrayList<>();
+  private final IRConverter converter;
 
-  public static boolean enableTwrCloseResourceDesugaring(InternalOptions options) {
-    return options.desugarState == DesugarState.ON
-        && options.enableTryWithResourcesDesugaring()
-        && !options.canUseTwrCloseResourceMethod();
-  }
+  private final DexMethod twrCloseResourceMethod;
 
-  public TwrCloseResourceRewriter(AppView<?> appView) {
+  private final Set<DexProgramClass> referencingClasses = Sets.newConcurrentHashSet();
+
+  public TwrCloseResourceRewriter(AppView<?> appView, IRConverter converter) {
     this.appView = appView;
+    this.converter = converter;
+
     DexItemFactory dexItemFactory = appView.dexItemFactory();
-    twrCloseResourceProto =
+    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 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;
+  public static boolean isUtilityClassDescriptor(DexType type) {
+    return type.descriptor.toString().equals(UTILITY_CLASS_DESCRIPTOR);
   }
 
   // Rewrites calls to $closeResource() method. Can be invoked concurrently.
-  public void rewriteIR(IRCode code) {
+  public void rewriteMethodCode(IRCode code) {
     InstructionListIterator iterator = code.instructionListIterator();
+    AppInfo appInfo = appView.appInfo();
     while (iterator.hasNext()) {
-      InvokeStatic invoke = iterator.next().asInvokeStatic();
-      if (invoke == null
-          || !isTwrCloseResourceMethod(invoke.getInvokedMethod(), appView.dexItemFactory())) {
+      Instruction instruction = iterator.next();
+      if (!instruction.isInvokeStatic()) {
+        continue;
+      }
+      InvokeStatic invoke = instruction.asInvokeStatic();
+      if (!isSynthesizedCloseResourceMethod(invoke.getInvokedMethod(), appView)) {
         continue;
       }
 
-      // Replace with a call to a synthetic utility.
+      // Replace with a call to a synthetic utility with the only
+      // implementation of the method $closeResource.
       assert invoke.outValue() == null;
       assert invoke.inValues().size() == 2;
-      ProgramMethod closeResourceMethod = createSyntheticCloseResourceMethod(code.context());
-      InvokeStatic newInvoke =
-          new InvokeStatic(closeResourceMethod.getReference(), null, invoke.inValues());
-      iterator.replaceCurrentInstruction(newInvoke);
-      synchronized (synthesizedMethods) {
-        synthesizedMethods.add(closeResourceMethod);
-      }
+      iterator.replaceCurrentInstruction(
+          new InvokeStatic(twrCloseResourceMethod, null, invoke.inValues()));
+
+      // Mark as a class referencing utility class.
+      referencingClasses.add(appInfo.definitionFor(code.method().getHolderType()).asProgramClass());
     }
   }
 
-  public static boolean isTwrCloseResourceMethod(DexMethod method, DexItemFactory factory) {
-    return method.name == factory.twrCloseResourceMethodName
-        && method.proto == factory.twrCloseResourceMethodProto;
+  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;
   }
 
-  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)));
-  }
+  public void synthesizeUtilityClass(
+      Builder<?> builder, ExecutorService executorService, InternalOptions options)
+      throws ExecutionException {
+    if (referencingClasses.isEmpty()) {
+      return;
+    }
 
-  public void processSynthesizedMethods(IRConverter converter) {
-    synthesizedMethods.forEach(converter::optimizeSynthesizedMethod);
+    // 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);
   }
 }
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 38c4e6f..1cce76e 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,6 +49,7 @@
 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;
@@ -129,7 +130,9 @@
     }
 
     if (extraNeverInlineMethods.contains(
-        appView.graphLens().getOriginalMethodSignature(singleTargetReference))) {
+            appView.graphLens().getOriginalMethodSignature(singleTargetReference))
+        || TwrCloseResourceRewriter.isSynthesizedCloseResourceMethod(
+            singleTargetReference, appView)) {
       whyAreYouNotInliningReporter.reportExtraNeverInline();
       return true;
     }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/ServiceLoaderRewriter.java b/src/main/java/com/android/tools/r8/ir/optimize/ServiceLoaderRewriter.java
index efc440b..1694a7b 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,15 +4,25 @@
 
 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.ProgramMethod;
+import com.android.tools.r8.graph.MethodCollection;
+import com.android.tools.r8.graph.ParameterAnnotationsList;
 import com.android.tools.r8.ir.code.ConstClass;
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.Instruction;
@@ -22,13 +32,15 @@
 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,
@@ -59,15 +71,19 @@
  */
 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 List<ProgramMethod> getServiceLoadMethods() {
-    return serviceLoadMethods;
+  public DexProgramClass getSynthesizedClass() {
+    return synthesizedClass.get();
   }
 
   public void rewrite(IRCode code, MethodProcessingId methodProcessingId) {
@@ -170,7 +186,7 @@
               constClass.getValue(),
               service -> {
                 DexEncodedMethod addedMethod =
-                    createSynthesizedMethod(service, classes, methodProcessingId, code.context());
+                    createSynthesizedMethod(service, classes, methodProcessingId);
                 if (appView.options().isGeneratingClassFiles()) {
                   addedMethod.upgradeClassFileVersion(code.method().getClassFileVersion());
                 }
@@ -183,31 +199,75 @@
   }
 
   private DexEncodedMethod createSynthesizedMethod(
-      DexType serviceType,
-      List<DexClass> classes,
-      MethodProcessingId methodProcessingId,
-      ProgramMethod context) {
+      DexType serviceType, List<DexClass> classes, MethodProcessingId methodProcessingId) {
+    MethodCollection methodCollection = getOrSetSynthesizedClass().getMethodCollection();
+    String methodNamePrefix = SERVICE_LOADER_METHOD_PREFIX_NAME + "$";
     DexProto proto = appView.dexItemFactory().createProto(appView.dexItemFactory().iteratorType);
-    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);
+    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;
     }
-    return method.getDefinition();
+  }
+
+  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;
   }
 
   /**
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 a972b39..1b55f13 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
@@ -15,7 +15,6 @@
 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 {
@@ -32,7 +31,6 @@
     SyntheticItems syntheticItems = appView.getSyntheticItems();
     ProgramMethod syntheticMethod =
         syntheticItems.createMethod(
-            SyntheticNaming.SyntheticKind.TO_STRING_IF_NOT_NULL,
             context,
             dexItemFactory,
             builder ->
@@ -62,7 +60,6 @@
     SyntheticItems syntheticItems = appView.getSyntheticItems();
     ProgramMethod syntheticMethod =
         syntheticItems.createMethod(
-            SyntheticNaming.SyntheticKind.THROW_CCE_IF_NOT_NULL,
             context,
             dexItemFactory,
             builder ->
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 9868a64..b5dfee6 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,18 +1090,10 @@
       } else if (classInitializerSideEffect.canBePostponed()) {
         feedback.classInitializerMayBePostponed(method);
       } else {
-        assert options.debug
-                || appView
-                    .getSyntheticItems()
-                    .verifySyntheticLambdaProperty(
-                        context.getHolder(),
-                        lambdaClass ->
-                            appView.appInfo().hasPinnedInstanceInitializer(lambdaClass.getType())
-                                || appView
-                                    .options()
-                                    .horizontalClassMergerOptions()
-                                    .isJavaLambdaMergingEnabled(),
-                        nonLambdaClass -> true)
+        assert !context.getHolderType().isD8R8SynthesizedLambdaClassType()
+                || options.debug
+                || appView.appInfo().hasPinnedInstanceInitializer(context.getHolderType())
+                || appView.options().horizontalClassMergerOptions().isJavaLambdaMergingEnabled()
             : "Unexpected observable side effects from lambda `" + context.toSourceString() + "`";
       }
       return;
diff --git a/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java b/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java
index cf6a117..ef724ec 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.SyntheticNaming;
+import com.android.tools.r8.synthesis.SyntheticItems;
 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 SyntheticNaming.verifyNotInternalSynthetic(clazz.getType());
+      assert SyntheticItems.verifyNotInternalSynthetic(clazz.getType());
       try {
         writeClass(clazz, consumer, rewriter, markerString);
       } catch (ClassTooLargeException e) {
@@ -194,7 +194,6 @@
     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/shaking/AppInfoWithLiveness.java b/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
index 5619d97..6b316f7 100644
--- a/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
+++ b/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
@@ -47,6 +47,7 @@
 import com.android.tools.r8.ir.desugar.DesugaredLibraryAPIConverter;
 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;
@@ -268,7 +269,7 @@
     this.switchMaps = switchMaps;
     this.lockCandidates = lockCandidates;
     this.initClassReferences = initClassReferences;
-    assert verify();
+    verify();
   }
 
   private AppInfoWithLiveness(AppInfoWithLiveness previous, CommittedItems committedItems) {
@@ -365,11 +366,10 @@
         previous.initClassReferences);
   }
 
-  private boolean verify() {
+  private void verify() {
     assert keepInfo.verifyPinnedTypesAreLive(liveTypes);
     assert objectAllocationInfoCollection.verifyAllocatedTypesAreLive(
         liveTypes, getMissingClasses(), this);
-    return true;
   }
 
   private static KeepInfoCollection extendPinnedItems(
@@ -454,7 +454,7 @@
     this.lockCandidates = previous.lockCandidates;
     this.initClassReferences = previous.initClassReferences;
     previous.markObsolete();
-    assert verify();
+    verify();
   }
 
   public static AppInfoWithLivenessModifier modifier() {
@@ -471,6 +471,7 @@
             || InterfaceMethodRewriter.isCompanionClassType(type)
             || InterfaceMethodRewriter.isEmulatedLibraryClassType(type)
             || type.toDescriptorString().startsWith("Lj$/$r8$retargetLibraryMember$")
+            || TwrCloseResourceRewriter.isUtilityClassDescriptor(type)
             // TODO(b/150736225): Not sure how to remove these.
             || DesugaredLibraryAPIConverter.isVivifiedType(type)
         : "Failed lookup of non-missing type: " + type;
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 37c66c4..c15fdb8 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -86,7 +86,6 @@
 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.kotlin.KotlinMetadataEnqueuerExtension;
 import com.android.tools.r8.logging.Log;
 import com.android.tools.r8.naming.identifiernamestring.IdentifierNameStringLookupResult;
@@ -360,14 +359,12 @@
 
   private final LambdaRewriter lambdaRewriter;
   private final BackportedMethodRewriter backportRewriter;
-  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();
 
   Enqueuer(
@@ -406,10 +403,6 @@
     lambdaRewriter = options.desugarState == DesugarState.ON ? new LambdaRewriter(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);
@@ -928,7 +921,7 @@
       assert contextMethod.getCode().isCfCode() : "Unexpected input type with lambdas";
       CfCode code = contextMethod.getCode().asCfCode();
       if (code != null) {
-        LambdaClass lambdaClass = lambdaRewriter.createLambdaClass(descriptor, context);
+        LambdaClass lambdaClass = lambdaRewriter.getOrCreateLambdaClass(descriptor, context);
         lambdaClasses.put(lambdaClass.type, new Pair<>(lambdaClass, context));
         lambdaCallSites
             .computeIfAbsent(context, k -> new IdentityHashMap<>())
@@ -1241,24 +1234,12 @@
     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)) {
@@ -3057,9 +3038,13 @@
       return empty;
     }
 
-    void addInstantiatedClass(DexProgramClass clazz, ProgramMethod context) {
+    void addInstantiatedClass(
+        DexProgramClass clazz, ProgramMethod context, boolean isMainDexClass) {
       assert !syntheticInstantiations.containsKey(clazz.type);
       syntheticInstantiations.put(clazz.type, new Pair<>(clazz, context));
+      if (isMainDexClass) {
+        mainDexTypes.add(clazz);
+      }
     }
 
     void addClasspathClass(DexClasspathClass clazz) {
@@ -3081,6 +3066,10 @@
 
     void amendApplication(Builder appBuilder) {
       assert !isEmpty();
+      for (Pair<DexProgramClass, ProgramMethod> clazzAndContext :
+          syntheticInstantiations.values()) {
+        appBuilder.addProgramClass(clazzAndContext.getFirst());
+      }
       appBuilder.addClasspathClasses(syntheticClasspathClasses.values());
     }
 
@@ -3151,7 +3140,6 @@
     synthesizeLambdas(additions);
     synthesizeLibraryConversionWrappers(additions);
     synthesizeBackports(additions);
-    synthesizeTwrCloseResource(additions);
     if (additions.isEmpty()) {
       return;
     }
@@ -3195,12 +3183,6 @@
     }
   }
 
-  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();
@@ -3211,8 +3193,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.getLambdaProgramClass();
-      additions.addInstantiatedClass(programClass, context);
+      DexProgramClass programClass = lambdaClass.getOrCreateLambdaClass();
+      additions.addInstantiatedClass(programClass, context, lambdaClass.addToMainDexList.get());
       // Mark the instance constructor targeted and live.
       DexEncodedMethod constructor = programClass.lookupDirectMethod(lambdaClass.constructor);
       KeepReason reason = KeepReason.instantiatedIn(context);
@@ -3367,9 +3349,10 @@
     lambdaRewriter
         .getKnownLambdaClasses()
         .forEach(
-            lambda -> {
-              DexProgramClass synthesizedClass = lambda.getLambdaProgramClass();
+            (type, lambda) -> {
+              DexProgramClass synthesizedClass = lambda.getOrCreateLambdaClass();
               assert synthesizedClass != null;
+              assert synthesizedClass == appInfo().definitionForWithoutExistenceAssert(type);
               assert liveTypes.contains(synthesizedClass);
               if (synthesizedClass == null) {
                 return;
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 e60dfa3..424fd8d 100644
--- a/src/main/java/com/android/tools/r8/synthesis/CommittedItems.java
+++ b/src/main/java/com/android/tools/r8/synthesis/CommittedItems.java
@@ -7,6 +7,8 @@
 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;
 
@@ -26,19 +28,23 @@
   // Immutable package accessible fields to allow SyntheticItems creation.
   final DexApplication application;
   final int nextSyntheticId;
-  final CommittedSyntheticsCollection committed;
+  final ImmutableSet<DexType> legacySyntheticTypes;
+  final ImmutableMap<DexType, SyntheticReference> syntheticItems;
   final ImmutableList<DexType> committedTypes;
 
   CommittedItems(
       int nextSyntheticId,
       DexApplication application,
-      CommittedSyntheticsCollection committed,
+      ImmutableSet<DexType> legacySyntheticTypes,
+      ImmutableMap<DexType, SyntheticReference> syntheticItems,
       ImmutableList<DexType> committedTypes) {
+    assert verifyTypesAreInApp(application, legacySyntheticTypes);
+    assert verifyTypesAreInApp(application, syntheticItems.keySet());
     this.nextSyntheticId = nextSyntheticId;
     this.application = application;
-    this.committed = committed;
+    this.legacySyntheticTypes = legacySyntheticTypes;
+    this.syntheticItems = syntheticItems;
     this.committedTypes = committedTypes;
-    committed.verifyTypesAreInApp(application);
   }
 
   // Conversion to a mutable synthetic items collection. Should only be used in AppInfo creation.
@@ -56,7 +62,7 @@
 
   @Deprecated
   public Collection<DexType> getLegacySyntheticTypes() {
-    return committed.getLegacyTypes();
+    return legacySyntheticTypes;
   }
 
   @Override
@@ -64,4 +70,11 @@
     // 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
deleted file mode 100644
index 582a39a..0000000
--- a/src/main/java/com/android/tools/r8/synthesis/CommittedSyntheticsCollection.java
+++ /dev/null
@@ -1,243 +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.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, SyntheticClassReference> 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) {
-      definition.toReference().apply(this::addNonLegacyMethod, this::addNonLegacyClass);
-      return this;
-    }
-
-    public Builder addNonLegacyClass(SyntheticClassDefinition definition) {
-      return addNonLegacyClass(definition.toReference());
-    }
-
-    public Builder addNonLegacyClass(SyntheticClassReference 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, SyntheticClassReference> 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, SyntheticClassReference> nonLegacyClasses;
-
-  public CommittedSyntheticsCollection(
-      ImmutableSet<DexType> legacyTypes,
-      ImmutableMap<DexType, SyntheticMethodReference> nonLegacyMethods,
-      ImmutableMap<DexType, SyntheticClassReference> 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, SyntheticClassReference> 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 (SyntheticClassReference 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 2e371d0..f5d4b66 100644
--- a/src/main/java/com/android/tools/r8/synthesis/SynthesizingContext.java
+++ b/src/main/java/com/android/tools/r8/synthesis/SynthesizingContext.java
@@ -14,7 +14,6 @@
 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;
 
@@ -54,20 +53,6 @@
     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;
@@ -90,6 +75,14 @@
     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);
@@ -134,17 +127,15 @@
   void addIfDerivedFromMainDexClass(
       DexProgramClass externalSyntheticClass,
       MainDexClasses mainDexClasses,
-      Set<DexType> allMainDexTypes) {
+      Set<DexType> allMainDexTypes,
+      Set<DexType> derivedMainDexTypesToIgnore) {
     // 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 9d2460b..4f289c7 100644
--- a/src/main/java/com/android/tools/r8/synthesis/SyntheticClassBuilder.java
+++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticClassBuilder.java
@@ -33,10 +33,8 @@
 
   private DexType superType;
   private DexTypeList interfaces = DexTypeList.empty();
-  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 int nextMethodId = 0;
   private List<SyntheticMethodBuilder> methods = new ArrayList<>();
 
   SyntheticClassBuilder(DexType type, SynthesizingContext context, DexItemFactory factory) {
@@ -54,40 +52,12 @@
     return type;
   }
 
-  public SyntheticClassBuilder setInterfaces(List<DexType> interfaces) {
-    this.interfaces =
-        interfaces.isEmpty()
-            ? DexTypeList.empty()
-            : new DexTypeList(interfaces.toArray(DexType.EMPTY_ARRAY));
-    return this;
-  }
-
-  public SyntheticClassBuilder setStaticFields(List<DexEncodedField> fields) {
-    staticFields.clear();
-    staticFields.addAll(fields);
-    return this;
-  }
-
-  public SyntheticClassBuilder setInstanceFields(List<DexEncodedField> fields) {
-    instanceFields.clear();
-    instanceFields.addAll(fields);
-    return this;
-  }
-
-  public SyntheticClassBuilder setDirectMethods(Iterable<DexEncodedMethod> methods) {
-    directMethods.clear();
-    methods.forEach(directMethods::add);
-    return this;
-  }
-
-  public SyntheticClassBuilder setVirtualMethods(Iterable<DexEncodedMethod> methods) {
-    virtualMethods.clear();
-    methods.forEach(virtualMethods::add);
-    return this;
+  private String getNextMethodName() {
+    return SyntheticItems.INTERNAL_SYNTHETIC_METHOD_PREFIX + nextMethodId++;
   }
 
   public SyntheticClassBuilder addMethod(Consumer<SyntheticMethodBuilder> fn) {
-    SyntheticMethodBuilder method = new SyntheticMethodBuilder(this);
+    SyntheticMethodBuilder method = new SyntheticMethodBuilder(this, getNextMethodName());
     fn.accept(method);
     methods.add(method);
     return this;
@@ -95,27 +65,35 @@
 
   DexProgramClass build() {
     ClassAccessFlags accessFlags =
-        ClassAccessFlags.fromSharedAccessFlags(
-            Constants.ACC_FINAL | Constants.ACC_PUBLIC | Constants.ACC_SYNTHETIC);
+        ClassAccessFlags.fromSharedAccessFlags(Constants.ACC_PUBLIC | Constants.ACC_SYNTHETIC);
     Kind originKind = null;
     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()) {
-        virtualMethods.add(method);
+        virtuals.add(method);
       } else {
-        directMethods.add(method);
+        directs.add(method);
       }
     }
-    long checksum =
-        7 * (long) directMethods.hashCode()
-            + 11 * (long) virtualMethods.hashCode()
-            + 13 * (long) staticFields.hashCode()
-            + 17 * (long) instanceFields.hashCode();
+    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,
@@ -130,10 +108,10 @@
         innerClasses,
         ClassSignature.noSignature(),
         DexAnnotationSet.empty(),
-        staticFields.toArray(new DexEncodedField[staticFields.size()]),
-        instanceFields.toArray(new DexEncodedField[instanceFields.size()]),
-        directMethods.toArray(new DexEncodedMethod[directMethods.size()]),
-        virtualMethods.toArray(new DexEncodedMethod[virtualMethods.size()]),
+        staticFields,
+        instanceFields,
+        directMethods,
+        virtualMethods,
         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
deleted file mode 100644
index 4b0b2cf..0000000
--- a/src/main/java/com/android/tools/r8/synthesis/SyntheticClassDefinition.java
+++ /dev/null
@@ -1,65 +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.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;
-
-/**
- * Definition of a synthetic class item.
- *
- * <p>This class is internal to the synthetic items collection, thus package-protected.
- */
-class SyntheticClassDefinition
-    extends SyntheticDefinition<SyntheticClassReference, SyntheticClassDefinition> {
-
-  private final DexProgramClass clazz;
-
-  SyntheticClassDefinition(SyntheticKind kind, SynthesizingContext context, DexProgramClass clazz) {
-    super(kind, context);
-    this.clazz = clazz;
-  }
-
-  public DexProgramClass getProgramClass() {
-    return clazz;
-  }
-
-  @Override
-  SyntheticClassReference toReference() {
-    return new SyntheticClassReference(getKind(), getContext(), clazz.getType());
-  }
-
-  @Override
-  DexProgramClass getHolder() {
-    return clazz;
-  }
-
-  @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(SyntheticClassDefinition o, RepresentativeMap map) {
-    return clazz.compareWithTypeEquivalenceTo(o.clazz, map);
-  }
-
-  @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
deleted file mode 100644
index 9c79ab9..0000000
--- a/src/main/java/com/android/tools/r8/synthesis/SyntheticClassReference.java
+++ /dev/null
@@ -1,73 +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.synthesis;
-
-import com.android.tools.r8.graph.DexClass;
-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 SyntheticClassReference
-    extends SyntheticReference<SyntheticClassReference, SyntheticClassDefinition> {
-  final DexType type;
-
-  SyntheticClassReference(SyntheticKind kind, SynthesizingContext context, DexType type) {
-    super(kind, context);
-    this.type = type;
-  }
-
-  @Override
-  DexType getHolder() {
-    return type;
-  }
-
-  @Override
-  SyntheticClassDefinition lookupDefinition(Function<DexType, DexClass> definitions) {
-    DexClass clazz = definitions.apply(type);
-    if (clazz == null) {
-      return null;
-    }
-    assert clazz.isProgramClass();
-    return new SyntheticClassDefinition(getKind(), getContext(), clazz.asProgramClass());
-  }
-
-  @Override
-  SyntheticClassReference 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 SyntheticClassReference(getKind(), context, rewritten);
-  }
-
-  @Override
-  void apply(
-      Consumer<SyntheticMethodReference> onMethod, Consumer<SyntheticClassReference> onClass) {
-    onClass.accept(this);
-  }
-}
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 526f4fc..01f8825 100644
--- a/src/main/java/com/android/tools/r8/synthesis/SyntheticDefinition.java
+++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticDefinition.java
@@ -4,71 +4,30 @@
 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.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<
-    R extends SyntheticReference<R, D>, D extends SyntheticDefinition<R, D>> {
-
-  private final SyntheticKind kind;
+abstract class SyntheticDefinition {
   private final SynthesizingContext context;
 
-  SyntheticDefinition(SyntheticKind kind, SynthesizingContext context) {
-    assert kind != null;
-    assert context != null;
-    this.kind = kind;
+  SyntheticDefinition(SynthesizingContext context) {
     this.context = context;
   }
 
-  abstract R toReference();
+  abstract SyntheticReference toReference();
 
-  final SyntheticKind getKind() {
-    return kind;
-  }
-
-  final SynthesizingContext getContext() {
+  SynthesizingContext getContext() {
     return context;
   }
 
   abstract DexProgramClass getHolder();
 
-  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 HashCode computeHash(RepresentativeMap map, boolean intermediate);
 
-  abstract void internalComputeHash(Hasher hasher, RepresentativeMap map);
-
-  final boolean isEquivalentTo(D other, boolean includeContext) {
-    return compareTo(other, includeContext) == 0;
-  }
-
-  int compareTo(D other, boolean includeContext) {
-    if (includeContext) {
-      int order = getContext().compareTo(other.getContext());
-      if (order != 0) {
-        return order;
-      }
-    }
-    RepresentativeMap map = t -> t == other.getHolder().getType() ? getHolder().getType() : t;
-    return internalCompareTo(other, map);
-  }
-
-  abstract int internalCompareTo(D other, RepresentativeMap map);
-
-  public abstract boolean isValid();
+  abstract boolean isEquivalentTo(SyntheticDefinition other, boolean intermediate);
 }
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 67391a2..c18b29d 100644
--- a/src/main/java/com/android/tools/r8/synthesis/SyntheticFinalization.java
+++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticFinalization.java
@@ -6,31 +6,22 @@
 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.synthesis.SyntheticNaming.SyntheticKind;
+import com.android.tools.r8.utils.DescriptorUtils;
 import com.android.tools.r8.utils.InternalOptions;
-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;
@@ -41,146 +32,37 @@
 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.Function;
+import java.util.function.Predicate;
 
 public class SyntheticFinalization {
 
   public static class Result {
     public final CommittedItems commit;
-    public final NonIdentityGraphLens lens;
     public final PrunedItems prunedItems;
 
-    public Result(
-        CommittedItems commit, SyntheticFinalizationGraphLens lens, PrunedItems prunedItems) {
+    public Result(CommittedItems commit, PrunedItems prunedItems) {
       this.commit = commit;
-      this.lens = lens;
       this.prunedItems = prunedItems;
     }
   }
 
-  public static class SyntheticFinalizationGraphLens extends NestedGraphLens {
+  private static class EquivalenceGroup<T extends SyntheticDefinition & Comparable<T>>
+      implements Comparable<EquivalenceGroup<T>> {
+    private List<T> members;
 
-    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 singleton) {
+      this(singleton, Collections.singletonList(singleton));
     }
 
-    // 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) {
+    EquivalenceGroup(T representative, List<T> members) {
       assert !members.isEmpty();
       assert members.get(0) == representative;
       this.members = members;
     }
 
-    public T getRepresentative() {
+    T getRepresentative() {
       return members.get(0);
     }
 
@@ -188,103 +70,89 @@
       return members;
     }
 
-    public int compareToIncludingContext(EquivalenceGroup<T> other) {
-      return getRepresentative().compareTo(other.getRepresentative(), true);
-    }
-
-    public int compareTo(EquivalenceGroup<T> other, boolean includeContext) {
-      return getRepresentative().compareTo(other.getRepresentative(), includeContext);
-    }
-
     @Override
-    public String toString() {
-      return "EquivalenceGroup{ members = "
-          + members.size()
-          + ", repr = "
-          + getRepresentative()
-          + " }";
+    public int compareTo(EquivalenceGroup<T> other) {
+      return getRepresentative().compareTo(other.getRepresentative());
     }
   }
 
   private final InternalOptions options;
-  private final CommittedSyntheticsCollection synthetics;
+  private final ImmutableSet<DexType> legacySyntheticTypes;
+  private final ImmutableMap<DexType, SyntheticReference> syntheticItems;
 
-  SyntheticFinalization(InternalOptions options, CommittedSyntheticsCollection synthetics) {
+  SyntheticFinalization(
+      InternalOptions options,
+      ImmutableSet<DexType> legacySyntheticTypes,
+      ImmutableMap<DexType, SyntheticReference> syntheticItems) {
     this.options = options;
-    this.synthetics = synthetics;
+    this.legacySyntheticTypes = legacySyntheticTypes;
+    this.syntheticItems = syntheticItems;
   }
 
   public Result computeFinalSynthetics(AppView<?> appView) {
     assert verifyNoNestedSynthetics();
-    DexApplication application;
+    DexApplication application = appView.appInfo().app();
     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<>();
-    Builder lensBuilder = new Builder();
-    {
-      Map<DexType, NumberGenerator> generators = new IdentityHashMap<>();
-      application =
-          buildLensAndProgram(
-              appView,
-              computeEquivalences(appView, synthetics.getNonLegacyMethods().values(), generators),
-              computeEquivalences(appView, synthetics.getNonLegacyClasses().values(), generators),
-              mainDexClasses,
-              lensBuilder,
-              finalSyntheticClasses);
-    }
+    Set<DexType> derivedMainDexTypesToIgnore = Sets.newIdentityHashSet();
+    buildLensAndProgram(
+        appView,
+        equivalences,
+        syntheticItems::containsKey,
+        mainDexClasses,
+        lensBuilder,
+        newProgramClasses,
+        finalSyntheticClasses,
+        derivedMainDexTypesToIgnore);
+
+    newProgramClasses.addAll(finalSyntheticClasses);
 
     handleSynthesizedClassMapping(
-        finalSyntheticClasses, application, options, mainDexClasses, lensBuilder.typeMap);
+        finalSyntheticClasses, application, options, mainDexClasses, derivedMainDexTypesToIgnore);
 
+    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();
-    synthetics.forEachNonLegacyItem(
-        reference -> {
-          DexType type = reference.getHolder();
-          if (!finalSyntheticTypes.contains(type)) {
-            prunedSynthetics.add(type);
-          }
-        });
+    for (DexType type : syntheticItems.keySet()) {
+      if (!finalSyntheticTypes.contains(type)) {
+        prunedSynthetics.add(type);
+      }
+    }
 
     return new Result(
         new CommittedItems(
             SyntheticItems.INVALID_ID_AFTER_SYNTHETIC_FINALIZATION,
-            application,
-            new CommittedSyntheticsCollection(
-                synthetics.getLegacyTypes(), ImmutableMap.of(), ImmutableMap.of()),
+            app,
+            legacySyntheticTypes,
+            ImmutableMap.of(),
             ImmutableList.of()),
-        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());
-    return computeActualEquivalences(potentialEquivalences, generators, appView, intermediate);
-  }
-
-  private boolean isNotSyntheticType(DexType type) {
-    return !synthetics.containsNonLegacyType(type);
+        PrunedItems.builder().setPrunedApp(app).addRemovedClasses(prunedSynthetics).build());
   }
 
   private boolean verifyNoNestedSynthetics() {
     // Check that a context is never itself synthetic class.
-    synthetics.forEachNonLegacyItem(
-        item -> {
-          assert isNotSyntheticType(item.getContext().getSynthesizingContextType());
-        });
+    for (SyntheticReference item : syntheticItems.values()) {
+      assert !syntheticItems.containsKey(item.getContext().getSynthesizingContextType());
+    }
     return true;
   }
 
@@ -293,7 +161,7 @@
       DexApplication application,
       InternalOptions options,
       MainDexClasses mainDexClasses,
-      Map<DexType, DexType> derivedMainDexTypesToIgnore) {
+      Set<DexType> derivedMainDexTypesToIgnore) {
     boolean includeSynthesizedClassMappingInOutput = shouldAnnotateSynthetics(options);
     if (includeSynthesizedClassMappingInOutput) {
       updateSynthesizedClassMapping(application, finalSyntheticClasses);
@@ -309,7 +177,7 @@
       DexApplication application, List<DexProgramClass> finalSyntheticClasses) {
     ListMultimap<DexProgramClass, DexProgramClass> originalToSynthesized =
         ArrayListMultimap.create();
-    for (DexType type : synthetics.getLegacyTypes()) {
+    for (DexType type : legacySyntheticTypes) {
       DexProgramClass clazz = DexProgramClass.asProgramClassOrNull(application.definitionFor(type));
       if (clazz != null) {
         for (DexProgramClass origin : clazz.getSynthesizedFrom()) {
@@ -346,7 +214,7 @@
   private void updateMainDexListWithSynthesizedClassMap(
       DexApplication application,
       MainDexClasses mainDexClasses,
-      Map<DexType, DexType> derivedMainDexTypesToIgnore) {
+      Set<DexType> derivedMainDexTypesToIgnore) {
     if (mainDexClasses.isEmpty()) {
       return;
     }
@@ -360,11 +228,12 @@
                 DexAnnotation.readAnnotationSynthesizedClassMap(
                     programClass, application.dexItemFactory);
             for (DexType type : derived) {
-              DexType mappedType = derivedMainDexTypesToIgnore.getOrDefault(type, type);
-              DexProgramClass syntheticClass =
-                  DexProgramClass.asProgramClassOrNull(application.definitionFor(mappedType));
-              if (syntheticClass != null) {
-                newMainDexClasses.add(syntheticClass);
+              if (!derivedMainDexTypesToIgnore.contains(type)) {
+                DexProgramClass syntheticClass =
+                    DexProgramClass.asProgramClassOrNull(application.definitionFor(type));
+                if (syntheticClass != null) {
+                  newMainDexClasses.add(syntheticClass);
+                }
               }
             }
           }
@@ -379,16 +248,24 @@
     }
   }
 
-  private static DexApplication buildLensAndProgram(
+  private static void buildLensAndProgram(
       AppView<?> appView,
       Map<DexType, EquivalenceGroup<SyntheticMethodDefinition>> syntheticMethodGroups,
-      Map<DexType, EquivalenceGroup<SyntheticClassDefinition>> syntheticClassGroups,
+      Predicate<DexType> isSyntheticType,
       MainDexClasses mainDexClasses,
       Builder lensBuilder,
-      List<DexProgramClass> newSyntheticClasses) {
-    DexApplication application = appView.appInfo().app();
+      List<DexProgramClass> normalClasses,
+      List<DexProgramClass> newSyntheticClasses,
+      Set<DexType> derivedMainDexTypesToIgnore) {
     DexItemFactory factory = appView.dexItemFactory();
 
+    for (DexProgramClass clazz : appView.appInfo().classes()) {
+      if (!isSyntheticType.test(clazz.type)) {
+        assert SyntheticItems.verifyNotInternalSynthetic(clazz.type);
+        normalClasses.add(clazz);
+      }
+    }
+
     // TODO(b/168584485): Remove this once class-mapping support is removed.
     Set<DexType> derivedMainDexTypes = Sets.newIdentityHashSet();
     mainDexClasses.forEach(
@@ -403,178 +280,60 @@
           }
         });
 
-    Set<DexType> pruned = Sets.newIdentityHashSet();
     syntheticMethodGroups.forEach(
         (syntheticType, syntheticGroup) -> {
           SyntheticMethodDefinition representative = syntheticGroup.getRepresentative();
           SynthesizingContext context = representative.getContext();
           context.registerPrefixRewriting(syntheticType, appView);
-          DexProgramClass externalSyntheticClass =
-              createExternalMethodClass(syntheticType, representative, factory);
-          newSyntheticClasses.add(externalSyntheticClass);
-          addSyntheticMarker(representative.getKind(), externalSyntheticClass, context, 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)));
+          }
           assert externalSyntheticClass.getMethodCollection().size() == 1;
           DexEncodedMethod externalSyntheticMethod =
               externalSyntheticClass.methods().iterator().next();
-          for (SyntheticMethodDefinition member : syntheticGroup.getMembers()) {
-            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) -> {
-          SyntheticClassDefinition representative = syntheticGroup.getRepresentative();
-          SynthesizingContext context = representative.getContext();
-          context.registerPrefixRewriting(syntheticType, appView);
-          DexProgramClass externalSyntheticClass = representative.getProgramClass();
-          newSyntheticClasses.add(externalSyntheticClass);
-          addSyntheticMarker(representative.getKind(), externalSyntheticClass, context, appView);
-          for (SyntheticClassDefinition member : syntheticGroup.getMembers()) {
-            DexProgramClass memberClass = member.getProgramClass();
-            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);
-            }
-          }
-        });
-
-    List<DexProgramClass> newProgramClasses = new ArrayList<>(newSyntheticClasses);
-    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;
-    }
-
-    newSyntheticClasses.clear();
-
-    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);
-          newSyntheticClasses.add(externalSyntheticClass);
-          for (SyntheticClassDefinition member : syntheticGroup.getMembers()) {
-            addMainDexAndSynthesizedFromForMember(
-                member,
-                externalSyntheticClass,
-                mainDexClasses,
-                derivedMainDexTypes,
-                appForLookup::programDefinitionFor);
-          }
-        });
-    syntheticMethodGroups.forEach(
-        (syntheticType, syntheticGroup) -> {
-          DexProgramClass externalSyntheticClass = appForLookup.programDefinitionFor(syntheticType);
           newSyntheticClasses.add(externalSyntheticClass);
           for (SyntheticMethodDefinition member : syntheticGroup.getMembers()) {
-            addMainDexAndSynthesizedFromForMember(
-                member,
-                externalSyntheticClass,
-                mainDexClasses,
-                derivedMainDexTypes,
-                appForLookup::programDefinitionFor);
+            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);
+            }
           }
         });
-
-    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) {
-    SyntheticClassBuilder builder =
-        new SyntheticClassBuilder(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) {
@@ -586,12 +345,9 @@
     return options.intermediate && !options.cfToCfDesugar;
   }
 
-  private <T extends SyntheticDefinition<?, T>>
+  private static <T extends SyntheticDefinition & Comparable<T>>
       Map<DexType, EquivalenceGroup<T>> computeActualEquivalences(
-          Collection<List<T>> potentialEquivalences,
-          Map<DexType, NumberGenerator> generators,
-          AppView<?> appView,
-          boolean intermediate) {
+          Collection<List<T>> potentialEquivalences, boolean intermediate, DexItemFactory factory) {
     Map<DexType, List<EquivalenceGroup<T>>> groupsPerContext = new IdentityHashMap<>();
     potentialEquivalences.forEach(
         members -> {
@@ -612,24 +368,20 @@
     Map<DexType, EquivalenceGroup<T>> equivalences = new IdentityHashMap<>();
     groupsPerContext.forEach(
         (context, groups) -> {
-          // 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(EquivalenceGroup::compareToIncludingContext);
+          groups.sort(EquivalenceGroup::compareTo);
           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);
-            SyntheticKind kind = group.members.get(0).getKind();
-            DexType representativeType = createExternalType(kind, context, generators, appView);
+            DexType representativeType = createExternalType(context, i, factory);
             equivalences.put(representativeType, group);
           }
         });
     return equivalences;
   }
 
-  private static <T extends SyntheticDefinition<?, T>> List<List<T>> groupEquivalent(
+  private static <T extends SyntheticDefinition & Comparable<T>> List<List<T>> groupEquivalent(
       List<T> potentialEquivalence, boolean intermediate) {
     List<List<T>> groups = new ArrayList<>();
     // Each other member is in a shared group if it is actually equivalent to the first member.
@@ -651,61 +403,42 @@
     return groups;
   }
 
-  private static <T extends SyntheticDefinition<?, T>> boolean checkGroupsAreDistinct(
+  private static <T extends SyntheticDefinition & Comparable<T>> boolean checkGroupsAreDistinct(
       EquivalenceGroup<T> g1, EquivalenceGroup<T> g2) {
-    int order = g1.compareToIncludingContext(g2);
-    assert order != 0;
-    assert order != g2.compareToIncludingContext(g1);
+    assert g1.compareTo(g2) != 0;
     return true;
   }
 
-  private static <T extends SyntheticDefinition<?, T>> T findDeterministicRepresentative(
+  private static <T extends SyntheticDefinition & Comparable<T>> T findDeterministicRepresentative(
       List<T> members) {
     // 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, true) < 0) {
+      if (next.compareTo(smallest) < 0) {
         smallest = next;
       }
     }
     return smallest;
   }
 
-  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 DexType createExternalType(
+      DexType representativeContext, int nextContextId, DexItemFactory factory) {
+    return factory.createType(
+        DescriptorUtils.getDescriptorFromClassBinaryName(
+            representativeContext.getInternalName()
+                + SyntheticItems.EXTERNAL_SYNTHETIC_CLASS_SEPARATOR
+                + nextContextId));
   }
 
-  private static <T extends SyntheticDefinition<?, T>>
-      Collection<List<T>> computePotentialEquivalences(
-          Map<DexType, T> definitions, boolean intermediate, DexItemFactory factory) {
+  private static <T extends SyntheticDefinition> Collection<List<T>> computePotentialEquivalences(
+      Map<DexType, T> definitions, boolean intermediate) {
     if (definitions.isEmpty()) {
       return Collections.emptyList();
     }
-    // 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.
-    RepresentativeMap map = t -> definitions.containsKey(t) ? factory.voidType : t;
+    Set<DexType> allTypes = definitions.keySet();
+    DexType representative = allTypes.iterator().next();
+    RepresentativeMap map = t -> allTypes.contains(t) ? representative : t;
     Map<HashCode, List<T>> equivalences = new HashMap<>(definitions.size());
     for (T definition : definitions.values()) {
       HashCode hash = definition.computeHash(map, intermediate);
@@ -714,24 +447,25 @@
     return equivalences.values();
   }
 
-  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) {
+  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)) {
         // We expect pruned definitions to have been removed.
         assert false;
         continue;
       }
-      if (definition.isValid()) {
-        definitions.put(reference.getHolder(), definition);
+      SyntheticMethodDefinition method = (SyntheticMethodDefinition) definition;
+      if (SyntheticMethodBuilder.isValidSyntheticMethod(method.getMethod().getDefinition())) {
+        methods.put(method.getHolder().getType(), method);
       } else {
         // Failing this check indicates that an optimization has modified the synthetic in a
         // disruptive way.
         assert false;
       }
     }
-    return definitions;
+    return methods;
   }
 }
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 179f990..bc7d42e 100644
--- a/src/main/java/com/android/tools/r8/synthesis/SyntheticItems.java
+++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticItems.java
@@ -6,8 +6,12 @@
 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.DexItemFactory;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexType;
@@ -17,8 +21,11 @@
 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;
@@ -28,112 +35,148 @@
 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;
 
-  /** 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<>();
+  /**
+   * 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<>();
 
-    /** Thread safe collection of synthetic items not yet committed to the application. */
-    private final ConcurrentHashMap<DexType, SyntheticDefinition<?, ?>> nonLegacyDefinitions =
-        new ConcurrentHashMap<>();
+  /**
+   * Immutable set of synthetic types in the application (eg, committed).
+   *
+   * <p>TODO(b/158159959): Remove legacy support.
+   */
+  private final ImmutableSet<DexType> legacySyntheticTypes;
 
-    boolean isEmpty() {
-      return legacyClasses.isEmpty() && nonLegacyDefinitions.isEmpty();
-    }
+  /** Thread safe collection of synthetic items not yet committed to the application. */
+  private final ConcurrentHashMap<DexType, SyntheticDefinition> pendingDefinitions =
+      new ConcurrentHashMap<>();
 
-    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> getAllClasses() {
-      List<DexProgramClass> allPending =
-          new ArrayList<>(nonLegacyDefinitions.size() + legacyClasses.size());
-      for (SyntheticDefinition<?, ?> item : nonLegacyDefinitions.values()) {
-        allPending.add(item.getHolder());
-      }
-      allPending.addAll(legacyClasses.values());
-      return Collections.unmodifiableList(allPending);
-    }
-  }
-
-  private final CommittedSyntheticsCollection committed;
-
-  private final PendingSynthetics pending = new PendingSynthetics();
+  /** Mapping from synthetic type to its synthetic description. */
+  private final ImmutableMap<DexType, SyntheticReference> nonLecacySyntheticItems;
 
   // Only for use from initial AppInfo/AppInfoWithClassHierarchy create functions. */
   public static CommittedItems createInitialSyntheticItems(DexApplication application) {
     return new CommittedItems(
-        0, application, CommittedSyntheticsCollection.empty(), ImmutableList.of());
+        0, application, ImmutableSet.of(), ImmutableMap.of(), ImmutableList.of());
   }
 
   // Only for conversion to a mutable synthetic items collection.
   SyntheticItems(CommittedItems commit) {
-    this(commit.nextSyntheticId, commit.committed);
+    this(commit.nextSyntheticId, commit.legacySyntheticTypes, commit.syntheticItems);
   }
 
-  private SyntheticItems(int nextSyntheticId, CommittedSyntheticsCollection committed) {
+  private SyntheticItems(
+      int nextSyntheticId,
+      ImmutableSet<DexType> legacySyntheticTypes,
+      ImmutableMap<DexType, SyntheticReference> nonLecacySyntheticItems) {
     this.nextSyntheticId = nextSyntheticId;
-    this.committed = committed;
+    this.legacySyntheticTypes = legacySyntheticTypes;
+    this.nonLecacySyntheticItems = nonLecacySyntheticItems;
+    assert Sets.intersection(nonLecacySyntheticItems.keySet(), legacySyntheticTypes).isEmpty();
   }
 
   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.committed.isEmpty();
-    assert synthetics.pending.isEmpty();
+    assert synthetics.nonLecacySyntheticItems.isEmpty();
+    assert !synthetics.hasPendingSyntheticClasses();
     if (appView.options().intermediate) {
       // If the compilation is in intermediate mode the synthetics should just be passed through.
       return;
     }
-    CommittedSyntheticsCollection.Builder builder = synthetics.committed.builder();
+    ImmutableMap.Builder<DexType, SyntheticReference> pending = ImmutableMap.builder();
     // TODO(b/158159959): Consider identifying synthetics in the input reader to speed this up.
     for (DexProgramClass clazz : appView.appInfo().classes()) {
-      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 SyntheticClassDefinition(marker.getKind(), marker.getContext(), clazz));
+      DexType annotatedContextType = isSynthesizedMethodsContainer(clazz, appView.dexItemFactory());
+      if (annotatedContextType == null) {
+        continue;
       }
+      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());
+          });
     }
-    CommittedSyntheticsCollection committed = builder.build();
-    if (committed.isEmpty()) {
+    pending.putAll(synthetics.nonLecacySyntheticItems);
+    ImmutableMap<DexType, SyntheticReference> nonLegacySyntheticItems = pending.build();
+    if (nonLegacySyntheticItems.isEmpty()) {
       return;
     }
     CommittedItems commit =
         new CommittedItems(
-            synthetics.nextSyntheticId, appView.appInfo().app(), committed, ImmutableList.of());
+            synthetics.nextSyntheticId,
+            appView.appInfo().app(),
+            synthetics.legacySyntheticTypes,
+            nonLegacySyntheticItems,
+            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() {
@@ -148,52 +191,49 @@
 
   @Override
   public DexClass definitionFor(DexType type, Function<DexType, DexClass> baseDefinitionFor) {
-    DexProgramClass clazz = pending.legacyClasses.get(type);
-    if (clazz == null) {
-      SyntheticDefinition<?, ?> item = pending.nonLegacyDefinitions.get(type);
+    DexProgramClass pending = legacyPendingClasses.get(type);
+    if (pending == null) {
+      SyntheticDefinition item = pendingDefinitions.get(type);
       if (item != null) {
-        clazz = item.getHolder();
+        pending = item.getHolder();
       }
     }
-    if (clazz != null) {
+    if (pending != null) {
       assert baseDefinitionFor.apply(type) == null
           : "Pending synthetic definition also present in the active program: " + type;
-      return clazz;
+      return pending;
     }
     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 !pending.isEmpty();
+    return !legacyPendingClasses.isEmpty() || !pendingDefinitions.isEmpty();
   }
 
   public Collection<DexProgramClass> getPendingSyntheticClasses() {
-    return pending.getAllClasses();
+    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);
   }
 
   private boolean isCommittedSynthetic(DexType type) {
-    return committed.containsType(type);
+    return nonLecacySyntheticItems.containsKey(type) || legacySyntheticTypes.contains(type);
   }
 
   private boolean isLegacyCommittedSynthetic(DexType type) {
-    return committed.containsLegacyType(type);
+    return legacySyntheticTypes.contains(type);
   }
 
   public boolean isPendingSynthetic(DexType type) {
-    return pending.containsType(type);
+    return pendingDefinitions.containsKey(type) || legacyPendingClasses.containsKey(type);
   }
 
   public boolean isLegacyPendingSynthetic(DexType type) {
-    return pending.legacyClasses.containsKey(type);
+    return legacyPendingClasses.containsKey(type);
   }
 
   public boolean isSyntheticClass(DexType type) {
@@ -207,27 +247,6 @@
     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);
   }
@@ -237,21 +256,18 @@
   }
 
   public Collection<DexProgramClass> getLegacyPendingClasses() {
-    return Collections.unmodifiableCollection(pending.legacyClasses.values());
+    return Collections.unmodifiableCollection(legacyPendingClasses.values());
   }
 
   private SynthesizingContext getSynthesizingContext(ProgramDefinition context) {
-    DexType contextType = context.getContextType();
-    SyntheticDefinition<?, ?> existingDefinition = pending.nonLegacyDefinitions.get(contextType);
-    if (existingDefinition != null) {
-      return existingDefinition.getContext();
+    SyntheticDefinition pendingItemContext = pendingDefinitions.get(context.getContextType());
+    if (pendingItemContext != null) {
+      return pendingItemContext.getContext();
     }
-    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);
+    SyntheticReference committedItemContext = nonLecacySyntheticItems.get(context.getContextType());
+    return committedItemContext != null
+        ? committedItemContext.getContext()
+        : SynthesizingContext.fromNonSyntheticInputContext(context);
   }
 
   // Addition and creation of synthetic items.
@@ -260,49 +276,25 @@
   public void addLegacySyntheticClass(DexProgramClass clazz) {
     assert clazz.type.isD8R8SynthesizedClassType();
     assert !isCommittedSynthetic(clazz.type);
-    assert !pending.nonLegacyDefinitions.containsKey(clazz.type);
-    DexProgramClass previous = pending.legacyClasses.put(clazz.type, clazz);
+    DexProgramClass previous = legacyPendingClasses.put(clazz.type, clazz);
     assert previous == null || previous == clazz;
   }
 
-  public DexProgramClass createClass(
-      SyntheticKind kind,
-      DexProgramClass context,
-      DexItemFactory factory,
-      Consumer<SyntheticClassBuilder> 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, getNextSyntheticId(), factory);
-    SyntheticClassBuilder classBuilder = new SyntheticClassBuilder(type, outerContext, factory);
-    fn.accept(classBuilder);
-    DexProgramClass clazz = classBuilder.build();
-    addPendingDefinition(new SyntheticClassDefinition(kind, outerContext, clazz));
-    return clazz;
-  }
-
   /** Create a single synthetic method item. */
   public ProgramMethod createMethod(
-      SyntheticKind kind,
-      ProgramDefinition context,
-      DexItemFactory factory,
-      Consumer<SyntheticMethodBuilder> fn) {
-    return createMethod(kind, context, factory, fn, this::getNextSyntheticId);
+      ProgramDefinition context, DexItemFactory factory, Consumer<SyntheticMethodBuilder> fn) {
+    return createMethod(context, factory, fn, this::getNextSyntheticId);
   }
 
   public ProgramMethod createMethod(
-      SyntheticKind kind,
       ProgramDefinition context,
       DexItemFactory factory,
       Consumer<SyntheticMethodBuilder> fn,
       MethodProcessingId methodProcessingId) {
-    return createMethod(
-        kind, context, factory, fn, methodProcessingId::getFullyQualifiedIdAndIncrement);
+    return createMethod(context, factory, fn, methodProcessingId::getFullyQualifiedIdAndIncrement);
   }
 
   private ProgramMethod createMethod(
-      SyntheticKind kind,
       ProgramDefinition context,
       DexItemFactory factory,
       Consumer<SyntheticMethodBuilder> fn,
@@ -311,20 +303,16 @@
     // 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);
+    DexType type = outerContext.createHygienicType(syntheticIdSupplier.get(), factory);
     SyntheticClassBuilder classBuilder = new SyntheticClassBuilder(type, outerContext, factory);
-    DexProgramClass clazz =
-        classBuilder
-            .addMethod(fn.andThen(m -> m.setName(SyntheticNaming.INTERNAL_SYNTHETIC_METHOD_PREFIX)))
-            .build();
+    DexProgramClass clazz = classBuilder.addMethod(fn).build();
     ProgramMethod method = new ProgramMethod(clazz, clazz.methods().iterator().next());
-    addPendingDefinition(new SyntheticMethodDefinition(kind, outerContext, method));
+    addPendingDefinition(new SyntheticMethodDefinition(outerContext, method));
     return method;
   }
 
-  private void addPendingDefinition(SyntheticDefinition<?, ?> definition) {
-    pending.nonLegacyDefinitions.put(definition.getHolder().getType(), definition);
+  private void addPendingDefinition(SyntheticDefinition definition) {
+    pendingDefinitions.put(definition.getHolder().getType(), definition);
   }
 
   // Commit of the synthetic items to a new fully populated application.
@@ -334,48 +322,125 @@
   }
 
   public CommittedItems commitPrunedItems(PrunedItems prunedItems) {
-    return commit(prunedItems, pending, committed, nextSyntheticId);
+    return commit(
+        prunedItems.getPrunedApp(),
+        prunedItems.getNoLongerSyntheticItems(),
+        legacyPendingClasses,
+        legacySyntheticTypes,
+        pendingDefinitions,
+        nonLecacySyntheticItems,
+        nextSyntheticId);
   }
 
   public CommittedItems commitRewrittenWithLens(
       DexApplication application, NonIdentityGraphLens lens) {
-    assert pending.verifyNotRewritten(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()));
     return commit(
-        PrunedItems.empty(application), pending, committed.rewriteWithLens(lens), nextSyntheticId);
+        application,
+        Collections.emptySet(),
+        legacyPendingClasses,
+        rewrittenLegacyTypes,
+        pendingDefinitions,
+        rewrittenItems.build(),
+        nextSyntheticId);
   }
 
   private static CommittedItems commit(
-      PrunedItems prunedItems,
-      PendingSynthetics pending,
-      CommittedSyntheticsCollection committed,
+      DexApplication application,
+      Set<DexType> removedClasses,
+      Map<DexType, DexProgramClass> legacyPendingClasses,
+      ImmutableSet<DexType> legacySyntheticTypes,
+      ConcurrentHashMap<DexType, SyntheticDefinition> pendingDefinitions,
+      ImmutableMap<DexType, SyntheticReference> syntheticItems,
       int nextSyntheticId) {
-    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.
+    // 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 amendedApplication;
-    if (pending.nonLegacyDefinitions.isEmpty()) {
+    if (pendingDefinitions.isEmpty()) {
+      mergedItems = filteredCopy(syntheticItems, removedClasses);
       additions = 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 : pending.nonLegacyDefinitions.values()) {
-        if (!removedClasses.contains(definition.getHolder().getType())) {
-          additionsBuilder.add(definition.getHolder().getType());
-          appBuilder.addProgramClass(definition.getHolder());
-          builder.addItem(definition);
+      for (SyntheticDefinition definition : pendingDefinitions.values()) {
+        if (removedClasses.contains(definition.getHolder().getType())) {
+          continue;
         }
+        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();
       amendedApplication = appBuilder.build();
     }
     return new CommittedItems(
-        nextSyntheticId, amendedApplication, builder.build().pruneItems(prunedItems), additions);
+        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);
+            }
+          });
+    }
   }
 
   private static boolean verifyClassesAreInApp(
@@ -390,6 +455,8 @@
 
   public Result computeFinalSynthetics(AppView<?> appView) {
     assert !hasPendingSyntheticClasses();
-    return new SyntheticFinalization(appView.options(), committed).computeFinalSynthetics(appView);
+    return new SyntheticFinalization(
+            appView.options(), legacySyntheticTypes, nonLecacySyntheticItems)
+        .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
deleted file mode 100644
index 03b7e36..0000000
--- a/src/main/java/com/android/tools/r8/synthesis/SyntheticMarker.java
+++ /dev/null
@@ -1,96 +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.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 60e3079..9fcc7d9 100644
--- a/src/main/java/com/android/tools/r8/synthesis/SyntheticMethodBuilder.java
+++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticMethodBuilder.java
@@ -9,7 +9,6 @@
 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;
@@ -21,25 +20,15 @@
   }
 
   private final SyntheticClassBuilder parent;
-  private DexString name = null;
+  private final String name;
   private DexProto proto = null;
   private CfVersion classFileVersion;
   private SyntheticCodeGenerator codeGenerator = null;
   private MethodAccessFlags accessFlags = null;
 
-  SyntheticMethodBuilder(SyntheticClassBuilder parent) {
+  SyntheticMethodBuilder(SyntheticClassBuilder parent, String name) {
     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) {
@@ -63,7 +52,6 @@
   }
 
   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 0ba1127..ecc1a81 100644
--- a/src/main/java/com/android/tools/r8/synthesis/SyntheticMethodDefinition.java
+++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticMethodDefinition.java
@@ -3,24 +3,27 @@
 // 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;
 
 /**
  * Definition of a synthetic method item.
  *
  * <p>This class is internal to the synthetic items collection, thus package-protected.
  */
-class SyntheticMethodDefinition
-    extends SyntheticDefinition<SyntheticMethodReference, SyntheticMethodDefinition> {
+class SyntheticMethodDefinition extends SyntheticDefinition
+    implements Comparable<SyntheticMethodDefinition> {
 
   private final ProgramMethod method;
 
-  SyntheticMethodDefinition(SyntheticKind kind, SynthesizingContext context, ProgramMethod method) {
-    super(kind, context);
+  SyntheticMethodDefinition(SynthesizingContext context, ProgramMethod method) {
+    super(context);
     this.method = method;
   }
 
@@ -29,8 +32,8 @@
   }
 
   @Override
-  SyntheticMethodReference toReference() {
-    return new SyntheticMethodReference(getKind(), getContext(), method.getReference());
+  SyntheticReference toReference() {
+    return new SyntheticMethodReference(getContext(), method.getReference());
   }
 
   @Override
@@ -39,18 +42,39 @@
   }
 
   @Override
-  void internalComputeHash(Hasher hasher, RepresentativeMap map) {
-    method.getDefinition().hashWithTypeEquivalence(hasher, map);
+  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();
   }
 
   @Override
-  int internalCompareTo(SyntheticMethodDefinition other, RepresentativeMap map) {
-    return method.getDefinition().compareWithTypeEquivalenceTo(other.method.getDefinition(), map);
+  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());
   }
 
+  // Since methods are sharable they must define an order from which representatives can be found.
   @Override
-  public boolean isValid() {
-    return SyntheticMethodBuilder.isValidSyntheticMethod(method.getDefinition());
+  public int compareTo(SyntheticMethodDefinition other) {
+    return Comparator.comparing(SyntheticMethodDefinition::getContext)
+        .thenComparing(m -> m.method.getDefinition(), DexEncodedMethod::syntheticCompareTo)
+        .compare(this, other);
   }
 
   @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 cade86e..28913d0 100644
--- a/src/main/java/com/android/tools/r8/synthesis/SyntheticMethodReference.java
+++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticMethodReference.java
@@ -5,11 +5,10 @@
 
 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.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;
 
 /**
@@ -17,35 +16,37 @@
  *
  * <p>This class is internal to the synthetic items collection, thus package-protected.
  */
-class SyntheticMethodReference
-    extends SyntheticReference<SyntheticMethodReference, SyntheticMethodDefinition> {
+class SyntheticMethodReference extends SyntheticReference {
   final DexMethod method;
 
-  SyntheticMethodReference(SyntheticKind kind, SynthesizingContext context, DexMethod method) {
-    super(kind, context);
+  SyntheticMethodReference(SynthesizingContext context, DexMethod method) {
+    super(context);
     this.method = method;
   }
 
   @Override
+  DexReference getReference() {
+    return method;
+  }
+
+  @Override
   DexType getHolder() {
     return method.holder;
   }
 
   @Override
-  SyntheticMethodDefinition lookupDefinition(Function<DexType, DexClass> definitions) {
+  SyntheticDefinition 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(getKind(), getContext(), definition)
-        : null;
+    return definition != null ? new SyntheticMethodDefinition(getContext(), definition) : null;
   }
 
   @Override
-  SyntheticMethodReference rewrite(NonIdentityGraphLens lens) {
+  SyntheticReference 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.
@@ -53,28 +54,12 @@
       // 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 SyntheticNaming.verifyNotInternalSynthetic(rewritten.holder);
+      assert SyntheticItems.verifyNotInternalSynthetic(rewritten.holder);
       return null;
     }
     SynthesizingContext context = getContext().rewrite(lens);
-    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
-  void apply(
-      Consumer<SyntheticMethodReference> onMethod, Consumer<SyntheticClassReference> onClass) {
-    onMethod.accept(this);
+    return context == getContext() && rewritten == method
+        ? this
+        : new SyntheticMethodReference(context, rewritten);
   }
 }
diff --git a/src/main/java/com/android/tools/r8/synthesis/SyntheticNaming.java b/src/main/java/com/android/tools/r8/synthesis/SyntheticNaming.java
deleted file mode 100644
index 9947f15..0000000
--- a/src/main/java/com/android/tools/r8/synthesis/SyntheticNaming.java
+++ /dev/null
@@ -1,163 +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.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.
-    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),
-    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);
-  }
-
-  private static boolean checkMatchFrom(
-      SyntheticKind kind, String name, int i, String externalSyntheticClassSeparator) {
-    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)
-        && 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/SyntheticReference.java b/src/main/java/com/android/tools/r8/synthesis/SyntheticReference.java
index ea33564..6618378 100644
--- a/src/main/java/com/android/tools/r8/synthesis/SyntheticReference.java
+++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticReference.java
@@ -4,10 +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.Consumer;
 import java.util.function.Function;
 
 /**
@@ -15,24 +14,16 @@
  *
  * <p>This class is internal to the synthetic items collection, thus package-protected.
  */
-abstract class SyntheticReference<
-    R extends SyntheticReference<R, D>, D extends SyntheticDefinition<R, D>> {
-
-  private final SyntheticKind kind;
+abstract class SyntheticReference {
   private final SynthesizingContext context;
 
-  SyntheticReference(SyntheticKind kind, SynthesizingContext context) {
-    assert kind != null;
-    assert context != null;
-    this.kind = kind;
+  SyntheticReference(SynthesizingContext context) {
     this.context = context;
   }
 
-  abstract D lookupDefinition(Function<DexType, DexClass> definitions);
+  abstract SyntheticDefinition lookupDefinition(Function<DexType, DexClass> definitions);
 
-  final SyntheticKind getKind() {
-    return kind;
-  }
+  abstract DexReference getReference();
 
   final SynthesizingContext getContext() {
     return context;
@@ -40,8 +31,5 @@
 
   abstract DexType getHolder();
 
-  abstract R rewrite(NonIdentityGraphLens lens);
-
-  abstract void apply(
-      Consumer<SyntheticMethodReference> onMethod, Consumer<SyntheticClassReference> onClass);
+  abstract SyntheticReference rewrite(NonIdentityGraphLens lens);
 }
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 d0e0a52..db914a4 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -1280,7 +1280,6 @@
 
     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;
 
@@ -1474,8 +1473,6 @@
     public boolean allowInvalidCfAccessFlags =
         System.getProperty("com.android.tools.r8.allowInvalidCfAccessFlags") != null;
 
-    public boolean allowConflictingSyntheticTypes = false;
-
     // Flag to allow processing of resources in D8. A data resource consumer still needs to be
     // specified.
     public boolean enableD8ResourcesPassThrough = false;
@@ -1606,16 +1603,6 @@
     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);
   }
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 fe6feab..40ae9ce 100644
--- a/src/main/java/com/android/tools/r8/utils/ListUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/ListUtils.java
@@ -40,14 +40,6 @@
     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/Pair.java b/src/main/java/com/android/tools/r8/utils/Pair.java
index c2f3489..ac58f56 100644
--- a/src/main/java/com/android/tools/r8/utils/Pair.java
+++ b/src/main/java/com/android/tools/r8/utils/Pair.java
@@ -50,9 +50,4 @@
   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/structural/CompareToVisitorBase.java b/src/main/java/com/android/tools/r8/utils/structural/CompareToVisitorBase.java
index c721599..234732c 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,39 +18,29 @@
 /** 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 debug(Boolean.compare(value1, value2));
+    return Boolean.compare(value1, value2);
   }
 
   @Override
   public final int visitInt(int value1, int value2) {
-    return debug(Integer.compare(value1, value2));
+    return Integer.compare(value1, value2);
   }
 
   @Override
   public int visitLong(long value1, long value2) {
-    return debug(Long.compare(value1, value2));
+    return Long.compare(value1, value2);
   }
 
   @Override
   public int visitFloat(float value1, float value2) {
-    return debug(Float.compare(value1, value2));
+    return Float.compare(value1, value2);
   }
 
   @Override
   public int visitDouble(double value1, double value2) {
-    return debug(Double.compare(value1, value2));
+    return Double.compare(value1, value2);
   }
 
   @Override
@@ -63,12 +53,12 @@
     if (order == 0) {
       order = visitBool(it1.hasNext(), it2.hasNext());
     }
-    return debug(order);
+    return order;
   }
 
   @Override
   public int visitDexString(DexString string1, DexString string2) {
-    return debug(string1.compareTo(string2));
+    return string1.compareTo(string2);
   }
 
   @Override
@@ -84,19 +74,19 @@
         order = visitDexMethod(reference1.asDexMethod(), reference2.asDexMethod());
       }
     }
-    return debug(order);
+    return order;
   }
 
   @Override
   public final <S> int visit(S item1, S item2, Comparator<S> comparator) {
-    return debug(comparator.compare(item1, item2));
+    return 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 debug(itemVisitor.order);
+    return itemVisitor.order;
   }
 
   private static class ItemSpecification<T>
@@ -208,7 +198,7 @@
     }
 
     @Override
-    protected <S> ItemSpecification<T> withCustomItemIterator(
+    protected <S> ItemSpecification<T> withItemIterator(
         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 7569929..f581065 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,35 +29,34 @@
 
   @Override
   public int visitDexType(DexType type1, DexType type2) {
-    return debug(
-        namingLens
-            .lookupDescriptor(type1)
-            .acceptCompareTo(namingLens.lookupDescriptor(type2), this));
+    return 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 debug(order);
+      return order;
     }
     order = namingLens.lookupName(field1).acceptCompareTo(namingLens.lookupName(field2), this);
     if (order != 0) {
-      return debug(order);
+      return order;
     }
-    return debug(field1.type.acceptCompareTo(field2.type, this));
+    return 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 debug(order);
+      return order;
     }
     order = namingLens.lookupName(method1).acceptCompareTo(namingLens.lookupName(method2), this);
     if (order != 0) {
-      return debug(order);
+      return order;
     }
-    return debug(method1.proto.acceptCompareTo(method2.proto, this));
+    return 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 ccb5c9e..89bd02b 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 debug(repr1.getDescriptor().acceptCompareTo(repr2.getDescriptor(), this));
+    return 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 244d404..892183d 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> withCustomItemIterator(
+  protected <S> HashCodeVisitor<T> withItemIterator(
       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 6c388fb..e63ac6c 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> withCustomItemIterator(
+    protected <S> ItemSpecification<T> withItemIterator(
         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 69d8796..a81fc18 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 withCustomItemIterator(
+  protected abstract <S> V withItemIterator(
       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 withCustomItemIterator(getter.andThen(Collection::iterator), acceptor, acceptor);
+    return withItemIterator(getter.andThen(Collection::iterator), acceptor, acceptor);
   }
 
   /**
@@ -79,23 +79,24 @@
         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));
+    return withItemIterator(
+        getter.andThen(Collection::iterator),
+        StructuralItem::acceptCompareTo,
+        StructuralItem::acceptHashing);
   }
 
   public final <S extends StructuralItem<S>> V withItemArray(Function<T, S[]> getter) {
-    return withItemIterator(getter.andThen(a -> Arrays.asList(a).iterator()));
+    return withItemIterator(
+        getter.andThen(a -> Arrays.asList(a).iterator()),
+        StructuralItem::acceptCompareTo,
+        StructuralItem::acceptHashing);
   }
 
   public final <S extends StructuralItem<S>> V withItemArrayAllowingNullMembers(
       Function<T, S[]> getter) {
-    return withCustomItemIterator(
+    return withItemIterator(
         getter.andThen(a -> Arrays.asList(a).iterator()),
         (a, b, visitor) -> {
           if (a == null || b == null) {
diff --git a/src/test/examplesAndroidO/multidex004/ref-list-1.txt b/src/test/examplesAndroidO/multidex004/ref-list-1.txt
index c817c33..ac6e888 100644
--- a/src/test/examplesAndroidO/multidex004/ref-list-1.txt
+++ b/src/test/examplesAndroidO/multidex004/ref-list-1.txt
@@ -1,4 +1,4 @@
-Lmultidex004/MainActivity-$$ExternalSyntheticLambda0;
+Lmultidex004/-$$Lambda$MainActivity$g120D_43GXrTOaB3kfYt6wSIJh4;
 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 82cfebf..d25c465 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.references.ClassReference;
-import com.android.tools.r8.references.Reference;
-import com.android.tools.r8.synthesis.SyntheticItemsTestUtils;
+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.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.AndroidApp;
 import com.android.tools.r8.utils.DescriptorUtils;
@@ -93,12 +93,11 @@
         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.endsWith(InterfaceMethodRewriter.COMPANION_CLASS_NAME_SUFFIX + ";")
-                  || SyntheticItemsTestUtils.isExternalTwrCloseMethod(reference)
-                  || SyntheticItemsTestUtils.isExternalLambda(reference)
-                  || SyntheticItemsTestUtils.isExternalStaticInterfaceCall(reference)
+              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.equals(mainClassDescriptor));
         }
         String classDescriptor =
diff --git a/src/test/java/com/android/tools/r8/R8RunExamplesAndroidOTest.java b/src/test/java/com/android/tools/r8/R8RunExamplesAndroidOTest.java
index 389b0fa..7157943 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, 102, "lambdadesugaring"))
+        .withDexCheck(inspector -> checkLambdaCount(inspector, 101, "lambdadesugaring"))
         .run();
 
     test("lambdadesugaring", "lambdadesugaring", "LambdaDesugaring")
         .withMinApiLevel(ToolHelper.getMinApiLevelForDexVmNoHigherThan(AndroidApiLevel.K))
         .withBuilderTransformation(
             b -> b.addProguardConfiguration(PROGUARD_OPTIONS, Origin.unknown()))
-        .withDexCheck(inspector -> checkLambdaCount(inspector, 7, "lambdadesugaring"))
+        .withDexCheck(inspector -> checkLambdaCount(inspector, 6, "lambdadesugaring"))
         .run();
   }
 
@@ -146,14 +146,14 @@
         .withOptionConsumer(opts -> opts.enableClassInlining = false)
         .withBuilderTransformation(
             b -> b.addProguardConfiguration(PROGUARD_OPTIONS, Origin.unknown()))
-        .withDexCheck(inspector -> checkLambdaCount(inspector, 102, "lambdadesugaring"))
+        .withDexCheck(inspector -> checkLambdaCount(inspector, 101, "lambdadesugaring"))
         .run();
 
     test("lambdadesugaring", "lambdadesugaring", "LambdaDesugaring")
         .withMinApiLevel(AndroidApiLevel.N)
         .withBuilderTransformation(
             b -> b.addProguardConfiguration(PROGUARD_OPTIONS, Origin.unknown()))
-        .withDexCheck(inspector -> checkLambdaCount(inspector, 7, "lambdadesugaring"))
+        .withDexCheck(inspector -> checkLambdaCount(inspector, 6, "lambdadesugaring"))
         .run();
   }
 
diff --git a/src/test/java/com/android/tools/r8/RunExamplesJava9Test.java b/src/test/java/com/android/tools/r8/RunExamplesJava9Test.java
index 53bea05..9f20f9e 100644
--- a/src/test/java/com/android/tools/r8/RunExamplesJava9Test.java
+++ b/src/test/java/com/android/tools/r8/RunExamplesJava9Test.java
@@ -32,7 +32,6 @@
 import java.util.function.Consumer;
 import java.util.function.UnaryOperator;
 import java.util.stream.Collectors;
-import org.junit.Ignore;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.rules.ExpectedException;
@@ -270,7 +269,6 @@
   }
 
   @Test
-  @Ignore("b/177532008: Re-enable once fixed.")
   public void testTwrCloseResourceMethod() throws Throwable {
     TestRunner<?> test = test("twr-close-resource", "twrcloseresource", "TwrCloseResourceTest");
     test
diff --git a/src/test/java/com/android/tools/r8/TestBase.java b/src/test/java/com/android/tools/r8/TestBase.java
index bb69054..e1d4c9e 100644
--- a/src/test/java/com/android/tools/r8/TestBase.java
+++ b/src/test/java/com/android/tools/r8/TestBase.java
@@ -1702,10 +1702,6 @@
     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/classmerging/horizontal/JavaLambdaMergingTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/JavaLambdaMergingTest.java
index 63c1f18..adabb3e 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,18 +4,16 @@
 
 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 {
@@ -25,7 +23,6 @@
   }
 
   @Test
-  @Ignore("b/174809311): Test does not work with hygienic lambdas. Rewrite or remove")
   public void test() throws Exception {
     testForR8(parameters.getBackend())
         .addInnerClasses(getClass())
@@ -41,12 +38,16 @@
             inspector -> {
               Set<DexType> lambdaSources =
                   inspector.getSources().stream()
-                      .filter(JavaLambdaMergingTest::isLambda)
+                      .filter(x -> x.toSourceString().contains(LAMBDA_CLASS_NAME_PREFIX))
                       .collect(Collectors.toSet());
               assertEquals(3, lambdaSources.size());
               DexType firstTarget = inspector.getTarget(lambdaSources.iterator().next());
               for (DexType lambdaSource : lambdaSources) {
-                assertTrue(isLambda(inspector.getTarget(lambdaSource)));
+                assertTrue(
+                    inspector
+                        .getTarget(lambdaSource)
+                        .toSourceString()
+                        .contains(LAMBDA_CLASS_NAME_PREFIX));
                 assertEquals(firstTarget, inspector.getTarget(lambdaSource));
               }
             })
@@ -58,11 +59,6 @@
         .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/debug/DebugTestBase.java b/src/test/java/com/android/tools/r8/debug/DebugTestBase.java
index a5a1a8a..13d59dc 100644
--- a/src/test/java/com/android/tools/r8/debug/DebugTestBase.java
+++ b/src/test/java/com/android/tools/r8/debug/DebugTestBase.java
@@ -9,7 +9,6 @@
 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;
@@ -2162,28 +2161,8 @@
       }
 
       private static boolean isInLambdaClass(VmMirror mirror, Location location) {
-        // 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;
+        String classSig = mirror.getClassSignature(location.classID);
+        return classSig.contains("$$Lambda$");
       }
 
       private static boolean isLambdaMethod(VmMirror mirror, Location location) {
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 875ead2..8f304c6 100644
--- a/src/test/java/com/android/tools/r8/desugar/DesugarMissingTypeLambdaTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/DesugarMissingTypeLambdaTest.java
@@ -3,6 +3,8 @@
 // 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;
 
@@ -17,7 +19,6 @@
 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;
@@ -78,7 +79,7 @@
         assertEquals(
             Reference.classFromClass(MissingInterface.class), desugarWarning.getMissingType());
         // TODO(b/132671303): The context class should not be the synthesized lambda class.
-        assertTrue(SyntheticItemsTestUtils.isInternalLambda(desugarWarning.getContextType()));
+        assertThat(desugarWarning.getContextType().getDescriptor(), containsString("$$Lambda"));
         // 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 dc6e894..c9377fe 100644
--- a/src/test/java/com/android/tools/r8/desugar/DesugarToClassFile.java
+++ b/src/test/java/com/android/tools/r8/desugar/DesugarToClassFile.java
@@ -48,7 +48,7 @@
   private void checkHasLambdaClass(CodeInspector inspector) {
     assertTrue(
         inspector.allClasses().stream()
-            .anyMatch(subject -> subject.isSynthesizedJavaLambdaClass()));
+            .anyMatch(subject -> subject.getOriginalName().contains("-$$Lambda$")));
   }
 
   @Test
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 acfc021..d094c78 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,6 +3,9 @@
 // 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;
@@ -13,10 +16,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.SyntheticItemsTestUtils;
-import com.android.tools.r8.synthesis.SyntheticNaming;
+import com.android.tools.r8.synthesis.SyntheticItems;
 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;
@@ -197,7 +200,9 @@
   private void checkNoOriginalsAndNoInternalSynthetics(CodeInspector inspector) {
     inspector.forAllClasses(
         clazz -> {
-          SyntheticNaming.verifyNotInternalSynthetic(clazz.getFinalReference());
+          assertThat(
+              clazz.getFinalName(),
+              not(containsString(SyntheticItems.INTERNAL_SYNTHETIC_CLASS_SEPARATOR)));
           if (!clazz.getOriginalName().equals(MiniAssert.class.getTypeName())) {
             clazz.forAllMethods(
                 method ->
@@ -228,11 +233,11 @@
     // of intermediates.
     Set<MethodReference> expectedSynthetics =
         ImmutableSet.of(
-            SyntheticItemsTestUtils.syntheticBackportMethod(
+            SyntheticItemsTestUtils.syntheticMethod(
                 User1.class, 0, Character.class.getMethod("compare", char.class, char.class)),
-            SyntheticItemsTestUtils.syntheticBackportMethod(
+            SyntheticItemsTestUtils.syntheticMethod(
                 User1.class, 1, Boolean.class.getMethod("compare", boolean.class, boolean.class)),
-            SyntheticItemsTestUtils.syntheticBackportMethod(
+            SyntheticItemsTestUtils.syntheticMethod(
                 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 05f84af..45f8957 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,6 +3,7 @@
 // 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;
@@ -24,11 +25,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;
@@ -56,6 +57,11 @@
   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}")
@@ -335,16 +341,16 @@
   private ImmutableSet<MethodReference> getNonMainDexExpectedSynthetics()
       throws NoSuchMethodException {
     return ImmutableSet.of(
-        SyntheticItemsTestUtils.syntheticBackportMethod(
+        SyntheticItemsTestUtils.syntheticMethod(
             User1.class, 1, Boolean.class.getMethod("compare", boolean.class, boolean.class)));
   }
 
   private ImmutableSet<MethodReference> getMainDexExpectedSynthetics()
       throws NoSuchMethodException {
     return ImmutableSet.of(
-        SyntheticItemsTestUtils.syntheticBackportMethod(
+        SyntheticItemsTestUtils.syntheticMethod(
             User1.class, 0, Character.class.getMethod("compare", char.class, char.class)),
-        SyntheticItemsTestUtils.syntheticBackportMethod(
+        SyntheticItemsTestUtils.syntheticMethod(
             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 fdb1044..2dbcc30 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,7 +62,12 @@
             codeInspector -> {
               boolean foundBridge = false;
               for (FoundClassSubject clazz : codeInspector.allClasses()) {
-                if (clazz.isSynthesizedJavaLambdaClass()) {
+                if (clazz
+                    .getOriginalName()
+                    .contains(
+                        "-$$Lambda$"
+                            + LambdaWithMultipleImplementingInterfaces.class.getSimpleName()
+                            + "$")) {
                   // 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/DesugaredLibraryDeterminismTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DesugaredLibraryDeterminismTest.java
index bfb1885..f7b5e5d 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,16 +17,11 @@
 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;
@@ -58,29 +53,12 @@
   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(m -> m.toString()).collect(Collectors.toSet());
-      Set<String> methods2 =
-          clazz2.allMethods().stream().map(m -> m.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());
@@ -101,7 +79,7 @@
     assertTrue(printDiffs(diffs), diffs.isEmpty());
   }
 
-  private static String printDiffs(Map<DexEncodedMethod, DexEncodedMethod> diffs) {
+  private 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/lambdas/DeduplicateLambdasWithDefaultMethodsTest.java b/src/test/java/com/android/tools/r8/desugar/lambdas/DeduplicateLambdasWithDefaultMethodsTest.java
deleted file mode 100644
index b252cf4..0000000
--- a/src/test/java/com/android/tools/r8/desugar/lambdas/DeduplicateLambdasWithDefaultMethodsTest.java
+++ /dev/null
@@ -1,107 +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.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
deleted file mode 100644
index 90c8009..0000000
--- a/src/test/java/com/android/tools/r8/desugar/lambdas/LambdaEqualityTest.java
+++ /dev/null
@@ -1,140 +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.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
deleted file mode 100644
index 1a6e364..0000000
--- a/src/test/java/com/android/tools/r8/desugar/lambdas/LambdaNamingConflictTest.java
+++ /dev/null
@@ -1,113 +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.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
deleted file mode 100644
index ac5b076..0000000
--- a/src/test/java/com/android/tools/r8/desugar/lambdas/LambdaStaticInstanceFieldDuplicationTest.java
+++ /dev/null
@@ -1,281 +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.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
deleted file mode 100644
index 5b73125..0000000
--- a/src/test/java/com/android/tools/r8/desugar/lambdas/LambdaToSysOutPrintlnDuplicationTest.java
+++ /dev/null
@@ -1,265 +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.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
deleted file mode 100644
index dd00acf..0000000
--- a/src/test/java/com/android/tools/r8/desugar/lambdas/LegacyLambdaMergeTest.java
+++ /dev/null
@@ -1,123 +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.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
deleted file mode 100644
index 35e31d5..0000000
--- a/src/test/java/com/android/tools/r8/desugar/lambdas/mergedcontext/MergedContextTest.java
+++ /dev/null
@@ -1,106 +0,0 @@
-// Copyright (c) 2021, the R8 project authors. Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
-package com.android.tools.r8.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/twr/TwrCloseResourceDuplicationTest.java b/src/test/java/com/android/tools/r8/desugar/twr/TwrCloseResourceDuplicationTest.java
deleted file mode 100644
index dde98e2..0000000
--- a/src/test/java/com/android/tools/r8/desugar/twr/TwrCloseResourceDuplicationTest.java
+++ /dev/null
@@ -1,157 +0,0 @@
-// Copyright (c) 2021, the R8 project authors. Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
-package com.android.tools.r8.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)
-        .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 5730599..934a15b 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
@@ -47,9 +47,9 @@
     if (parameters.isDexRuntime()) {
       result.inspect(
           inspector -> {
-            // With the hygienic synthetics the reference to System.out::print can always be shared.
+            // When in the same package we expect the two System.out::print lambdas to be shared.
             assertEquals(
-                2,
+                samePackage ? 2 : 3,
                 inspector.allClasses().stream()
                     .filter(c -> c.isSynthesizedJavaLambdaClass())
                     .count());
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 38bb02e..e0266bd 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,6 +4,7 @@
 
 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;
@@ -321,8 +322,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/maindexlist/MainDexListOutputTest.java b/src/test/java/com/android/tools/r8/maindexlist/MainDexListOutputTest.java
index 1bbcb9e..7565cf7 100644
--- a/src/test/java/com/android/tools/r8/maindexlist/MainDexListOutputTest.java
+++ b/src/test/java/com/android/tools/r8/maindexlist/MainDexListOutputTest.java
@@ -6,7 +6,6 @@
 
 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;
 
@@ -17,7 +16,6 @@
 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;
@@ -61,8 +59,8 @@
     @Override
     public void finished(DiagnosticsHandler handler) {
       String string = builder.toString();
-      assertThat(string, containsString(testClassMainDexName));
-      assertThat(string, SyntheticItemsTestUtils.containsExternalSyntheticReference());
+      assertTrue(string.contains(testClassMainDexName));
+      assertTrue(string.contains("Lambda"));
     }
   }
 
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 cf2aeeb..52b60fe 100644
--- a/src/test/java/com/android/tools/r8/maindexlist/MainDexTracingTest.java
+++ b/src/test/java/com/android/tools/r8/maindexlist/MainDexTracingTest.java
@@ -24,9 +24,9 @@
 import com.android.tools.r8.TestParameters;
 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;
@@ -433,13 +433,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 (!isExternalSyntheticLambda(reference)) {
+      if (!isLambda(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 (isExternalSyntheticLambda(fromList)) {
+        if (isLambda(fromList)) {
           assertEquals(Backend.DEX, backend);
           assertEquals(fromList, fromConsumer);
           nonLambdaOffset--;
@@ -481,8 +481,8 @@
     assertArrayEquals(entriesUnsorted, entriesSorted);
   }
 
-  private boolean isExternalSyntheticLambda(String mainDexEntry) {
-    return SyntheticItemsTestUtils.isExternalLambda(Reference.classFromDescriptor(mainDexEntry));
+  private boolean isLambda(String mainDexEntry) {
+    return mainDexEntry.contains(LambdaRewriter.LAMBDA_CLASS_NAME_PREFIX);
   }
 
   private String mainDexStringToDescriptor(String mainDexString) {
@@ -492,8 +492,11 @@
   }
 
   private void checkSameMainDexEntry(String reference, String computed) {
-    if (isExternalSyntheticLambda(reference)) {
-      // For synthetic classes we check that the context classes match.
+    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.
       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 9fa7fe9..d102004 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.synthesis.SyntheticItemsTestUtils;
+import com.android.tools.r8.ir.desugar.LambdaRewriter;
 import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.StringUtils;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
@@ -100,11 +100,10 @@
         inspector.allClasses().stream()
             .anyMatch(
                 clazz ->
-                    clazz.isSynthesizedJavaLambdaClass()
+                    clazz.getOriginalName().contains(LambdaRewriter.LAMBDA_CLASS_NAME_PREFIX)
                         && clazz
-                            .getOriginalReference()
-                            .equals(
-                                SyntheticItemsTestUtils.syntheticLambdaClass(lambdaHolder, 0))));
+                            .getOriginalName()
+                            .contains("$" + lambdaHolder.getSimpleName() + "$")));
   }
 
   static class TestClass {
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 6ae3a6f..3959d84 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,8 +14,6 @@
 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;
@@ -59,8 +57,7 @@
   }
 
   private boolean isSynthesizedLambdaFrame(StackTraceLine line) {
-    // TODO(141287349): The mapping should not map the external name to the internal name!
-    return SyntheticItemsTestUtils.isInternalLambda(Reference.classFromTypeName(line.className));
+    return line.className.contains("-$$Lambda$");
   }
 
   private void checkLambdaFrame(StackTrace retracedStackTrace) {
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 fd1a05a..688a96f 100644
--- a/src/test/java/com/android/tools/r8/rewrite/ServiceLoaderMultipleCallsTest.java
+++ b/src/test/java/com/android/tools/r8/rewrite/ServiceLoaderMultipleCallsTest.java
@@ -4,10 +4,11 @@
 
 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.junit.Assert.assertFalse;
+import static org.hamcrest.MatcherAssert.assertThat;
 
 import com.android.tools.r8.CompilationFailedException;
 import com.android.tools.r8.DataEntryResource;
@@ -19,7 +20,6 @@
 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,18 +109,11 @@
             inspector -> {
               // Check that we have actually rewritten the calls to ServiceLoader.load.
               assertEquals(0, getServiceLoaderLoads(inspector, MainRunner.class));
-              // 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 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 that we have removed the service configuration from META-INF/services.
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 c2f8041..cd153f2 100644
--- a/src/test/java/com/android/tools/r8/shaking/PreserveDesugaredLambdaTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/PreserveDesugaredLambdaTest.java
@@ -79,7 +79,8 @@
                     codeInspector.allClasses().stream()
                         .anyMatch(
                             c -> {
-                              if (c.isSynthesizedJavaLambdaClass()) {
+                              if (c.getOriginalName()
+                                  .contains("-$$Lambda$PreserveDesugaredLambdaTest$Main")) {
                                 assertThat(c.uniqueMethodWithName("computeTheFoo"), isPresent());
                                 return true;
                               }
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 436c030..4d385a0 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,7 +21,6 @@
 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;
@@ -29,7 +28,6 @@
 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;
@@ -131,13 +129,12 @@
     consumer.printWhyAreYouKeeping(classFromClass(A.class), new PrintStream(baos));
     assertThat(baos.toString(), containsString(KEPT_REASON_SUFFIX));
 
-    // TODO(b/124499108): Currently (internal) synthetic lambda classes are referenced,
+    // TODO(b/124499108): Currently synthetic lambda classes are referenced,
     //  should be their originating context.
-    Matcher<String> hasLambda = SyntheticItemsTestUtils.containsInternalSyntheticReference();
     if (parameters.isDexRuntime()) {
-      assertThat(baos.toString(), hasLambda);
+      assertThat(baos.toString(), containsString("-$$Lambda$"));
     } else {
-      assertThat(baos.toString(), not(hasLambda));
+      assertThat(baos.toString(), not(containsString("-$$Lambda$")));
     }
   }
 }
diff --git a/src/test/java/com/android/tools/r8/synthesis/SyntheticItemsTestUtils.java b/src/test/java/com/android/tools/r8/synthesis/SyntheticItemsTestUtils.java
deleted file mode 100644
index 438d3f9..0000000
--- a/src/test/java/com/android/tools/r8/synthesis/SyntheticItemsTestUtils.java
+++ /dev/null
@@ -1,77 +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.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/SyntheticItemsTestUtils.java b/src/test/java/com/android/tools/r8/utils/SyntheticItemsTestUtils.java
new file mode 100644
index 0000000..9e78bac
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/utils/SyntheticItemsTestUtils.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.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 a42fa4b..ffe8151 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,11 +136,6 @@
   }
 
   @Override
-  public ClassReference getOriginalReference() {
-    return null;
-  }
-
-  @Override
   public ClassReference getFinalReference() {
     return null;
   }
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 f927e62..3a9976f 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,7 +285,6 @@
         && ((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 20d7466..a2d32eb 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,8 +191,6 @@
 
   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 2ae5f05..4b9f733 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,11 +209,6 @@
     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/FoundClassSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundClassSubject.java
index 4220b50..a676666 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,7 +34,6 @@
 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;
@@ -390,11 +389,6 @@
   }
 
   @Override
-  public ClassReference getOriginalReference() {
-    return Reference.classFromDescriptor(getOriginalDescriptor());
-  }
-
-  @Override
   public ClassReference getFinalReference() {
     return Reference.classFromDescriptor(getFinalDescriptor());
   }
@@ -436,9 +430,7 @@
 
   @Override
   public boolean isSynthesizedJavaLambdaClass() {
-    // TODO(141287349): Make this precise based on the map input.
-    return SyntheticItemsTestUtils.isExternalLambda(getOriginalReference())
-        || SyntheticItemsTestUtils.isExternalLambda(getFinalReference());
+    return dexClass.type.getName().contains("$Lambda$");
   }
 
   @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 8b34260..56436e9 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,8 +43,6 @@
 
   boolean isInvokeSpecial();
 
-  boolean isInvokeDynamic();
-
   DexMethod getMethod();
 
   boolean isNop();
diff --git a/tools/toolhelper.py b/tools/toolhelper.py
index 1cfd0f0..45d84e2 100644
--- a/tools/toolhelper.py
+++ b/tools/toolhelper.py
@@ -27,10 +27,6 @@
   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:
@@ -107,13 +103,3 @@
     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