Merge commit '80bb4c3a9fe91ce7bf9c54297894efb8af21dc52' into dev-release
diff --git a/infra/config/global/cr-buildbucket.cfg b/infra/config/global/cr-buildbucket.cfg
index 65521a9..959f32b 100644
--- a/infra/config/global/cr-buildbucket.cfg
+++ b/infra/config/global/cr-buildbucket.cfg
@@ -176,6 +176,15 @@
       }
     }
     builders {
+      name: "linux-jdk11"
+      mixins: "linux"
+      mixins: "normal"
+      priority: 26
+      recipe {
+        properties_j: "test_options:[\"--runtimes=jdk11\", \"--no_internal\", \"--one_line_per_test\", \"--archive_failures\"]"
+      }
+    }
+    builders {
       name: "linux_release"
       mixins: "normal"
       mixins: "linux"
@@ -184,6 +193,33 @@
       }
     }
     builders {
+      name: "linux-jdk8_release"
+      mixins: "linux"
+      mixins: "normal"
+      priority: 26
+      recipe {
+        properties_j: "test_options:[\"--runtimes=jdk8\", \"--no_internal\", \"--one_line_per_test\", \"--archive_failures\"]"
+      }
+    }
+    builders {
+      name: "linux-jdk9_release"
+      mixins: "linux"
+      mixins: "normal"
+      priority: 26
+      recipe {
+        properties_j: "test_options:[\"--runtimes=jdk9\", \"--no_internal\", \"--one_line_per_test\", \"--archive_failures\"]"
+      }
+    }
+    builders {
+      name: "linux-jdk11_release"
+      mixins: "linux"
+      mixins: "normal"
+      priority: 26
+      recipe {
+        properties_j: "test_options:[\"--runtimes=jdk11\", \"--no_internal\", \"--one_line_per_test\", \"--archive_failures\"]"
+      }
+    }
+    builders {
       name: "linux-android-4.0.4"
       mixins: "linux"
       mixins: "normal"
diff --git a/infra/config/global/luci-milo.cfg b/infra/config/global/luci-milo.cfg
index 0e7614f..1d4ce51 100644
--- a/infra/config/global/luci-milo.cfg
+++ b/infra/config/global/luci-milo.cfg
@@ -26,6 +26,11 @@
     short_name: "jdk9"
   }
   builders {
+    name: "buildbucket/luci.r8.ci/linux-jdk11"
+    category: "R8"
+    short_name: "jdk11"
+  }
+  builders {
     name: "buildbucket/luci.r8.ci/linux-android-4.0.4"
     category: "R8"
     short_name: "4.0.4"
@@ -111,6 +116,21 @@
     short_name: "linux"
   }
   builders {
+    name: "buildbucket/luci.r8.ci/linux-jdk8_release"
+    category: "R8 release"
+    short_name: "jdk8"
+  }
+  builders {
+    name: "buildbucket/luci.r8.ci/linux-jdk9_release"
+    category: "R8 release"
+    short_name: "jdk9"
+  }
+  builders {
+    name: "buildbucket/luci.r8.ci/linux-jdk11_release"
+    category: "R8 release"
+    short_name: "jdk11"
+  }
+  builders {
     name: "buildbucket/luci.r8.ci/linux-android-4.0.4_release"
     category: "R8 release"
     short_name: "4.0.4"
diff --git a/infra/config/global/luci-scheduler.cfg b/infra/config/global/luci-scheduler.cfg
index f819ee4..edc7dbf 100644
--- a/infra/config/global/luci-scheduler.cfg
+++ b/infra/config/global/luci-scheduler.cfg
@@ -29,6 +29,7 @@
   triggers: "linux"
   triggers: "linux-jdk8"
   triggers: "linux-jdk9"
+  triggers: "linux-jdk11"
   triggers: "linux-android-4.0.4"
   triggers: "linux-android-4.4.4"
   triggers: "linux-android-5.1.1"
@@ -75,6 +76,10 @@
     path_regexps: "src/main/java/com/android/tools/r8/Version.java"
   }
   triggers: "archive_release"
+  triggers: "linux_release"
+  triggers: "linux-jdk8_release"
+  triggers: "linux-jdk9_release"
+  triggers: "linux-jdk11_release"
   triggers: "linux-android-4.0.4_release"
   triggers: "linux-android-4.4.4_release"
   triggers: "linux-android-5.1.1_release"
@@ -85,7 +90,6 @@
   triggers: "linux-android-10.0.0_release"
   triggers: "linux-internal_release"
   triggers: "linux-jctf_release"
-  triggers: "linux_release"
   triggers: "r8cf-linux-jctf_release"
   triggers: "windows_release"
 }
@@ -200,6 +204,20 @@
 }
 
 job {
+  id: "linux-jdk8_release"
+  acl_sets: "default"
+  triggering_policy: {
+    kind: GREEDY_BATCHING
+    max_concurrent_invocations: 2
+  }
+  buildbucket {
+    server: "cr-buildbucket.appspot.com"
+    bucket: "luci.r8.ci"
+    builder: "linux-jdk8_release"
+  }
+}
+
+job {
   id: "linux-jdk9"
   acl_sets: "default"
   triggering_policy: {
@@ -214,6 +232,48 @@
 }
 
 job {
+  id: "linux-jdk9_release"
+  acl_sets: "default"
+  triggering_policy: {
+    kind: GREEDY_BATCHING
+    max_concurrent_invocations: 2
+  }
+  buildbucket {
+    server: "cr-buildbucket.appspot.com"
+    bucket: "luci.r8.ci"
+    builder: "linux-jdk9_release"
+  }
+}
+
+job {
+  id: "linux-jdk11"
+  acl_sets: "default"
+  triggering_policy: {
+    kind: GREEDY_BATCHING
+    max_concurrent_invocations: 2
+  }
+  buildbucket {
+    server: "cr-buildbucket.appspot.com"
+    bucket: "luci.r8.ci"
+    builder: "linux-jdk11"
+  }
+}
+
+job {
+  id: "linux-jdk11_release"
+  acl_sets: "default"
+  triggering_policy: {
+    kind: GREEDY_BATCHING
+    max_concurrent_invocations: 2
+  }
+  buildbucket {
+    server: "cr-buildbucket.appspot.com"
+    bucket: "luci.r8.ci"
+    builder: "linux-jdk11_release"
+  }
+}
+
+job {
   id: "linux-android-4.0.4"
   acl_sets: "default"
   triggering_policy: {
diff --git a/src/main/java/com/android/tools/r8/D8.java b/src/main/java/com/android/tools/r8/D8.java
index 9b3f92c..b1c7df4 100644
--- a/src/main/java/com/android/tools/r8/D8.java
+++ b/src/main/java/com/android/tools/r8/D8.java
@@ -310,6 +310,7 @@
         // Since tracing is not lens aware, this needs to be done prior to synthetic finalization
         // which will construct a graph lens.
         if (!options.mainDexKeepRules.isEmpty()) {
+          appView.dexItemFactory().clearTypeElementsCache();
           MainDexInfo mainDexInfo =
               new GenerateMainDexList(options)
                   .traceMainDex(
diff --git a/src/main/java/com/android/tools/r8/L8Command.java b/src/main/java/com/android/tools/r8/L8Command.java
index 27108a1..af10925 100644
--- a/src/main/java/com/android/tools/r8/L8Command.java
+++ b/src/main/java/com/android/tools/r8/L8Command.java
@@ -430,6 +430,9 @@
   private DumpOptions dumpOptions() {
     DumpOptions.Builder builder = DumpOptions.builder(Tool.L8);
     dumpBaseCommandOptions(builder);
+    if (r8Command != null) {
+      builder.setProguardConfiguration(r8Command.getInternalOptions().getProguardConfiguration());
+    }
     return builder.setDesugaredLibraryConfiguration(libraryConfiguration).build();
   }
 }
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java
index 546fad4..8a4c9cc 100644
--- a/src/main/java/com/android/tools/r8/R8.java
+++ b/src/main/java/com/android/tools/r8/R8.java
@@ -103,6 +103,7 @@
 import com.android.tools.r8.shaking.VerticalClassMergerGraphLens;
 import com.android.tools.r8.shaking.WhyAreYouKeepingConsumer;
 import com.android.tools.r8.synthesis.SyntheticFinalization;
+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.CfgPrinter;
@@ -291,6 +292,7 @@
 
         appView = AppView.createForR8(application, mainDexInfo);
         appView.setAppServices(AppServices.builder(appView).build());
+        SyntheticItems.collectSyntheticInputs(appView);
       }
 
       // Check for potentially having pass-through of Cf-code for kotlin libraries.
diff --git a/src/main/java/com/android/tools/r8/dex/ApplicationReader.java b/src/main/java/com/android/tools/r8/dex/ApplicationReader.java
index df1a88b..e0c1cc2 100644
--- a/src/main/java/com/android/tools/r8/dex/ApplicationReader.java
+++ b/src/main/java/com/android/tools/r8/dex/ApplicationReader.java
@@ -297,7 +297,12 @@
             () -> {
               try {
                 String content = map.getString();
-                builder.setProguardMap(ClassNameMapper.mapperFromString(content));
+                builder.setProguardMap(
+                    ClassNameMapper.mapperFromString(
+                        content,
+                        options.reporter,
+                        false,
+                        options.testing.enableExperimentalMapFileVersion));
               } catch (IOException | ResourceException e) {
                 throw new CompilationError("Failure to read proguard map file", e, map.getOrigin());
               }
diff --git a/src/main/java/com/android/tools/r8/dex/CodeToKeep.java b/src/main/java/com/android/tools/r8/dex/CodeToKeep.java
index f4e85a8..2261228 100644
--- a/src/main/java/com/android/tools/r8/dex/CodeToKeep.java
+++ b/src/main/java/com/android/tools/r8/dex/CodeToKeep.java
@@ -79,9 +79,12 @@
 
     @Override
     void recordMethod(DexMethod method) {
-      if (shouldKeep(method.holder)) {
-        keepClass(method.holder);
-        toKeep.get(method.holder).methods.add(method);
+      DexType baseType = method.holder.toBaseType(options.dexItemFactory());
+      if (shouldKeep(baseType)) {
+        keepClass(baseType);
+        if (!method.holder.isArrayType()) {
+          toKeep.get(method.holder).methods.add(method);
+        }
       }
       if (shouldKeep(method.proto.returnType)) {
         keepClass(method.proto.returnType);
@@ -95,9 +98,12 @@
 
     @Override
     void recordField(DexField field) {
-      if (shouldKeep(field.holder)) {
-        keepClass(field.holder);
-        toKeep.get(field.holder).fields.add(field);
+      DexType baseType = field.holder.toBaseType(options.dexItemFactory());
+      if (shouldKeep(baseType)) {
+        keepClass(baseType);
+        if (!field.holder.isArrayType()) {
+          toKeep.get(field.holder).fields.add(field);
+        }
       }
       if (shouldKeep(field.type)) {
         keepClass(field.type);
diff --git a/src/main/java/com/android/tools/r8/graph/AppView.java b/src/main/java/com/android/tools/r8/graph/AppView.java
index 7321cc0..1cd36de 100644
--- a/src/main/java/com/android/tools/r8/graph/AppView.java
+++ b/src/main/java/com/android/tools/r8/graph/AppView.java
@@ -558,6 +558,10 @@
     return true;
   }
 
+  public boolean hasClassHierarchy() {
+    return appInfo().hasClassHierarchy();
+  }
+
   @SuppressWarnings("unchecked")
   public AppView<AppInfoWithClassHierarchy> withClassHierarchy() {
     return appInfo.hasClassHierarchy()
@@ -565,6 +569,12 @@
         : null;
   }
 
+  @SuppressWarnings("unchecked")
+  public AppView<AppInfo> withoutClassHierarchy() {
+    assert !hasClassHierarchy();
+    return (AppView<AppInfo>) this;
+  }
+
   public boolean hasLiveness() {
     return appInfo().hasLiveness();
   }
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 ceb543f..646fca7 100644
--- a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
+++ b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
@@ -536,6 +536,8 @@
   public final DexType androidUtilPropertyType =
       createStaticallyKnownType("Landroid/util/Property;");
   public final DexType androidViewViewType = createStaticallyKnownType("Landroid/view/View;");
+  public final DexType androidUtilSparseArrayType =
+      createStaticallyKnownType("Landroid/util/SparseArray;");
 
   public final StringBuildingMethods stringBuilderMethods =
       new StringBuildingMethods(stringBuilderType);
@@ -581,6 +583,8 @@
   public final AndroidSystemOsConstantsMembers androidSystemOsConstantsMembers =
       new AndroidSystemOsConstantsMembers();
   public final AndroidViewViewMembers androidViewViewMembers = new AndroidViewViewMembers();
+  public final AndroidUtilSparseArrayMembers androidUtilSparseArrayMembers =
+      new AndroidUtilSparseArrayMembers();
 
   // java.**
   public final JavaIoFileMembers javaIoFileMembers = new JavaIoFileMembers();
@@ -959,6 +963,13 @@
     }
   }
 
+  public class AndroidUtilSparseArrayMembers extends LibraryMembers {
+    public final DexMethod put =
+        createMethod(androidUtilSparseArrayType, createProto(voidType, intType, objectType), "put");
+    public final DexMethod set =
+        createMethod(androidUtilSparseArrayType, createProto(voidType, intType, objectType), "set");
+  }
+
   public class BooleanMembers extends LibraryMembers {
 
     public final DexField FALSE = createField(boxedBooleanType, boxedBooleanType, "FALSE");
diff --git a/src/main/java/com/android/tools/r8/graph/DexString.java b/src/main/java/com/android/tools/r8/graph/DexString.java
index 031ee9d..af2ad74 100644
--- a/src/main/java/com/android/tools/r8/graph/DexString.java
+++ b/src/main/java/com/android/tools/r8/graph/DexString.java
@@ -40,6 +40,10 @@
     return this;
   }
 
+  public int size() {
+    return size;
+  }
+
   @Override
   public StructuralMapping<DexString> getStructuralMapping() {
     // Structural accept is never accessed as all accept methods are defined directly.
diff --git a/src/main/java/com/android/tools/r8/graph/EnclosingMethodAttribute.java b/src/main/java/com/android/tools/r8/graph/EnclosingMethodAttribute.java
index 82f34d7..9cec3cb 100644
--- a/src/main/java/com/android/tools/r8/graph/EnclosingMethodAttribute.java
+++ b/src/main/java/com/android/tools/r8/graph/EnclosingMethodAttribute.java
@@ -55,6 +55,10 @@
     return enclosingClass;
   }
 
+  public DexType getEnclosingType() {
+    return enclosingMethod != null ? enclosingMethod.getHolderType() : enclosingClass;
+  }
+
   @Override
   public int hashCode() {
     assert (enclosingClass == null) != (enclosingMethod == null);
diff --git a/src/main/java/com/android/tools/r8/graph/GenericSignature.java b/src/main/java/com/android/tools/r8/graph/GenericSignature.java
index bf7c42b..e38a163 100644
--- a/src/main/java/com/android/tools/r8/graph/GenericSignature.java
+++ b/src/main/java/com/android/tools/r8/graph/GenericSignature.java
@@ -7,11 +7,11 @@
 import static com.android.tools.r8.utils.DescriptorUtils.getDescriptorFromClassBinaryName;
 import static com.google.common.base.Predicates.alwaysTrue;
 
+import com.android.tools.r8.DiagnosticsHandler;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.naming.NamingLens;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.utils.DescriptorUtils;
-import com.android.tools.r8.utils.Reporter;
 import com.google.common.collect.ImmutableList;
 import java.lang.reflect.GenericSignatureFormatError;
 import java.nio.CharBuffer;
@@ -139,6 +139,10 @@
       return false;
     }
 
+    default boolean isValid() {
+      return !isInvalid();
+    }
+
     DexDefinitionSignature<T> toInvalid();
   }
 
@@ -153,6 +157,7 @@
       this.name = name;
       this.classBound = classBound;
       this.interfaceBounds = interfaceBounds;
+      assert interfaceBounds != null;
     }
 
     public String getName() {
@@ -516,7 +521,9 @@
       this.type = type;
       this.typeArguments = typeArguments;
       this.enclosingTypeSignature = enclosingTypeSignature;
+      assert type != DexItemFactory.nullValueType || indicator == WildcardIndicator.NOT_AN_ARGUMENT;
       assert typeArguments.stream().allMatch(FieldTypeSignature::isArgument);
+      assert typeArguments.stream().allMatch(FieldTypeSignature::hasSignature);
     }
 
     public DexType type() {
@@ -540,6 +547,7 @@
     @Override
     public ClassTypeSignature asArgument(WildcardIndicator indicator) {
       assert indicator != WildcardIndicator.NOT_AN_ARGUMENT;
+      assert hasSignature();
       return new ClassTypeSignature(type, typeArguments, enclosingTypeSignature, indicator);
     }
 
@@ -857,7 +865,7 @@
       String signature,
       Origin origin,
       DexItemFactory factory,
-      Reporter reporter) {
+      DiagnosticsHandler diagnosticsHandler) {
     if (signature == null || signature.isEmpty()) {
       return ClassSignature.NO_CLASS_SIGNATURE;
     }
@@ -865,7 +873,7 @@
     try {
       return parser.parseClassSignature(signature);
     } catch (GenericSignatureFormatError e) {
-      reporter.warning(
+      diagnosticsHandler.warning(
           GenericSignatureDiagnostic.invalidClassSignature(signature, className, origin, e));
       return ClassSignature.NO_CLASS_SIGNATURE;
     }
@@ -876,7 +884,7 @@
       String signature,
       Origin origin,
       DexItemFactory factory,
-      Reporter reporter) {
+      DiagnosticsHandler diagnosticsHandler) {
     if (signature == null || signature.isEmpty()) {
       return NO_FIELD_TYPE_SIGNATURE;
     }
@@ -884,7 +892,7 @@
     try {
       return parser.parseFieldTypeSignature(signature);
     } catch (GenericSignatureFormatError e) {
-      reporter.warning(
+      diagnosticsHandler.warning(
           GenericSignatureDiagnostic.invalidFieldSignature(signature, fieldName, origin, e));
       return GenericSignature.NO_FIELD_TYPE_SIGNATURE;
     }
@@ -895,7 +903,7 @@
       String signature,
       Origin origin,
       DexItemFactory factory,
-      Reporter reporter) {
+      DiagnosticsHandler diagnosticsHandler) {
     if (signature == null || signature.isEmpty()) {
       return MethodTypeSignature.NO_METHOD_TYPE_SIGNATURE;
     }
@@ -903,7 +911,7 @@
     try {
       return parser.parseMethodTypeSignature(signature);
     } catch (GenericSignatureFormatError e) {
-      reporter.warning(
+      diagnosticsHandler.warning(
           GenericSignatureDiagnostic.invalidMethodSignature(signature, methodName, origin, e));
       return MethodTypeSignature.NO_METHOD_TYPE_SIGNATURE;
     }
@@ -1071,7 +1079,7 @@
         builder.add(parseFieldTypeSignature());
       }
       if (builder == null) {
-        return new FormalTypeParameter(typeParameterIdentifier, classBound, null);
+        return new FormalTypeParameter(typeParameterIdentifier, classBound, EMPTY_TYPE_ARGUMENTS);
       }
       return new FormalTypeParameter(typeParameterIdentifier, classBound, builder.build());
     }
diff --git a/src/main/java/com/android/tools/r8/graph/GenericSignatureEnqueuerAnalysis.java b/src/main/java/com/android/tools/r8/graph/GenericSignatureEnqueuerAnalysis.java
index ea66ba7..e889a40 100644
--- a/src/main/java/com/android/tools/r8/graph/GenericSignatureEnqueuerAnalysis.java
+++ b/src/main/java/com/android/tools/r8/graph/GenericSignatureEnqueuerAnalysis.java
@@ -4,17 +4,9 @@
 
 package com.android.tools.r8.graph;
 
-import com.android.tools.r8.graph.GenericSignature.ClassSignature;
-import com.android.tools.r8.graph.GenericSignature.ClassTypeSignature;
-import com.android.tools.r8.graph.GenericSignature.FieldTypeSignature;
-import com.android.tools.r8.graph.GenericSignature.FormalTypeParameter;
-import com.android.tools.r8.graph.GenericSignature.MethodTypeSignature;
-import com.android.tools.r8.graph.GenericSignature.ReturnType;
-import com.android.tools.r8.graph.GenericSignature.TypeSignature;
 import com.android.tools.r8.graph.analysis.EnqueuerAnalysis;
 import com.android.tools.r8.shaking.Enqueuer.EnqueuerDefinitionSupplier;
 import com.android.tools.r8.shaking.EnqueuerWorklist;
-import java.util.List;
 
 public class GenericSignatureEnqueuerAnalysis extends EnqueuerAnalysis {
 
@@ -26,164 +18,20 @@
 
   @Override
   public void processNewlyLiveClass(DexProgramClass clazz, EnqueuerWorklist worklist) {
-    new GenericSignatureTypeVisitor(clazz, enqueuerDefinitionSupplier)
+    new GenericSignatureTypeVisitor(clazz, enqueuerDefinitionSupplier::definitionFor)
         .visitClassSignature(clazz.getClassSignature());
   }
 
   @Override
   public void processNewlyLiveField(ProgramField field, ProgramDefinition context) {
-    new GenericSignatureTypeVisitor(context, enqueuerDefinitionSupplier)
+    new GenericSignatureTypeVisitor(context, enqueuerDefinitionSupplier::definitionFor)
         .visitFieldTypeSignature(field.getDefinition().getGenericSignature());
   }
 
   @Override
   public void processNewlyLiveMethod(ProgramMethod method, ProgramDefinition context) {
-    new GenericSignatureTypeVisitor(context, enqueuerDefinitionSupplier)
+    new GenericSignatureTypeVisitor(context, enqueuerDefinitionSupplier::definitionFor)
         .visitMethodSignature(method.getDefinition().getGenericSignature());
   }
 
-  private static class GenericSignatureTypeVisitor implements GenericSignatureVisitor {
-
-    private final ProgramDefinition context;
-    private final EnqueuerDefinitionSupplier enqueuerDefinitionSupplier;
-
-    private GenericSignatureTypeVisitor(
-        ProgramDefinition context, EnqueuerDefinitionSupplier enqueuerDefinitionSupplier) {
-      this.context = context;
-      this.enqueuerDefinitionSupplier = enqueuerDefinitionSupplier;
-    }
-
-    @Override
-    public ClassSignature visitClassSignature(ClassSignature classSignature) {
-      if (classSignature.hasNoSignature()) {
-        return classSignature;
-      }
-      return classSignature.visit(this);
-    }
-
-    @Override
-    public MethodTypeSignature visitMethodSignature(MethodTypeSignature methodSignature) {
-      if (methodSignature.hasNoSignature()) {
-        return methodSignature;
-      }
-      return methodSignature.visit(this);
-    }
-
-    @Override
-    public FieldTypeSignature visitFieldTypeSignature(FieldTypeSignature fieldSignature) {
-      if (fieldSignature.hasNoSignature()) {
-        return fieldSignature;
-      }
-      if (fieldSignature.isStar()) {
-        return fieldSignature;
-      }
-      if (fieldSignature.isTypeVariableSignature()) {
-        return fieldSignature;
-      }
-      if (fieldSignature.isArrayTypeSignature()) {
-        fieldSignature.asArrayTypeSignature().visit(this);
-        return fieldSignature;
-      }
-      assert fieldSignature.isClassTypeSignature();
-      return fieldSignature.asClassTypeSignature().visit(this);
-    }
-
-    @Override
-    public List<FormalTypeParameter> visitFormalTypeParameters(
-        List<FormalTypeParameter> formalTypeParameters) {
-      formalTypeParameters.forEach(formalTypeParameter -> formalTypeParameter.visit(this));
-      return formalTypeParameters;
-    }
-
-    @Override
-    public FieldTypeSignature visitClassBound(FieldTypeSignature fieldSignature) {
-      return visitFieldTypeSignature(fieldSignature);
-    }
-
-    @Override
-    public List<FieldTypeSignature> visitInterfaceBounds(List<FieldTypeSignature> fieldSignatures) {
-      if (fieldSignatures == null) {
-        return null;
-      }
-      fieldSignatures.forEach(this::visitInterfaceBound);
-      return fieldSignatures;
-    }
-
-    @Override
-    public FieldTypeSignature visitInterfaceBound(FieldTypeSignature fieldSignature) {
-      return visitFieldTypeSignature(fieldSignature);
-    }
-
-    @Override
-    public ClassTypeSignature visitSuperClass(ClassTypeSignature classTypeSignature) {
-      return classTypeSignature.visit(this);
-    }
-
-    @Override
-    public List<ClassTypeSignature> visitSuperInterfaces(
-        List<ClassTypeSignature> interfaceSignatures) {
-      if (interfaceSignatures == null) {
-        return null;
-      }
-      interfaceSignatures.forEach(this::visitSuperInterface);
-      return interfaceSignatures;
-    }
-
-    @Override
-    public ClassTypeSignature visitSuperInterface(ClassTypeSignature classTypeSignature) {
-      return classTypeSignature.visit(this);
-    }
-
-    @Override
-    public TypeSignature visitTypeSignature(TypeSignature typeSignature) {
-      if (typeSignature.isBaseTypeSignature()) {
-        return typeSignature;
-      }
-      assert typeSignature.isFieldTypeSignature();
-      return visitFieldTypeSignature(typeSignature.asFieldTypeSignature());
-    }
-
-    @Override
-    public ClassTypeSignature visitSimpleClass(ClassTypeSignature classTypeSignature) {
-      return classTypeSignature.visit(this);
-    }
-
-    @Override
-    public ReturnType visitReturnType(ReturnType returnType) {
-      if (returnType.isVoidDescriptor()) {
-        return returnType;
-      }
-      visitTypeSignature(returnType.typeSignature);
-      return returnType;
-    }
-
-    @Override
-    public List<TypeSignature> visitMethodTypeSignatures(List<TypeSignature> typeSignatures) {
-      typeSignatures.forEach(this::visitTypeSignature);
-      return typeSignatures;
-    }
-
-    @Override
-    public List<TypeSignature> visitThrowsSignatures(List<TypeSignature> typeSignatures) {
-      typeSignatures.forEach(this::visitTypeSignature);
-      return typeSignatures;
-    }
-
-    @Override
-    public List<FieldTypeSignature> visitTypeArguments(List<FieldTypeSignature> typeArguments) {
-      typeArguments.forEach(this::visitFieldTypeSignature);
-      return typeArguments;
-    }
-
-    @Override
-    public FormalTypeParameter visitFormalTypeParameter(FormalTypeParameter formalTypeParameter) {
-      return formalTypeParameter.visit(this);
-    }
-
-    @Override
-    public DexType visitType(DexType type) {
-      enqueuerDefinitionSupplier.definitionFor(type, context);
-      return type;
-    }
-  }
 }
diff --git a/src/main/java/com/android/tools/r8/graph/GenericSignaturePartialTypeArgumentApplier.java b/src/main/java/com/android/tools/r8/graph/GenericSignaturePartialTypeArgumentApplier.java
new file mode 100644
index 0000000..2c17201
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/graph/GenericSignaturePartialTypeArgumentApplier.java
@@ -0,0 +1,206 @@
+// Copyright (c) 2021, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.graph;
+
+import com.android.tools.r8.graph.GenericSignature.ClassSignature;
+import com.android.tools.r8.graph.GenericSignature.ClassTypeSignature;
+import com.android.tools.r8.graph.GenericSignature.FieldTypeSignature;
+import com.android.tools.r8.graph.GenericSignature.FormalTypeParameter;
+import com.android.tools.r8.graph.GenericSignature.MethodTypeSignature;
+import com.android.tools.r8.graph.GenericSignature.ReturnType;
+import com.android.tools.r8.graph.GenericSignature.StarFieldTypeSignature;
+import com.android.tools.r8.graph.GenericSignature.TypeSignature;
+import com.android.tools.r8.graph.GenericSignature.WildcardIndicator;
+import com.android.tools.r8.utils.ListUtils;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+public class GenericSignaturePartialTypeArgumentApplier implements GenericSignatureVisitor {
+
+  private final Map<String, DexType> substitutions;
+  private final DexType objectType;
+  private final Set<String> introducedClassTypeVariables = new HashSet<>();
+  private final Set<String> introducedMethodTypeVariables = new HashSet<>();
+
+  // Wildcards can only be called be used in certain positions:
+  // https://docs.oracle.com/javase/tutorial/java/generics/wildcards.html
+  private boolean canUseWildcardInArguments = true;
+
+  private GenericSignaturePartialTypeArgumentApplier(
+      Map<String, DexType> substitutions, DexType objectType) {
+    this.substitutions = substitutions;
+    this.objectType = objectType;
+  }
+
+  public static GenericSignaturePartialTypeArgumentApplier build(
+      AppView<?> appView, ClassSignature classSignature, Map<String, DexType> substitutions) {
+    GenericSignaturePartialTypeArgumentApplier applier =
+        new GenericSignaturePartialTypeArgumentApplier(
+            substitutions, appView.dexItemFactory().objectType);
+    classSignature.formalTypeParameters.forEach(
+        parameter -> applier.introducedClassTypeVariables.add(parameter.name));
+    return applier;
+  }
+
+  @Override
+  public ClassSignature visitClassSignature(ClassSignature classSignature) {
+    return classSignature.visit(this);
+  }
+
+  @Override
+  public MethodTypeSignature visitMethodSignature(MethodTypeSignature methodSignature) {
+    assert introducedMethodTypeVariables.isEmpty();
+    methodSignature.formalTypeParameters.forEach(
+        parameter -> introducedMethodTypeVariables.add(parameter.name));
+    MethodTypeSignature rewritten = methodSignature.visit(this);
+    introducedMethodTypeVariables.clear();
+    return rewritten;
+  }
+
+  @Override
+  public DexType visitType(DexType type) {
+    return type;
+  }
+
+  @Override
+  public TypeSignature visitTypeSignature(TypeSignature typeSignature) {
+    if (typeSignature.isBaseTypeSignature()) {
+      return typeSignature;
+    }
+    return visitFieldTypeSignature(typeSignature.asFieldTypeSignature());
+  }
+
+  @Override
+  public FormalTypeParameter visitFormalTypeParameter(FormalTypeParameter formalTypeParameter) {
+    FormalTypeParameter rewritten = formalTypeParameter.visit(this);
+    // Guard against no information being present in bounds.
+    assert (rewritten.getClassBound() != null && rewritten.getClassBound().hasSignature())
+        || !rewritten.getInterfaceBounds().isEmpty();
+    return rewritten;
+  }
+
+  @Override
+  public List<FieldTypeSignature> visitInterfaceBounds(List<FieldTypeSignature> fieldSignatures) {
+    if (fieldSignatures == null || fieldSignatures.isEmpty()) {
+      return fieldSignatures;
+    }
+    return ListUtils.mapOrElse(fieldSignatures, this::visitFieldTypeSignature);
+  }
+
+  @Override
+  public List<ClassTypeSignature> visitSuperInterfaces(
+      List<ClassTypeSignature> interfaceSignatures) {
+    if (interfaceSignatures.isEmpty()) {
+      return interfaceSignatures;
+    }
+    canUseWildcardInArguments = false;
+    List<ClassTypeSignature> map =
+        ListUtils.mapOrElse(interfaceSignatures, this::visitSuperInterface);
+    canUseWildcardInArguments = true;
+    return map;
+  }
+
+  @Override
+  public List<FieldTypeSignature> visitTypeArguments(List<FieldTypeSignature> typeArguments) {
+    if (typeArguments.isEmpty()) {
+      return typeArguments;
+    }
+    return ListUtils.mapOrElse(typeArguments, this::visitFieldTypeSignature);
+  }
+
+  @Override
+  public ClassTypeSignature visitSuperInterface(ClassTypeSignature classTypeSignature) {
+    return classTypeSignature.visit(this);
+  }
+
+  @Override
+  public FieldTypeSignature visitClassBound(FieldTypeSignature fieldSignature) {
+    return visitFieldTypeSignature(fieldSignature);
+  }
+
+  @Override
+  public FieldTypeSignature visitInterfaceBound(FieldTypeSignature fieldSignature) {
+    return visitFieldTypeSignature(fieldSignature);
+  }
+
+  @Override
+  public ClassTypeSignature visitSimpleClass(ClassTypeSignature classTypeSignature) {
+    return classTypeSignature.visit(this);
+  }
+
+  @Override
+  public List<TypeSignature> visitThrowsSignatures(List<TypeSignature> typeSignatures) {
+    if (typeSignatures.isEmpty()) {
+      return typeSignatures;
+    }
+    return ListUtils.mapOrElse(typeSignatures, this::visitTypeSignature);
+  }
+
+  @Override
+  public ReturnType visitReturnType(ReturnType returnType) {
+    if (returnType.isVoidDescriptor()) {
+      return returnType;
+    }
+    TypeSignature originalSignature = returnType.typeSignature;
+    TypeSignature rewrittenSignature = visitTypeSignature(originalSignature);
+    if (originalSignature == rewrittenSignature) {
+      return returnType;
+    }
+    return new ReturnType(rewrittenSignature);
+  }
+
+  @Override
+  public List<FormalTypeParameter> visitFormalTypeParameters(
+      List<FormalTypeParameter> formalTypeParameters) {
+    if (formalTypeParameters.isEmpty()) {
+      return formalTypeParameters;
+    }
+    return ListUtils.mapOrElse(formalTypeParameters, this::visitFormalTypeParameter);
+  }
+
+  @Override
+  public List<TypeSignature> visitMethodTypeSignatures(List<TypeSignature> typeSignatures) {
+    if (typeSignatures.isEmpty()) {
+      return typeSignatures;
+    }
+    return ListUtils.mapOrElse(typeSignatures, this::visitTypeSignature);
+  }
+
+  @Override
+  public ClassTypeSignature visitSuperClass(ClassTypeSignature classTypeSignature) {
+    canUseWildcardInArguments = false;
+    ClassTypeSignature visit = classTypeSignature.visit(this);
+    canUseWildcardInArguments = true;
+    return visit;
+  }
+
+  @Override
+  public FieldTypeSignature visitFieldTypeSignature(FieldTypeSignature fieldSignature) {
+    if (fieldSignature.isStar()) {
+      return fieldSignature;
+    } else if (fieldSignature.isClassTypeSignature()) {
+      return fieldSignature.asClassTypeSignature().visit(this);
+    } else if (fieldSignature.isArrayTypeSignature()) {
+      return fieldSignature.asArrayTypeSignature().visit(this);
+    } else {
+      assert fieldSignature.isTypeVariableSignature();
+      String typeVariableName = fieldSignature.asTypeVariableSignature().typeVariable();
+      if (substitutions.containsKey(typeVariableName)
+          && !introducedClassTypeVariables.contains(typeVariableName)
+          && !introducedMethodTypeVariables.contains(typeVariableName)) {
+        DexType substitution = substitutions.get(typeVariableName);
+        if (substitution == null) {
+          substitution = objectType;
+        }
+        return substitution == objectType && canUseWildcardInArguments
+            ? StarFieldTypeSignature.getStarFieldTypeSignature()
+            : new ClassTypeSignature(substitution).asArgument(WildcardIndicator.NONE);
+      }
+      return fieldSignature;
+    }
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/graph/GenericSignatureTypeRewriter.java b/src/main/java/com/android/tools/r8/graph/GenericSignatureTypeRewriter.java
index 6d31cca..6df0d8b 100644
--- a/src/main/java/com/android/tools/r8/graph/GenericSignatureTypeRewriter.java
+++ b/src/main/java/com/android/tools/r8/graph/GenericSignatureTypeRewriter.java
@@ -4,10 +4,7 @@
 
 package com.android.tools.r8.graph;
 
-import static com.android.tools.r8.graph.GenericSignature.EMPTY_SUPER_INTERFACES;
 import static com.android.tools.r8.graph.GenericSignature.EMPTY_TYPE_ARGUMENTS;
-import static com.android.tools.r8.graph.GenericSignature.EMPTY_TYPE_PARAMS;
-import static com.android.tools.r8.graph.GenericSignature.EMPTY_TYPE_SIGNATURES;
 import static com.google.common.base.Predicates.alwaysFalse;
 
 import com.android.tools.r8.graph.GenericSignature.ClassSignature;
@@ -28,9 +25,9 @@
   private final DexItemFactory factory;
   private final Predicate<DexType> wasPruned;
   private final Function<DexType, DexType> lookupType;
-  private final DexProgramClass context;
+  private final DexType context;
 
-  private final FieldTypeSignature objectTypeSignature;
+  private final ClassTypeSignature objectTypeSignature;
 
   public GenericSignatureTypeRewriter(AppView<?> appView, DexProgramClass context) {
     this(
@@ -39,14 +36,14 @@
             ? appView.appInfo().withLiveness()::wasPruned
             : alwaysFalse(),
         appView.graphLens()::lookupType,
-        context);
+        context.getType());
   }
 
   public GenericSignatureTypeRewriter(
       DexItemFactory factory,
       Predicate<DexType> wasPruned,
       Function<DexType, DexType> lookupType,
-      DexProgramClass context) {
+      DexType context) {
     this.factory = factory;
     this.wasPruned = wasPruned;
     this.lookupType = lookupType;
@@ -120,20 +117,28 @@
     public List<FormalTypeParameter> visitFormalTypeParameters(
         List<FormalTypeParameter> formalTypeParameters) {
       if (formalTypeParameters.isEmpty()) {
-        return EMPTY_TYPE_PARAMS;
+        return formalTypeParameters;
       }
       return ListUtils.mapOrElse(formalTypeParameters, this::visitFormalTypeParameter);
     }
 
     @Override
     public FormalTypeParameter visitFormalTypeParameter(FormalTypeParameter formalTypeParameter) {
-      return formalTypeParameter.visit(this);
+      FormalTypeParameter rewritten = formalTypeParameter.visit(this);
+      // Guard against no information being present in bounds.
+      boolean isEmptyClassBound =
+          rewritten.getClassBound() == null || rewritten.getClassBound().hasNoSignature();
+      if (isEmptyClassBound && rewritten.getInterfaceBounds().isEmpty()) {
+        return new FormalTypeParameter(
+            formalTypeParameter.getName(), objectTypeSignature, rewritten.getInterfaceBounds());
+      }
+      return rewritten;
     }
 
     @Override
     public ClassTypeSignature visitSuperClass(ClassTypeSignature classTypeSignature) {
       ClassTypeSignature rewritten = classTypeSignature.visit(this);
-      return rewritten == null || rewritten.type() == context.type
+      return rewritten == null || rewritten.type() == context
           ? new ClassTypeSignature(factory.objectType, EMPTY_TYPE_ARGUMENTS)
           : rewritten;
     }
@@ -142,7 +147,7 @@
     public List<ClassTypeSignature> visitSuperInterfaces(
         List<ClassTypeSignature> interfaceSignatures) {
       if (interfaceSignatures.isEmpty()) {
-        return EMPTY_SUPER_INTERFACES;
+        return interfaceSignatures;
       }
       return ListUtils.mapOrElse(interfaceSignatures, this::visitSuperInterface);
     }
@@ -150,13 +155,13 @@
     @Override
     public ClassTypeSignature visitSuperInterface(ClassTypeSignature classTypeSignature) {
       ClassTypeSignature rewritten = classTypeSignature.visit(this);
-      return rewritten == null || rewritten.type() == context.type ? null : rewritten;
+      return rewritten == null || rewritten.type() == context ? null : rewritten;
     }
 
     @Override
     public List<TypeSignature> visitMethodTypeSignatures(List<TypeSignature> typeSignatures) {
       if (typeSignatures.isEmpty()) {
-        return EMPTY_TYPE_SIGNATURES;
+        return typeSignatures;
       }
       return ListUtils.mapOrElse(
           typeSignatures,
@@ -186,7 +191,7 @@
     @Override
     public List<TypeSignature> visitThrowsSignatures(List<TypeSignature> typeSignatures) {
       if (typeSignatures.isEmpty()) {
-        return EMPTY_TYPE_SIGNATURES;
+        return typeSignatures;
       }
       // If a throwing type is no longer found we remove it from the signature.
       return ListUtils.mapOrElse(typeSignatures, this::visitTypeSignature);
@@ -199,11 +204,8 @@
 
     @Override
     public List<FieldTypeSignature> visitInterfaceBounds(List<FieldTypeSignature> fieldSignatures) {
-      if (fieldSignatures == null) {
-        return null;
-      }
-      if (fieldSignatures.isEmpty()) {
-        return EMPTY_TYPE_ARGUMENTS;
+      if (fieldSignatures == null || fieldSignatures.isEmpty()) {
+        return fieldSignatures;
       }
       return ListUtils.mapOrElse(fieldSignatures, this::visitFieldTypeSignature);
     }
@@ -221,7 +223,7 @@
     @Override
     public List<FieldTypeSignature> visitTypeArguments(List<FieldTypeSignature> typeArguments) {
       if (typeArguments.isEmpty()) {
-        return EMPTY_TYPE_ARGUMENTS;
+        return typeArguments;
       }
       return ListUtils.mapOrElse(
           typeArguments,
diff --git a/src/main/java/com/android/tools/r8/graph/GenericSignatureTypeVariableRemover.java b/src/main/java/com/android/tools/r8/graph/GenericSignatureTypeVariableRemover.java
new file mode 100644
index 0000000..f33c9c3
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/graph/GenericSignatureTypeVariableRemover.java
@@ -0,0 +1,125 @@
+// Copyright (c) 2021, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.graph;
+
+import com.android.tools.r8.graph.GenericSignature.FieldTypeSignature;
+import com.android.tools.r8.graph.GenericSignature.FormalTypeParameter;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.function.Predicate;
+
+public class GenericSignatureTypeVariableRemover {
+
+  private final AppView<?> appView;
+  private final Predicate<InnerClassAttribute> innerClassPruned;
+  private final Predicate<EnclosingMethodAttribute> enclosingClassOrMethodPruned;
+
+  public GenericSignatureTypeVariableRemover(
+      AppView<?> appView,
+      Predicate<InnerClassAttribute> innerClassPruned,
+      Predicate<EnclosingMethodAttribute> enclosingClassOrMethodPruned) {
+    this.appView = appView;
+    this.innerClassPruned = innerClassPruned;
+    this.enclosingClassOrMethodPruned = enclosingClassOrMethodPruned;
+  }
+
+  public void removeDeadGenericSignatureTypeVariables(DexProgramClass clazz) {
+    if (clazz.getClassSignature().hasNoSignature() || clazz.getClassSignature().isInvalid()) {
+      return;
+    }
+    Map<String, DexType> substitutions = new HashMap<>();
+    getPrunedTypeParameters(clazz, substitutions, false);
+    if (substitutions.isEmpty()) {
+      return;
+    }
+    GenericSignaturePartialTypeArgumentApplier genericSignatureTypeArgumentApplier =
+        GenericSignaturePartialTypeArgumentApplier.build(
+            appView, clazz.getClassSignature(), substitutions);
+    clazz.setClassSignature(
+        genericSignatureTypeArgumentApplier.visitClassSignature(clazz.getClassSignature()));
+    clazz
+        .methods()
+        .forEach(
+            method -> {
+              if (method.getGenericSignature().hasSignature()
+                  && method.getGenericSignature().isValid()
+                  && method.isVirtualMethod()) {
+                method.setGenericSignature(
+                    genericSignatureTypeArgumentApplier.visitMethodSignature(
+                        method.getGenericSignature()));
+              }
+            });
+    clazz
+        .instanceFields()
+        .forEach(
+            field -> {
+              if (field.getGenericSignature().hasSignature()
+                  && field.getGenericSignature().isValid()) {
+                field.setGenericSignature(
+                    genericSignatureTypeArgumentApplier.visitFieldTypeSignature(
+                        field.getGenericSignature()));
+              }
+            });
+  }
+
+  private void getPrunedTypeParameters(
+      DexClass clazz, Map<String, DexType> substitutions, boolean seenPruned) {
+    InnerClassAttribute innerClassAttribute = clazz.getInnerClassAttributeForThisClass();
+    if (innerClassAttribute != null
+        && innerClassAttribute.getOuter() != null
+        && (seenPruned || innerClassPruned.test(innerClassAttribute))) {
+      DexClass outerClass = appView.definitionFor(innerClassAttribute.getOuter());
+      if (outerClass != null && outerClass.getClassSignature().isValid()) {
+        updateMap(outerClass.getClassSignature().getFormalTypeParameters(), substitutions);
+        getPrunedTypeParameters(outerClass, substitutions, true);
+      }
+    }
+    if (clazz.getEnclosingMethodAttribute() != null
+        && (seenPruned || enclosingClassOrMethodPruned.test(clazz.getEnclosingMethodAttribute()))) {
+      DexClass outerClass =
+          appView.definitionFor(clazz.getEnclosingMethodAttribute().getEnclosingType());
+      if (outerClass == null) {
+        return;
+      }
+      if (clazz.getEnclosingMethodAttribute().getEnclosingMethod() != null) {
+        DexEncodedMethod enclosingMethod =
+            outerClass.lookupMethod(clazz.getEnclosingMethodAttribute().getEnclosingMethod());
+        if (enclosingMethod != null) {
+          updateMap(enclosingMethod.getGenericSignature().getFormalTypeParameters(), substitutions);
+          if (enclosingMethod.isStatic()) {
+            return;
+          }
+        }
+      }
+      if (outerClass.getClassSignature().isValid()) {
+        updateMap(outerClass.getClassSignature().getFormalTypeParameters(), substitutions);
+      }
+      getPrunedTypeParameters(outerClass, substitutions, true);
+    }
+  }
+
+  private void updateMap(
+      List<FormalTypeParameter> formalTypeParameters, Map<String, DexType> substitutions) {
+    // We are updating the map going from inner most to outer, thus the any overriding formal type
+    // parameters will be in the substitution map already.
+    formalTypeParameters.forEach(
+        parameter -> {
+          if (substitutions.containsKey(parameter.getName())) {
+            return;
+          }
+          // The null substitution will use the wildcard as argument, which is smaller than using
+          // Ljava/lang/Object;
+          DexType substitution = null;
+          FieldTypeSignature classBound = parameter.getClassBound();
+          if (classBound != null
+              && classBound.hasSignature()
+              && classBound.isClassTypeSignature()) {
+            substitution = classBound.asClassTypeSignature().type();
+          }
+          substitutions.put(parameter.getName(), substitution);
+        });
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/graph/GenericSignatureTypeVisitor.java b/src/main/java/com/android/tools/r8/graph/GenericSignatureTypeVisitor.java
new file mode 100644
index 0000000..dff8d5d
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/graph/GenericSignatureTypeVisitor.java
@@ -0,0 +1,160 @@
+// Copyright (c) 2021, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.graph;
+
+import com.android.tools.r8.graph.GenericSignature.ClassSignature;
+import com.android.tools.r8.graph.GenericSignature.ClassTypeSignature;
+import com.android.tools.r8.graph.GenericSignature.FieldTypeSignature;
+import com.android.tools.r8.graph.GenericSignature.FormalTypeParameter;
+import com.android.tools.r8.graph.GenericSignature.MethodTypeSignature;
+import com.android.tools.r8.graph.GenericSignature.ReturnType;
+import com.android.tools.r8.graph.GenericSignature.TypeSignature;
+import java.util.List;
+import java.util.function.BiConsumer;
+
+class GenericSignatureTypeVisitor implements GenericSignatureVisitor {
+
+  private final ProgramDefinition context;
+  private final BiConsumer<DexType, ProgramDefinition> visitedTypeConsumer;
+
+  GenericSignatureTypeVisitor(
+      ProgramDefinition context, BiConsumer<DexType, ProgramDefinition> visitedTypeConsumer) {
+    this.context = context;
+    this.visitedTypeConsumer = visitedTypeConsumer;
+  }
+
+  @Override
+  public ClassSignature visitClassSignature(ClassSignature classSignature) {
+    if (classSignature.hasNoSignature()) {
+      return classSignature;
+    }
+    return classSignature.visit(this);
+  }
+
+  @Override
+  public MethodTypeSignature visitMethodSignature(MethodTypeSignature methodSignature) {
+    if (methodSignature.hasNoSignature()) {
+      return methodSignature;
+    }
+    return methodSignature.visit(this);
+  }
+
+  @Override
+  public FieldTypeSignature visitFieldTypeSignature(FieldTypeSignature fieldSignature) {
+    if (fieldSignature.hasNoSignature()) {
+      return fieldSignature;
+    }
+    if (fieldSignature.isStar()) {
+      return fieldSignature;
+    }
+    if (fieldSignature.isTypeVariableSignature()) {
+      return fieldSignature;
+    }
+    if (fieldSignature.isArrayTypeSignature()) {
+      fieldSignature.asArrayTypeSignature().visit(this);
+      return fieldSignature;
+    }
+    assert fieldSignature.isClassTypeSignature();
+    return fieldSignature.asClassTypeSignature().visit(this);
+  }
+
+  @Override
+  public List<FormalTypeParameter> visitFormalTypeParameters(
+      List<FormalTypeParameter> formalTypeParameters) {
+    formalTypeParameters.forEach(this::visitFormalTypeParameter);
+    return formalTypeParameters;
+  }
+
+  @Override
+  public FieldTypeSignature visitClassBound(FieldTypeSignature fieldSignature) {
+    return visitFieldTypeSignature(fieldSignature);
+  }
+
+  @Override
+  public List<FieldTypeSignature> visitInterfaceBounds(List<FieldTypeSignature> fieldSignatures) {
+    if (fieldSignatures == null) {
+      return null;
+    }
+    fieldSignatures.forEach(this::visitInterfaceBound);
+    return fieldSignatures;
+  }
+
+  @Override
+  public FieldTypeSignature visitInterfaceBound(FieldTypeSignature fieldSignature) {
+    return visitFieldTypeSignature(fieldSignature);
+  }
+
+  @Override
+  public ClassTypeSignature visitSuperClass(ClassTypeSignature classTypeSignature) {
+    return classTypeSignature.visit(this);
+  }
+
+  @Override
+  public List<ClassTypeSignature> visitSuperInterfaces(
+      List<ClassTypeSignature> interfaceSignatures) {
+    if (interfaceSignatures == null) {
+      return null;
+    }
+    interfaceSignatures.forEach(this::visitSuperInterface);
+    return interfaceSignatures;
+  }
+
+  @Override
+  public ClassTypeSignature visitSuperInterface(ClassTypeSignature classTypeSignature) {
+    return classTypeSignature.visit(this);
+  }
+
+  @Override
+  public TypeSignature visitTypeSignature(TypeSignature typeSignature) {
+    if (typeSignature.isBaseTypeSignature()) {
+      return typeSignature;
+    }
+    assert typeSignature.isFieldTypeSignature();
+    return visitFieldTypeSignature(typeSignature.asFieldTypeSignature());
+  }
+
+  @Override
+  public ClassTypeSignature visitSimpleClass(ClassTypeSignature classTypeSignature) {
+    return classTypeSignature.visit(this);
+  }
+
+  @Override
+  public ReturnType visitReturnType(ReturnType returnType) {
+    if (returnType.isVoidDescriptor()) {
+      return returnType;
+    }
+    visitTypeSignature(returnType.typeSignature);
+    return returnType;
+  }
+
+  @Override
+  public List<TypeSignature> visitMethodTypeSignatures(List<TypeSignature> typeSignatures) {
+    typeSignatures.forEach(this::visitTypeSignature);
+    return typeSignatures;
+  }
+
+  @Override
+  public List<TypeSignature> visitThrowsSignatures(List<TypeSignature> typeSignatures) {
+    typeSignatures.forEach(this::visitTypeSignature);
+    return typeSignatures;
+  }
+
+  @Override
+  public List<FieldTypeSignature> visitTypeArguments(List<FieldTypeSignature> typeArguments) {
+    typeArguments.forEach(this::visitFieldTypeSignature);
+    return typeArguments;
+  }
+
+  @Override
+  public FormalTypeParameter visitFormalTypeParameter(FormalTypeParameter formalTypeParameter) {
+    return formalTypeParameter.visit(this);
+  }
+
+  @Override
+  public DexType visitType(DexType type) {
+    visitedTypeConsumer.accept(type, context);
+    return type;
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/ClassInstanceFieldsMerger.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/ClassInstanceFieldsMerger.java
index 23cb57c..a429d5c 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/ClassInstanceFieldsMerger.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/ClassInstanceFieldsMerger.java
@@ -4,13 +4,13 @@
 
 package com.android.tools.r8.horizontalclassmerging;
 
+import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexEncodedField;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.horizontalclassmerging.HorizontalClassMergerGraphLens.Builder;
 import com.android.tools.r8.horizontalclassmerging.policies.SameInstanceFields.InstanceFieldInfo;
-import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.utils.IterableUtils;
 import com.google.common.collect.Iterables;
 import java.util.ArrayList;
@@ -22,7 +22,7 @@
 
 public class ClassInstanceFieldsMerger {
 
-  private final AppView<AppInfoWithLiveness> appView;
+  private final AppView<? extends AppInfoWithClassHierarchy> appView;
   private final Builder lensBuilder;
 
   private DexEncodedField classIdField;
@@ -31,7 +31,7 @@
   private final Map<DexEncodedField, List<DexEncodedField>> fieldMappings = new LinkedHashMap<>();
 
   public ClassInstanceFieldsMerger(
-      AppView<AppInfoWithLiveness> appView,
+      AppView<? extends AppInfoWithClassHierarchy> appView,
       HorizontalClassMergerGraphLens.Builder lensBuilder,
       MergeGroup group) {
     this.appView = appView;
@@ -128,8 +128,9 @@
 
   public DexEncodedField[] merge() {
     List<DexEncodedField> newFields = new ArrayList<>();
-    assert classIdField != null;
-    newFields.add(classIdField);
+    if (classIdField != null) {
+      newFields.add(classIdField);
+    }
     fieldMappings.forEach(
         (targetField, oldFields) ->
             newFields.add(mergeSourceFieldsToTargetField(targetField, oldFields)));
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/ClassMerger.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/ClassMerger.java
index a47acd6..23b27f3 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/ClassMerger.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/ClassMerger.java
@@ -8,6 +8,7 @@
 
 import com.android.tools.r8.cf.CfVersion;
 import com.android.tools.r8.dex.Constants;
+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.DexAnnotationSet;
@@ -17,6 +18,7 @@
 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.DexMethodSignature;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexProto;
 import com.android.tools.r8.graph.DexType;
@@ -30,10 +32,8 @@
 import com.android.tools.r8.ir.analysis.value.NumberFromIntervalValue;
 import com.android.tools.r8.ir.optimize.info.OptimizationFeedback;
 import com.android.tools.r8.ir.optimize.info.OptimizationFeedbackSimple;
-import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.shaking.KeepClassInfo;
 import com.android.tools.r8.utils.IterableUtils;
-import com.android.tools.r8.utils.MethodSignatureEquivalence;
-import com.google.common.base.Equivalence.Wrapper;
 import com.google.common.collect.Iterables;
 import com.google.common.collect.Sets;
 import it.unimi.dsi.fastutil.objects.Reference2IntMap;
@@ -41,6 +41,7 @@
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Comparator;
+import java.util.Iterator;
 import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
@@ -58,7 +59,7 @@
 
   private static final OptimizationFeedback feedback = OptimizationFeedbackSimple.getInstance();
 
-  private final AppView<AppInfoWithLiveness> appView;
+  private final AppView<? extends AppInfoWithClassHierarchy> appView;
   private final MergeGroup group;
   private final DexItemFactory dexItemFactory;
   private final ClassInitializerSynthesizedCode classInitializerSynthesizedCode;
@@ -72,7 +73,7 @@
   private final Collection<ConstructorMerger> constructorMergers;
 
   private ClassMerger(
-      AppView<AppInfoWithLiveness> appView,
+      AppView<? extends AppInfoWithClassHierarchy> appView,
       HorizontalClassMergerGraphLens.Builder lensBuilder,
       MergeGroup group,
       Collection<VirtualMethodMerger> virtualMethodMergers,
@@ -191,6 +192,8 @@
   }
 
   void appendClassIdField() {
+    assert appView.hasLiveness();
+
     boolean deprecated = false;
     boolean d8R8Synthesized = true;
     DexEncodedField classIdField =
@@ -211,7 +214,7 @@
     // be able to recognize that {0, 1, 2, 3} is useless, we record that the value of the field is
     // known to be in [0; 3] here.
     NumberFromIntervalValue abstractValue = new NumberFromIntervalValue(0, group.size() - 1);
-    feedback.recordFieldHasAbstractValue(classIdField, appView, abstractValue);
+    feedback.recordFieldHasAbstractValue(classIdField, appView.withLiveness(), abstractValue);
 
     classInstanceFieldsMerger.setClassIdField(classIdField);
   }
@@ -261,7 +264,10 @@
 
   public void mergeGroup(SyntheticArgumentClass syntheticArgumentClass) {
     fixAccessFlags();
-    appendClassIdField();
+
+    if (group.hasClassIdField()) {
+      appendClassIdField();
+    }
 
     mergeAnnotations();
     mergeInterfaces();
@@ -275,102 +281,131 @@
   }
 
   public static class Builder {
-    private final AppView<AppInfoWithLiveness> appView;
+    private final AppView<? extends AppInfoWithClassHierarchy> appView;
     private final MergeGroup group;
-    private final ClassInitializerSynthesizedCode.Builder classInitializerSynthesizedCodeBuilder =
-        new ClassInitializerSynthesizedCode.Builder();
-    private final Map<DexProto, ConstructorMerger.Builder> constructorMergerBuilders =
-        new LinkedHashMap<>();
-    private final List<ConstructorMerger.Builder> unmergedConstructorBuilders = new ArrayList<>();
-    private final Map<Wrapper<DexMethod>, VirtualMethodMerger.Builder> virtualMethodMergerBuilders =
-        new LinkedHashMap<>();
 
-    public Builder(AppView<AppInfoWithLiveness> appView, MergeGroup group) {
+    public Builder(AppView<? extends AppInfoWithClassHierarchy> appView, MergeGroup group) {
       this.appView = appView;
       this.group = group;
     }
 
-    private Builder setup() {
-      DexItemFactory dexItemFactory = appView.dexItemFactory();
-      DexProgramClass target =
-          IterableUtils.findOrDefault(group, DexClass::isPublic, group.iterator().next());
-      // TODO(b/165498187): ensure the name for the field is fresh
-      group.setClassIdField(
-          dexItemFactory.createField(
-              target.getType(), dexItemFactory.intType, CLASS_ID_FIELD_NAME));
-      group.setTarget(target);
-      setupForMethodMerging(target);
-      group.forEachSource(this::setupForMethodMerging);
-      return this;
-    }
-
-    private void setupForMethodMerging(DexProgramClass toMerge) {
-      if (toMerge.hasClassInitializer()) {
-        classInitializerSynthesizedCodeBuilder.add(toMerge.getClassInitializer());
+    private void selectTarget() {
+      Iterable<DexProgramClass> candidates = Iterables.filter(group, DexClass::isPublic);
+      if (IterableUtils.isEmpty(candidates)) {
+        candidates = group;
       }
-      toMerge.forEachProgramDirectMethodMatching(
-          DexEncodedMethod::isInstanceInitializer, this::addConstructor);
-      toMerge.forEachProgramVirtualMethod(this::addVirtualMethod);
+      Iterator<DexProgramClass> candidateIterator = candidates.iterator();
+      DexProgramClass target = IterableUtils.first(candidates);
+      while (candidateIterator.hasNext()) {
+        DexProgramClass current = candidateIterator.next();
+        KeepClassInfo keepClassInfo = appView.getKeepInfo().getClassInfo(current);
+        if (keepClassInfo.isMinificationAllowed(appView.options())) {
+          target = current;
+          break;
+        }
+        // Select the target with the shortest name.
+        if (current.getType().getDescriptor().size() < target.getType().getDescriptor().size) {
+          target = current;
+        }
+      }
+      group.setTarget(appView.testing().horizontalClassMergingTarget.apply(candidates, target));
     }
 
-    private void addConstructor(ProgramMethod method) {
-      assert method.getDefinition().isInstanceInitializer();
+    private ClassInitializerSynthesizedCode createClassInitializerMerger() {
+      ClassInitializerSynthesizedCode.Builder builder =
+          new ClassInitializerSynthesizedCode.Builder();
+      group.forEach(
+          clazz -> {
+            if (clazz.hasClassInitializer()) {
+              builder.add(clazz.getClassInitializer());
+            }
+          });
+      return builder.build();
+    }
+
+    private List<ConstructorMerger> createInstanceInitializerMergers() {
+      List<ConstructorMerger> constructorMergers = new ArrayList<>();
       if (appView.options().horizontalClassMergerOptions().isConstructorMergingEnabled()) {
-        constructorMergerBuilders
-            .computeIfAbsent(
-                method.getDefinition().getProto(), ignore -> new ConstructorMerger.Builder(appView))
-            .add(method.getDefinition());
+        Map<DexProto, ConstructorMerger.Builder> buildersByProto = new LinkedHashMap<>();
+        group.forEach(
+            clazz ->
+                clazz.forEachProgramDirectMethodMatching(
+                    DexEncodedMethod::isInstanceInitializer,
+                    method ->
+                        buildersByProto
+                            .computeIfAbsent(
+                                method.getDefinition().getProto(),
+                                ignore -> new ConstructorMerger.Builder(appView))
+                            .add(method.getDefinition())));
+        for (ConstructorMerger.Builder builder : buildersByProto.values()) {
+          constructorMergers.addAll(builder.build(group));
+        }
       } else {
-        unmergedConstructorBuilders.add(
-            new ConstructorMerger.Builder(appView).add(method.getDefinition()));
+        group.forEach(
+            clazz ->
+                clazz.forEachProgramDirectMethodMatching(
+                    DexEncodedMethod::isInstanceInitializer,
+                    method ->
+                        constructorMergers.addAll(
+                            new ConstructorMerger.Builder(appView)
+                                .add(method.getDefinition())
+                                .build(group))));
       }
+
+      // Try and merge the constructors with the most arguments first, to avoid using synthetic
+      // arguments if possible.
+      constructorMergers.sort(Comparator.comparing(ConstructorMerger::getArity).reversed());
+      return constructorMergers;
     }
 
-    private void addVirtualMethod(ProgramMethod method) {
-      assert method.getDefinition().isNonPrivateVirtualMethod();
-      virtualMethodMergerBuilders
-          .computeIfAbsent(
-              MethodSignatureEquivalence.get().wrap(method.getReference()),
-              ignore -> new VirtualMethodMerger.Builder())
-          .add(method);
-    }
-
-    private Collection<ConstructorMerger.Builder> getConstructorMergerBuilders() {
-      return appView.options().horizontalClassMergerOptions().isConstructorMergingEnabled()
-          ? constructorMergerBuilders.values()
-          : unmergedConstructorBuilders;
-    }
-
-    public ClassMerger build(
-        HorizontalClassMergerGraphLens.Builder lensBuilder) {
-      setup();
+    private List<VirtualMethodMerger> createVirtualMethodMergers() {
+      Map<DexMethodSignature, VirtualMethodMerger.Builder> virtualMethodMergerBuilders =
+          new LinkedHashMap<>();
+      group.forEach(
+          clazz ->
+              clazz.forEachProgramVirtualMethod(
+                  virtualMethod ->
+                      virtualMethodMergerBuilders
+                          .computeIfAbsent(
+                              virtualMethod.getReference().getSignature(),
+                              ignore -> new VirtualMethodMerger.Builder())
+                          .add(virtualMethod)));
       List<VirtualMethodMerger> virtualMethodMergers =
           new ArrayList<>(virtualMethodMergerBuilders.size());
       for (VirtualMethodMerger.Builder builder : virtualMethodMergerBuilders.values()) {
         virtualMethodMergers.add(builder.build(appView, group));
       }
-      // Try and merge the functions with the most arguments first, to avoid using synthetic
-      // arguments if possible.
-      virtualMethodMergers.sort(Comparator.comparing(VirtualMethodMerger::getArity).reversed());
+      return virtualMethodMergers;
+    }
 
-      List<ConstructorMerger> constructorMergers =
-          new ArrayList<>(constructorMergerBuilders.size());
-      for (ConstructorMerger.Builder builder : getConstructorMergerBuilders()) {
-        constructorMergers.addAll(builder.build(appView, group));
+    private void createClassIdField() {
+      // TODO(b/165498187): ensure the name for the field is fresh
+      DexItemFactory dexItemFactory = appView.dexItemFactory();
+      group.setClassIdField(
+          dexItemFactory.createField(
+              group.getTarget().getType(), dexItemFactory.intType, CLASS_ID_FIELD_NAME));
+    }
+
+    public ClassMerger build(
+        HorizontalClassMergerGraphLens.Builder lensBuilder) {
+      selectTarget();
+
+      List<VirtualMethodMerger> virtualMethodMergers = createVirtualMethodMergers();
+
+      boolean requiresClassIdField =
+          virtualMethodMergers.stream()
+              .anyMatch(virtualMethodMerger -> !virtualMethodMerger.isNopOrTrivial());
+      if (requiresClassIdField) {
+        createClassIdField();
       }
 
-      // Try and merge the functions with the most arguments first, to avoid using synthetic
-      // arguments if possible.
-      virtualMethodMergers.sort(Comparator.comparing(VirtualMethodMerger::getArity).reversed());
-      constructorMergers.sort(Comparator.comparing(ConstructorMerger::getArity).reversed());
-
       return new ClassMerger(
           appView,
           lensBuilder,
           group,
           virtualMethodMergers,
-          constructorMergers,
-          classInitializerSynthesizedCodeBuilder.build());
+          createInstanceInitializerMergers(),
+          createClassInitializerMerger());
     }
   }
 }
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/ConstructorEntryPoint.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/ConstructorEntryPoint.java
index 732a5d2..d650b17 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/ConstructorEntryPoint.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/ConstructorEntryPoint.java
@@ -49,6 +49,10 @@
     this.classIdField = classIdField;
   }
 
+  private boolean hasClassIdField() {
+    return classIdField != null;
+  }
+
   void addConstructorInvoke(DexMethod typeConstructor) {
     add(
         builder -> {
@@ -66,11 +70,13 @@
 
   /** Assign the given register to the class id field. */
   void addRegisterClassIdAssignment(int idRegister) {
+    assert hasClassIdField();
     add(builder -> builder.addInstancePut(idRegister, getReceiverRegister(), classIdField));
   }
 
   /** Assign the given constant integer value to the class id field. */
   void addConstantRegisterClassIdAssignment(int classId) {
+    assert hasClassIdField();
     int idRegister = nextRegister(ValueType.INT);
     add(builder -> builder.addIntConst(idRegister, classId));
     addRegisterClassIdAssignment(idRegister);
@@ -82,7 +88,9 @@
     // The class id register is always the first synthetic argument.
     int idRegister = getParamRegister(exampleTargetConstructor.getArity());
 
-    addRegisterClassIdAssignment(idRegister);
+    if (hasClassIdField()) {
+      addRegisterClassIdAssignment(idRegister);
+    }
 
     int[] keys = new int[typeConstructorCount - 1];
     int[] offsets = new int[typeConstructorCount - 1];
@@ -115,7 +123,9 @@
 
   protected void prepareSingleConstructorInstructions() {
     Entry<DexMethod> entry = typeConstructors.int2ReferenceEntrySet().first();
-    addConstantRegisterClassIdAssignment(entry.getIntKey());
+    if (hasClassIdField()) {
+      addConstantRegisterClassIdAssignment(entry.getIntKey());
+    }
     addConstructorInvoke(entry.getValue());
     add(IRBuilder::addReturn, endsBlock);
   }
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/ConstructorMerger.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/ConstructorMerger.java
index 13673fa..25066e7 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/ConstructorMerger.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/ConstructorMerger.java
@@ -8,6 +8,7 @@
 
 import com.android.tools.r8.cf.CfVersion;
 import com.android.tools.r8.dex.Constants;
+import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexAnnotationSet;
 import com.android.tools.r8.graph.DexEncodedMethod;
@@ -20,7 +21,6 @@
 import com.android.tools.r8.ir.conversion.ExtraConstantIntParameter;
 import com.android.tools.r8.ir.conversion.ExtraParameter;
 import com.android.tools.r8.ir.conversion.ExtraUnusedNullParameter;
-import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.utils.ListUtils;
 import com.android.tools.r8.utils.structural.Ordered;
 import it.unimi.dsi.fastutil.ints.Int2ReferenceAVLTreeMap;
@@ -69,11 +69,10 @@
   public static class Builder {
     private int estimatedDexCodeSize;
     private final List<List<DexEncodedMethod>> constructorGroups = new ArrayList<>();
-    private AppView<AppInfoWithLiveness> appView;
+    private final AppView<? extends AppInfoWithClassHierarchy> appView;
 
-    public Builder(AppView<AppInfoWithLiveness> appView) {
+    public Builder(AppView<? extends AppInfoWithClassHierarchy> appView) {
       this.appView = appView;
-
       createNewGroup();
     }
 
@@ -96,7 +95,7 @@
       return this;
     }
 
-    public List<ConstructorMerger> build(AppView<?> appView, MergeGroup group) {
+    public List<ConstructorMerger> build(MergeGroup group) {
       assert constructorGroups.stream().noneMatch(List::isEmpty);
       return ListUtils.map(
           constructorGroups, constructors -> new ConstructorMerger(appView, group, constructors));
@@ -185,7 +184,7 @@
         new ConstructorEntryPointSynthesizedCode(
             typeConstructorClassMap,
             newConstructorReference,
-            group.getClassIdField(),
+            group.hasClassIdField() ? group.getClassIdField() : null,
             bridgeConstructorReference);
     DexEncodedMethod newConstructor =
         new DexEncodedMethod(
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontalClassMerger.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontalClassMerger.java
index 7085879..eff9460 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontalClassMerger.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontalClassMerger.java
@@ -4,6 +4,7 @@
 
 package com.android.tools.r8.horizontalclassmerging;
 
+import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.horizontalclassmerging.policies.AllInstantiatedOrUninstantiated;
@@ -15,6 +16,7 @@
 import com.android.tools.r8.horizontalclassmerging.policies.NoAnnotationClasses;
 import com.android.tools.r8.horizontalclassmerging.policies.NoClassAnnotationCollisions;
 import com.android.tools.r8.horizontalclassmerging.policies.NoClassInitializerWithObservableSideEffects;
+import com.android.tools.r8.horizontalclassmerging.policies.NoDeadEnumLiteMaps;
 import com.android.tools.r8.horizontalclassmerging.policies.NoDirectRuntimeTypeChecks;
 import com.android.tools.r8.horizontalclassmerging.policies.NoEnums;
 import com.android.tools.r8.horizontalclassmerging.policies.NoIndirectRuntimeTypeChecks;
@@ -38,7 +40,6 @@
 import com.android.tools.r8.horizontalclassmerging.policies.SyntheticItemsPolicy;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.shaking.FieldAccessInfoCollectionModifier;
-import com.android.tools.r8.shaking.KeepInfoCollection;
 import com.android.tools.r8.shaking.RuntimeTypeCheckInfo;
 import com.android.tools.r8.utils.Timing;
 import com.google.common.collect.ImmutableList;
@@ -49,9 +50,19 @@
 
 public class HorizontalClassMerger {
 
-  private final AppView<AppInfoWithLiveness> appView;
+  // TODO(b/181846319): Add 'FINAL' mode that runs after synthetic finalization.
+  public enum Mode {
+    INITIAL;
 
-  public HorizontalClassMerger(AppView<AppInfoWithLiveness> appView) {
+    public boolean isInitial() {
+      return this == INITIAL;
+    }
+  }
+
+  private final AppView<? extends AppInfoWithClassHierarchy> appView;
+  private final Mode mode = Mode.INITIAL;
+
+  public HorizontalClassMerger(AppView<? extends AppInfoWithClassHierarchy> appView) {
     this.appView = appView;
     assert appView.options().enableInlining;
   }
@@ -76,7 +87,9 @@
     // Merge the classes.
     List<ClassMerger> classMergers = initializeClassMergers(lensBuilder, groups);
     SyntheticArgumentClass syntheticArgumentClass =
-        new SyntheticArgumentClass.Builder(appView).build(groups);
+        mode.isInitial()
+            ? new SyntheticArgumentClass.Builder(appView.withLiveness()).build(groups)
+            : null;
     applyClassMergers(classMergers, syntheticArgumentClass);
 
     // Generate the graph lens.
@@ -87,8 +100,9 @@
         createLens(mergedClasses, lensBuilder, syntheticArgumentClass);
 
     // Prune keep info.
-    KeepInfoCollection keepInfo = appView.appInfo().getKeepInfo();
-    keepInfo.mutate(mutator -> mutator.removeKeepInfoForPrunedItems(mergedClasses.getSources()));
+    appView
+        .getKeepInfo()
+        .mutate(mutator -> mutator.removeKeepInfoForPrunedItems(mergedClasses.getSources()));
 
     return new HorizontalClassMergerResult(createFieldAccessInfoCollectionModifier(groups), lens);
   }
@@ -98,21 +112,26 @@
     FieldAccessInfoCollectionModifier.Builder builder =
         new FieldAccessInfoCollectionModifier.Builder();
     for (MergeGroup group : groups) {
-      DexProgramClass target = group.getTarget();
-      target.forEachProgramInstanceInitializerMatching(
-          definition -> definition.getCode().isHorizontalClassMergingCode(),
-          method -> builder.recordFieldWrittenInContext(group.getClassIdField(), method));
-      target.forEachProgramVirtualMethodMatching(
-          definition -> definition.hasCode() && definition.getCode().isHorizontalClassMergingCode(),
-          method -> builder.recordFieldReadInContext(group.getClassIdField(), method));
+      if (group.hasClassIdField()) {
+        DexProgramClass target = group.getTarget();
+        target.forEachProgramInstanceInitializerMatching(
+            definition -> definition.getCode().isHorizontalClassMergingCode(),
+            method -> builder.recordFieldWrittenInContext(group.getClassIdField(), method));
+        target.forEachProgramVirtualMethodMatching(
+            definition ->
+                definition.hasCode() && definition.getCode().isHorizontalClassMergingCode(),
+            method -> builder.recordFieldReadInContext(group.getClassIdField(), method));
+      }
     }
     return builder.build();
   }
 
   private List<Policy> getPolicies(RuntimeTypeCheckInfo runtimeTypeCheckInfo) {
+    AppView<AppInfoWithLiveness> appViewWithLiveness = appView.withLiveness();
     List<SingleClassPolicy> singleClassPolicies =
         ImmutableList.of(
-            new NotMatchedByNoHorizontalClassMerging(appView),
+            new NotMatchedByNoHorizontalClassMerging(appViewWithLiveness),
+            new NoDeadEnumLiteMaps(appViewWithLiveness),
             new NoAnnotationClasses(),
             new NoEnums(appView),
             new NoInnerClasses(),
@@ -125,7 +144,7 @@
             new NoServiceLoaders(appView),
             new NotVerticallyMergedIntoSubtype(appView),
             new NoDirectRuntimeTypeChecks(runtimeTypeCheckInfo),
-            new DontInlinePolicy(appView));
+            new DontInlinePolicy(appViewWithLiveness));
     List<MultiClassPolicy> multiClassPolicies =
         ImmutableList.of(
             new SameInstanceFields(appView),
@@ -135,13 +154,13 @@
             new NoIndirectRuntimeTypeChecks(appView, runtimeTypeCheckInfo),
             new PreventMethodImplementation(appView),
             new PreventMergeIntoDifferentMainDexGroups(appView),
-            new AllInstantiatedOrUninstantiated(appView),
+            new AllInstantiatedOrUninstantiated(appViewWithLiveness),
             new SameParentClass(),
             new SameNestHost(appView),
-            new PreserveMethodCharacteristics(appView),
+            new PreserveMethodCharacteristics(appViewWithLiveness),
             new SameFeatureSplit(appView),
             new RespectPackageBoundaries(appView),
-            new DontMergeSynchronizedClasses(appView),
+            new DontMergeSynchronizedClasses(appViewWithLiveness),
             new MinimizeFieldCasts(),
             new LimitGroups(appView));
     return ImmutableList.<Policy>builder()
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/SubtypingForrestForClasses.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/SubtypingForrestForClasses.java
index 05440b4..afb02cd 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/SubtypingForrestForClasses.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/SubtypingForrestForClasses.java
@@ -4,9 +4,9 @@
 
 package com.android.tools.r8.horizontalclassmerging;
 
+import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexProgramClass;
-import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
@@ -30,12 +30,12 @@
  * </code>
  */
 public class SubtypingForrestForClasses {
-  private final AppView<AppInfoWithLiveness> appView;
 
+  private final AppView<? extends AppInfoWithClassHierarchy> appView;
   private final Collection<DexProgramClass> roots = new ArrayList<>();
   private final Map<DexProgramClass, List<DexProgramClass>> subtypeMap = new IdentityHashMap<>();
 
-  public SubtypingForrestForClasses(AppView<AppInfoWithLiveness> appView) {
+  public SubtypingForrestForClasses(AppView<? extends AppInfoWithClassHierarchy> appView) {
     this.appView = appView;
     calculateSubtyping(appView.appInfo().classes());
   }
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/TreeFixer.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/TreeFixer.java
index c66d5de..d475710 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/TreeFixer.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/TreeFixer.java
@@ -5,6 +5,7 @@
 package com.android.tools.r8.horizontalclassmerging;
 
 import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexClass.FieldSetter;
 import com.android.tools.r8.graph.DexEncodedField;
@@ -19,7 +20,6 @@
 import com.android.tools.r8.graph.TreeFixerBase;
 import com.android.tools.r8.ir.conversion.ExtraUnusedNullParameter;
 import com.android.tools.r8.shaking.AnnotationFixer;
-import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.utils.OptionalBool;
 import com.google.common.collect.BiMap;
 import com.google.common.collect.HashBiMap;
@@ -37,16 +37,17 @@
  * tracked in {@link TreeFixer#lensBuilder}.
  */
 class TreeFixer extends TreeFixerBase {
+
+  private final AppView<? extends AppInfoWithClassHierarchy> appView;
   private final HorizontallyMergedClasses mergedClasses;
   private final HorizontalClassMergerGraphLens.Builder lensBuilder;
-  private final AppView<AppInfoWithLiveness> appView;
   private final DexItemFactory dexItemFactory;
   private final SyntheticArgumentClass syntheticArgumentClass;
   private final BiMap<DexMethodSignature, DexMethodSignature> reservedInterfaceSignatures =
       HashBiMap.create();
 
   public TreeFixer(
-      AppView<AppInfoWithLiveness> appView,
+      AppView<? extends AppInfoWithClassHierarchy> appView,
       HorizontallyMergedClasses mergedClasses,
       HorizontalClassMergerGraphLens.Builder lensBuilder,
       SyntheticArgumentClass syntheticArgumentClass) {
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/VirtualMethodMerger.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/VirtualMethodMerger.java
index c144128..efcbb27 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/VirtualMethodMerger.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/VirtualMethodMerger.java
@@ -5,6 +5,7 @@
 package com.android.tools.r8.horizontalclassmerging;
 
 import com.android.tools.r8.cf.CfVersion;
+import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexAnnotationSet;
 import com.android.tools.r8.graph.DexEncodedMethod;
@@ -18,7 +19,6 @@
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.graph.ResolutionResult.SingleResolutionResult;
 import com.android.tools.r8.ir.synthetic.AbstractSynthesizedCode;
-import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.utils.ListUtils;
 import com.android.tools.r8.utils.OptionalBool;
 import com.android.tools.r8.utils.structural.Ordered;
@@ -30,21 +30,22 @@
 import java.util.List;
 
 public class VirtualMethodMerger {
+
+  private final AppView<? extends AppInfoWithClassHierarchy> appView;
   private final DexItemFactory dexItemFactory;
   private final MergeGroup group;
   private final List<ProgramMethod> methods;
-  private final AppView<AppInfoWithLiveness> appView;
   private final DexMethod superMethod;
 
   public VirtualMethodMerger(
-      AppView<AppInfoWithLiveness> appView,
+      AppView<? extends AppInfoWithClassHierarchy> appView,
       MergeGroup group,
       List<ProgramMethod> methods,
       DexMethod superMethod) {
+    this.appView = appView;
     this.dexItemFactory = appView.dexItemFactory();
     this.group = group;
     this.methods = methods;
-    this.appView = appView;
     this.superMethod = superMethod;
   }
 
@@ -57,7 +58,8 @@
     }
 
     /** Get the super method handle if this method overrides a parent method. */
-    private DexMethod superMethod(AppView<AppInfoWithLiveness> appView, DexProgramClass target) {
+    private DexMethod superMethod(
+        AppView<? extends AppInfoWithClassHierarchy> appView, DexProgramClass target) {
       DexMethod template = methods.iterator().next().getReference();
       SingleResolutionResult resolutionResult =
           appView
@@ -79,7 +81,8 @@
       return resolutionResult.getResolvedMethod().getReference();
     }
 
-    public VirtualMethodMerger build(AppView<AppInfoWithLiveness> appView, MergeGroup group) {
+    public VirtualMethodMerger build(
+        AppView<? extends AppInfoWithClassHierarchy> appView, MergeGroup group) {
       // If not all the classes are in the merge group, find the fallback super method to call.
       DexMethod superMethod =
           methods.size() < group.size() ? superMethod(appView, group.getTarget()) : null;
@@ -171,6 +174,10 @@
     return numberOfNonAbstractMethods <= 1;
   }
 
+  boolean isNopOrTrivial() {
+    return isNop() || isTrivial();
+  }
+
   /**
    * If there is only a single method that does not override anything then it is safe to just move
    * it to the target type if it is not already in it.
@@ -221,12 +228,11 @@
     assert !methods.isEmpty();
 
     // Handle trivial merges.
-    if (isNop() || isTrivial()) {
+    if (isNopOrTrivial()) {
       mergeTrivial(classMethodsBuilder, lensBuilder);
       return;
     }
 
-
     Int2ReferenceSortedMap<DexMethod> classIdToMethodMap = new Int2ReferenceAVLTreeMap<>();
 
     CfVersion classFileVersion = null;
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/CheckAbstractClasses.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/CheckAbstractClasses.java
index e78b196..41fba55 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/CheckAbstractClasses.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/CheckAbstractClasses.java
@@ -4,11 +4,11 @@
 
 package com.android.tools.r8.horizontalclassmerging.policies;
 
+import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.horizontalclassmerging.MultiClassSameReferencePolicy;
 import com.android.tools.r8.horizontalclassmerging.policies.CheckAbstractClasses.AbstractClassification;
-import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.utils.InternalOptions;
 
 public class CheckAbstractClasses extends MultiClassSameReferencePolicy<AbstractClassification> {
@@ -20,7 +20,7 @@
 
   private final InternalOptions options;
 
-  public CheckAbstractClasses(AppView<AppInfoWithLiveness> appView) {
+  public CheckAbstractClasses(AppView<? extends AppInfoWithClassHierarchy> appView) {
     this.options = appView.options();
   }
 
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/LimitGroups.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/LimitGroups.java
index 80e1292..c798f04 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/LimitGroups.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/LimitGroups.java
@@ -4,11 +4,11 @@
 
 package com.android.tools.r8.horizontalclassmerging.policies;
 
+import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.horizontalclassmerging.MergeGroup;
 import com.android.tools.r8.horizontalclassmerging.MultiClassPolicy;
-import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.LinkedList;
@@ -17,7 +17,7 @@
 
   private final int maxGroupSize;
 
-  public LimitGroups(AppView<AppInfoWithLiveness> appView) {
+  public LimitGroups(AppView<? extends AppInfoWithClassHierarchy> appView) {
     maxGroupSize = appView.options().horizontalClassMergerOptions().getMaxGroupSize();
     assert maxGroupSize >= 2;
   }
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoDeadEnumLiteMaps.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoDeadEnumLiteMaps.java
new file mode 100644
index 0000000..3706d0a
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoDeadEnumLiteMaps.java
@@ -0,0 +1,35 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.horizontalclassmerging.policies;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.horizontalclassmerging.SingleClassPolicy;
+import com.android.tools.r8.ir.analysis.proto.EnumLiteProtoShrinker;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import java.util.Collections;
+import java.util.Set;
+
+public class NoDeadEnumLiteMaps extends SingleClassPolicy {
+
+  private final Set<DexType> deadEnumLiteMaps;
+
+  public NoDeadEnumLiteMaps(AppView<AppInfoWithLiveness> appView) {
+    this.deadEnumLiteMaps =
+        appView.withProtoEnumShrinker(
+            EnumLiteProtoShrinker::getDeadEnumLiteMaps, Collections.emptySet());
+  }
+
+  @Override
+  public boolean canMerge(DexProgramClass clazz) {
+    return !deadEnumLiteMaps.contains(clazz.getType());
+  }
+
+  @Override
+  public String getName() {
+    return "NoDeadEnumLiteMaps";
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoEnums.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoEnums.java
index 8bea8a4..e037345 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoEnums.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoEnums.java
@@ -4,20 +4,20 @@
 
 package com.android.tools.r8.horizontalclassmerging.policies;
 
+import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.horizontalclassmerging.SingleClassPolicy;
-import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import it.unimi.dsi.fastutil.objects.Reference2BooleanMap;
 import it.unimi.dsi.fastutil.objects.Reference2BooleanOpenHashMap;
 
 public class NoEnums extends SingleClassPolicy {
 
-  private final AppView<AppInfoWithLiveness> appView;
+  private final AppView<? extends AppInfoWithClassHierarchy> appView;
   private final Reference2BooleanMap<DexClass> cache = new Reference2BooleanOpenHashMap<>();
 
-  public NoEnums(AppView<AppInfoWithLiveness> appView) {
+  public NoEnums(AppView<? extends AppInfoWithClassHierarchy> appView) {
     this.appView = appView;
   }
 
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoIndirectRuntimeTypeChecks.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoIndirectRuntimeTypeChecks.java
index 8de67d6..12b6ba4 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoIndirectRuntimeTypeChecks.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoIndirectRuntimeTypeChecks.java
@@ -4,26 +4,27 @@
 
 package com.android.tools.r8.horizontalclassmerging.policies;
 
+import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.DexTypeList;
 import com.android.tools.r8.horizontalclassmerging.MultiClassSameReferencePolicy;
-import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.shaking.RuntimeTypeCheckInfo;
 import it.unimi.dsi.fastutil.objects.Reference2BooleanMap;
 import it.unimi.dsi.fastutil.objects.Reference2BooleanOpenHashMap;
 
 public class NoIndirectRuntimeTypeChecks extends MultiClassSameReferencePolicy<DexTypeList> {
 
-  private final AppView<AppInfoWithLiveness> appView;
+  private final AppView<? extends AppInfoWithClassHierarchy> appView;
   private final RuntimeTypeCheckInfo runtimeTypeCheckInfo;
 
   private final Reference2BooleanMap<DexType> cache = new Reference2BooleanOpenHashMap<>();
 
   public NoIndirectRuntimeTypeChecks(
-      AppView<AppInfoWithLiveness> appView, RuntimeTypeCheckInfo runtimeTypeCheckInfo) {
+      AppView<? extends AppInfoWithClassHierarchy> appView,
+      RuntimeTypeCheckInfo runtimeTypeCheckInfo) {
     this.appView = appView;
     this.runtimeTypeCheckInfo = runtimeTypeCheckInfo;
   }
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoKeepRules.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoKeepRules.java
index ec8a5aa..a13a445 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoKeepRules.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoKeepRules.java
@@ -4,34 +4,37 @@
 
 package com.android.tools.r8.horizontalclassmerging.policies;
 
+import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexEncodedMember;
 import com.android.tools.r8.graph.DexMember;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.horizontalclassmerging.SingleClassPolicy;
-import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.shaking.KeepInfoCollection;
 import com.google.common.collect.Iterables;
 import com.google.common.collect.Sets;
 import java.util.Set;
 
 public class NoKeepRules extends SingleClassPolicy {
-  private final AppView<AppInfoWithLiveness> appView;
+
+  private final AppView<? extends AppInfoWithClassHierarchy> appView;
+  private final KeepInfoCollection keepInfo;
+
   private final Set<DexType> dontMergeTypes = Sets.newIdentityHashSet();
 
-  public NoKeepRules(AppView<AppInfoWithLiveness> appView) {
+  public NoKeepRules(AppView<? extends AppInfoWithClassHierarchy> appView) {
     this.appView = appView;
-
+    this.keepInfo = appView.getKeepInfo();
     appView.appInfo().classes().forEach(this::processClass);
   }
 
   private void processClass(DexProgramClass programClass) {
     DexType type = programClass.getType();
-    boolean pinProgramClass = appView.appInfo().isPinned(type);
-
+    boolean pinProgramClass = keepInfo.isPinned(type, appView);
     for (DexEncodedMember<?, ?> member : programClass.members()) {
       DexMember<?, ?> reference = member.getReference();
-      if (appView.appInfo().isPinned(reference)) {
+      if (keepInfo.isPinned(reference, appView)) {
         pinProgramClass = true;
         Iterables.addAll(
             dontMergeTypes,
@@ -39,7 +42,6 @@
                 reference.getReferencedBaseTypes(appView.dexItemFactory()), DexType::isClassType));
       }
     }
-
     if (pinProgramClass) {
       dontMergeTypes.add(type);
     }
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoServiceLoaders.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoServiceLoaders.java
index b1f0d99..30bee86 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoServiceLoaders.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoServiceLoaders.java
@@ -4,18 +4,18 @@
 
 package com.android.tools.r8.horizontalclassmerging.policies;
 
+import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.horizontalclassmerging.SingleClassPolicy;
-import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import java.util.Set;
 
 public class NoServiceLoaders extends SingleClassPolicy {
-  private final AppView<AppInfoWithLiveness> appView;
+  private final AppView<? extends AppInfoWithClassHierarchy> appView;
   private final Set<DexType> allServiceImplementations;
 
-  public NoServiceLoaders(AppView<AppInfoWithLiveness> appView) {
+  public NoServiceLoaders(AppView<? extends AppInfoWithClassHierarchy> appView) {
     this.appView = appView;
 
     allServiceImplementations = appView.appServices().computeAllServiceImplementations();
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NotMatchedByNoHorizontalClassMerging.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NotMatchedByNoHorizontalClassMerging.java
index 8751afb..86ad07c 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NotMatchedByNoHorizontalClassMerging.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NotMatchedByNoHorizontalClassMerging.java
@@ -6,29 +6,20 @@
 
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexProgramClass;
-import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.horizontalclassmerging.SingleClassPolicy;
-import com.android.tools.r8.ir.analysis.proto.EnumLiteProtoShrinker;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
-import java.util.Collections;
-import java.util.Set;
 
 public class NotMatchedByNoHorizontalClassMerging extends SingleClassPolicy {
 
   private final AppView<AppInfoWithLiveness> appView;
-  private final Set<DexType> deadEnumLiteMaps;
 
   public NotMatchedByNoHorizontalClassMerging(AppView<AppInfoWithLiveness> appView) {
     this.appView = appView;
-    this.deadEnumLiteMaps =
-        appView.withProtoEnumShrinker(
-            EnumLiteProtoShrinker::getDeadEnumLiteMaps, Collections.emptySet());
   }
 
   @Override
   public boolean canMerge(DexProgramClass program) {
-    return !deadEnumLiteMaps.contains(program.getType())
-        && !appView.appInfo().isNoHorizontalClassMergingOfType(program.getType());
+    return !appView.appInfo().isNoHorizontalClassMergingOfType(program.getType());
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NotVerticallyMergedIntoSubtype.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NotVerticallyMergedIntoSubtype.java
index 92d50d1..062e695 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NotVerticallyMergedIntoSubtype.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NotVerticallyMergedIntoSubtype.java
@@ -4,15 +4,15 @@
 
 package com.android.tools.r8.horizontalclassmerging.policies;
 
+import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.horizontalclassmerging.SingleClassPolicy;
-import com.android.tools.r8.shaking.AppInfoWithLiveness;
 
 public class NotVerticallyMergedIntoSubtype extends SingleClassPolicy {
-  private final AppView<AppInfoWithLiveness> appView;
+  private final AppView<? extends AppInfoWithClassHierarchy> appView;
 
-  public NotVerticallyMergedIntoSubtype(AppView<AppInfoWithLiveness> appView) {
+  public NotVerticallyMergedIntoSubtype(AppView<? extends AppInfoWithClassHierarchy> appView) {
     this.appView = appView;
   }
 
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/PreventMergeIntoDifferentMainDexGroups.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/PreventMergeIntoDifferentMainDexGroups.java
index 38df5cf..9d529d3 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/PreventMergeIntoDifferentMainDexGroups.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/PreventMergeIntoDifferentMainDexGroups.java
@@ -4,10 +4,10 @@
 
 package com.android.tools.r8.horizontalclassmerging.policies;
 
+import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.horizontalclassmerging.MultiClassSameReferencePolicy;
-import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.shaking.MainDexInfo;
 import com.android.tools.r8.shaking.MainDexInfo.MainDexGroup;
 import com.android.tools.r8.synthesis.SyntheticItems;
@@ -18,7 +18,8 @@
   private final MainDexInfo mainDexInfo;
   private final SyntheticItems synthetics;
 
-  public PreventMergeIntoDifferentMainDexGroups(AppView<AppInfoWithLiveness> appView) {
+  public PreventMergeIntoDifferentMainDexGroups(
+      AppView<? extends AppInfoWithClassHierarchy> appView) {
     mainDexInfo = appView.appInfo().getMainDexInfo();
     synthetics = appView.getSyntheticItems();
   }
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/PreventMethodImplementation.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/PreventMethodImplementation.java
index b182330..5bffb33 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/PreventMethodImplementation.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/PreventMethodImplementation.java
@@ -4,6 +4,7 @@
 
 package com.android.tools.r8.horizontalclassmerging.policies;
 
+import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexEncodedMethod;
@@ -15,7 +16,6 @@
 import com.android.tools.r8.horizontalclassmerging.MergeGroup;
 import com.android.tools.r8.horizontalclassmerging.MultiClassPolicy;
 import com.android.tools.r8.horizontalclassmerging.SubtypingForrestForClasses;
-import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.utils.collections.DexMethodSignatureSet;
 import java.util.Collection;
 import java.util.IdentityHashMap;
@@ -49,7 +49,8 @@
  * <p>See: https://docs.oracle.com/javase/specs/jvms/se15/html/jvms-5.html#jvms-5.4.3.3)
  */
 public class PreventMethodImplementation extends MultiClassPolicy {
-  private final AppView<AppInfoWithLiveness> appView;
+
+  private final AppView<? extends AppInfoWithClassHierarchy> appView;
   private final SubtypingForrestForClasses subtypingForrestForClasses;
 
   private final InterfaceDefaultSignaturesCache interfaceDefaultMethodsCache =
@@ -123,7 +124,7 @@
     }
   }
 
-  public PreventMethodImplementation(AppView<AppInfoWithLiveness> appView) {
+  public PreventMethodImplementation(AppView<? extends AppInfoWithClassHierarchy> appView) {
     this.appView = appView;
     this.subtypingForrestForClasses = new SubtypingForrestForClasses(appView);
   }
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/RespectPackageBoundaries.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/RespectPackageBoundaries.java
index 78cfe13..8f59c36 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/RespectPackageBoundaries.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/RespectPackageBoundaries.java
@@ -4,12 +4,12 @@
 
 package com.android.tools.r8.horizontalclassmerging.policies;
 
+import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexEncodedMember;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.horizontalclassmerging.MergeGroup;
 import com.android.tools.r8.horizontalclassmerging.MultiClassPolicy;
-import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.shaking.VerticalClassMerger.IllegalAccessDetector;
 import com.android.tools.r8.utils.TraversalContinuation;
 import java.util.ArrayList;
@@ -18,9 +18,10 @@
 import java.util.Map;
 
 public class RespectPackageBoundaries extends MultiClassPolicy {
-  private final AppView<AppInfoWithLiveness> appView;
 
-  public RespectPackageBoundaries(AppView<AppInfoWithLiveness> appView) {
+  private final AppView<? extends AppInfoWithClassHierarchy> appView;
+
+  public RespectPackageBoundaries(AppView<? extends AppInfoWithClassHierarchy> appView) {
     this.appView = appView;
   }
 
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/SameFeatureSplit.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/SameFeatureSplit.java
index b3ec017..58054a2 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/SameFeatureSplit.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/SameFeatureSplit.java
@@ -5,15 +5,15 @@
 package com.android.tools.r8.horizontalclassmerging.policies;
 
 import com.android.tools.r8.FeatureSplit;
+import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.horizontalclassmerging.MultiClassSameReferencePolicy;
-import com.android.tools.r8.shaking.AppInfoWithLiveness;
 
 public class SameFeatureSplit extends MultiClassSameReferencePolicy<FeatureSplit> {
-  private final AppView<AppInfoWithLiveness> appView;
+  private final AppView<? extends AppInfoWithClassHierarchy> appView;
 
-  public SameFeatureSplit(AppView<AppInfoWithLiveness> appView) {
+  public SameFeatureSplit(AppView<? extends AppInfoWithClassHierarchy> appView) {
     this.appView = appView;
   }
 
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/SameInstanceFields.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/SameInstanceFields.java
index 291aae5..391fb75 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/SameInstanceFields.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/SameInstanceFields.java
@@ -4,6 +4,7 @@
 
 package com.android.tools.r8.horizontalclassmerging.policies;
 
+import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexEncodedField;
 import com.android.tools.r8.graph.DexItemFactory;
@@ -12,7 +13,6 @@
 import com.android.tools.r8.graph.FieldAccessFlags;
 import com.android.tools.r8.horizontalclassmerging.MultiClassSameReferencePolicy;
 import com.android.tools.r8.horizontalclassmerging.policies.SameInstanceFields.InstanceFieldInfo;
-import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.google.common.collect.HashMultiset;
 import com.google.common.collect.Multiset;
 import java.util.Objects;
@@ -21,7 +21,7 @@
 
   private final DexItemFactory dexItemFactory;
 
-  public SameInstanceFields(AppView<AppInfoWithLiveness> appView) {
+  public SameInstanceFields(AppView<? extends AppInfoWithClassHierarchy> appView) {
     this.dexItemFactory = appView.dexItemFactory();
   }
 
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/SameNestHost.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/SameNestHost.java
index fc8e39c..a4ba653 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/SameNestHost.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/SameNestHost.java
@@ -4,18 +4,18 @@
 
 package com.android.tools.r8.horizontalclassmerging.policies;
 
+import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
 import com.android.tools.r8.graph.AppView;
 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.horizontalclassmerging.MultiClassSameReferencePolicy;
-import com.android.tools.r8.shaking.AppInfoWithLiveness;
 
 public class SameNestHost extends MultiClassSameReferencePolicy<DexType> {
 
   private final DexItemFactory dexItemFactory;
 
-  public SameNestHost(AppView<AppInfoWithLiveness> appView) {
+  public SameNestHost(AppView<? extends AppInfoWithClassHierarchy> appView) {
     this.dexItemFactory = appView.dexItemFactory();
   }
 
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/SyntheticItemsPolicy.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/SyntheticItemsPolicy.java
index cbc9b3d..6a84212 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/SyntheticItemsPolicy.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/SyntheticItemsPolicy.java
@@ -4,11 +4,11 @@
 
 package com.android.tools.r8.horizontalclassmerging.policies;
 
+import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.horizontalclassmerging.MultiClassSameReferencePolicy;
 import com.android.tools.r8.horizontalclassmerging.policies.SyntheticItemsPolicy.ClassKind;
-import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.synthesis.SyntheticItems;
 import com.android.tools.r8.synthesis.SyntheticNaming.SyntheticKind;
 
@@ -19,9 +19,9 @@
     NOT_SYNTHETIC
   }
 
-  private final AppView<AppInfoWithLiveness> appView;
+  private final AppView<? extends AppInfoWithClassHierarchy> appView;
 
-  public SyntheticItemsPolicy(AppView<AppInfoWithLiveness> appView) {
+  public SyntheticItemsPolicy(AppView<? extends AppInfoWithClassHierarchy> appView) {
     this.appView = appView;
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/code/CanonicalPositions.java b/src/main/java/com/android/tools/r8/ir/code/CanonicalPositions.java
index de25bd7..06537e3 100644
--- a/src/main/java/com/android/tools/r8/ir/code/CanonicalPositions.java
+++ b/src/main/java/com/android/tools/r8/ir/code/CanonicalPositions.java
@@ -17,6 +17,7 @@
   private final Position callerPosition;
   private final Map<Position, Position> canonicalPositions;
   private final Position preamblePosition;
+  private final boolean isCompilerSynthesizedInlinee;
 
   // Lazily computed synthetic position for shared exceptional exits in synchronized methods.
   private Position syntheticPosition;
@@ -24,18 +25,22 @@
   public CanonicalPositions(
       Position callerPosition,
       int expectedPositionsCount,
-      DexMethod method) {
+      DexMethod method,
+      boolean methodIsSynthesized) {
     canonicalPositions =
         new HashMap<>(1 + (callerPosition == null ? 0 : 1) + expectedPositionsCount);
-    this.callerPosition = callerPosition;
     if (callerPosition != null) {
-      canonicalPositions.put(callerPosition, callerPosition);
+      this.callerPosition = getCanonical(callerPosition);
+      isCompilerSynthesizedInlinee = methodIsSynthesized;
+      preamblePosition =
+          methodIsSynthesized
+              ? callerPosition
+              : getCanonical(new Position(0, null, method, callerPosition));
+    } else {
+      this.callerPosition = null;
+      isCompilerSynthesizedInlinee = false;
+      preamblePosition = getCanonical(Position.synthetic(0, method, null));
     }
-    preamblePosition =
-        callerPosition == null
-            ? Position.synthetic(0, method, null)
-            : new Position(0, null, method, callerPosition);
-    canonicalPositions.put(preamblePosition, preamblePosition);
   }
 
   public Position getPreamblePosition() {
@@ -53,16 +58,21 @@
 
   /**
    * Append callerPosition (supplied in constructor) to the end of caller's caller chain and return
-   * the canonical instance. Always returns null if preserveCaller (also supplied in constructor) is
-   * false.
+   * the canonical instance.
    */
   public Position canonicalizeCallerPosition(Position caller) {
     if (caller == null) {
       return callerPosition;
     }
     if (caller.callerPosition == null && callerPosition == null) {
+      // This is itself the outer-most position.
       return getCanonical(caller);
     }
+    if (caller.callerPosition == null && isCompilerSynthesizedInlinee) {
+      // This is the outer-most position of the inlinee (eg, the inlinee itself).
+      // If compiler synthesized, strip it from the position info by directly returning caller.
+      return callerPosition;
+    }
     Position callerOfCaller = canonicalizeCallerPosition(caller.callerPosition);
     return getCanonical(
         caller.isNone()
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/CfSourceCode.java b/src/main/java/com/android/tools/r8/ir/conversion/CfSourceCode.java
index 1d7f619..9f464b8 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/CfSourceCode.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/CfSourceCode.java
@@ -246,7 +246,12 @@
       }
     }
     this.state = new CfState(origin);
-    canonicalPositions = new CanonicalPositions(callerPosition, cfPositionCount, originalMethod);
+    canonicalPositions =
+        new CanonicalPositions(
+            callerPosition,
+            cfPositionCount,
+            originalMethod,
+            method.getDefinition().isD8R8Synthesized());
     internalOutputMode = appView.options().getInternalOutputMode();
 
     needsGeneratedMethodSynchronization =
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/DexSourceCode.java b/src/main/java/com/android/tools/r8/ir/conversion/DexSourceCode.java
index facf351..6c0279a 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/DexSourceCode.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/DexSourceCode.java
@@ -87,7 +87,8 @@
         new CanonicalPositions(
             callerPosition,
             debugEntries == null ? 0 : debugEntries.size(),
-            originalMethod);
+            originalMethod,
+            method.getDefinition().isD8R8Synthesized());
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/BackportedMethodRewriter.java b/src/main/java/com/android/tools/r8/ir/desugar/BackportedMethodRewriter.java
index 7bb5741..832fd8c 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
@@ -31,6 +31,7 @@
 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.ir.desugar.backports.SparseArrayMethodRewrites;
 import com.android.tools.r8.synthesis.SyntheticNaming;
 import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.AndroidApp;
@@ -1055,6 +1056,13 @@
             new MethodGenerator(
                 method, BackportedMethods::MathMethods_floorModLongInt, "floorModLongInt"));
       }
+
+      // android.util.SparseArray
+
+      // void android.util.SparseArray.set(int, Object))
+      addProvider(
+          new InvokeRewriter(
+              factory.androidUtilSparseArrayMembers.set, SparseArrayMethodRewrites.rewriteSet()));
     }
 
     private void initializeJava9MethodProviders(DexItemFactory factory) {
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/backports/SparseArrayMethodRewrites.java b/src/main/java/com/android/tools/r8/ir/desugar/backports/SparseArrayMethodRewrites.java
new file mode 100644
index 0000000..e1858e2
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/desugar/backports/SparseArrayMethodRewrites.java
@@ -0,0 +1,20 @@
+// Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.desugar.backports;
+
+import com.android.tools.r8.cf.code.CfInvoke;
+import com.android.tools.r8.ir.desugar.BackportedMethodRewriter.MethodInvokeRewriter;
+import org.objectweb.asm.Opcodes;
+
+public final class SparseArrayMethodRewrites {
+
+  private SparseArrayMethodRewrites() {}
+
+  public static MethodInvokeRewriter rewriteSet() {
+    // Rewrite android/util/SparseArray#set to android/util/SparseArray#put
+    return (invoke, factory) ->
+        new CfInvoke(Opcodes.INVOKEVIRTUAL, factory.androidUtilSparseArrayMembers.put, false);
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/naming/ClassNameMapper.java b/src/main/java/com/android/tools/r8/naming/ClassNameMapper.java
index 26742bd..9d7d84c 100644
--- a/src/main/java/com/android/tools/r8/naming/ClassNameMapper.java
+++ b/src/main/java/com/android/tools/r8/naming/ClassNameMapper.java
@@ -100,25 +100,35 @@
   }
 
   public static ClassNameMapper mapperFromString(
-      String contents, DiagnosticsHandler diagnosticsHandler, boolean allowEmptyMappedRanges)
+      String contents,
+      DiagnosticsHandler diagnosticsHandler,
+      boolean allowEmptyMappedRanges,
+      boolean allowExperimentalMapping)
       throws IOException {
     return mapperFromBufferedReader(
-        CharSource.wrap(contents).openBufferedStream(), diagnosticsHandler, allowEmptyMappedRanges);
+        CharSource.wrap(contents).openBufferedStream(),
+        diagnosticsHandler,
+        allowEmptyMappedRanges,
+        allowExperimentalMapping);
   }
 
   private static ClassNameMapper mapperFromBufferedReader(
       BufferedReader reader, DiagnosticsHandler diagnosticsHandler) throws IOException {
-    return mapperFromBufferedReader(reader, diagnosticsHandler, false);
+    return mapperFromBufferedReader(reader, diagnosticsHandler, false, false);
   }
 
   public static ClassNameMapper mapperFromBufferedReader(
-      BufferedReader reader, DiagnosticsHandler diagnosticsHandler, boolean allowEmptyMappedRanges)
+      BufferedReader reader,
+      DiagnosticsHandler diagnosticsHandler,
+      boolean allowEmptyMappedRanges,
+      boolean allowExperimentalMapping)
       throws IOException {
     try (ProguardMapReader proguardReader =
         new ProguardMapReader(
             reader,
             diagnosticsHandler != null ? diagnosticsHandler : new Reporter(),
-            allowEmptyMappedRanges)) {
+            allowEmptyMappedRanges,
+            allowExperimentalMapping)) {
       ClassNameMapper.Builder builder = ClassNameMapper.builder();
       proguardReader.parse(builder);
       return builder.build();
diff --git a/src/main/java/com/android/tools/r8/naming/ProguardMapReader.java b/src/main/java/com/android/tools/r8/naming/ProguardMapReader.java
index ae65b10..f1e483f 100644
--- a/src/main/java/com/android/tools/r8/naming/ProguardMapReader.java
+++ b/src/main/java/com/android/tools/r8/naming/ProguardMapReader.java
@@ -9,9 +9,9 @@
 import com.android.tools.r8.naming.MemberNaming.MethodSignature;
 import com.android.tools.r8.naming.MemberNaming.Signature;
 import com.android.tools.r8.naming.ProguardMap.Builder;
+import com.android.tools.r8.naming.mappinginformation.MapVersionMappingInformation;
 import com.android.tools.r8.naming.mappinginformation.MappingInformation;
 import com.android.tools.r8.naming.mappinginformation.MappingInformationDiagnostics;
-import com.android.tools.r8.naming.mappinginformation.MetaInfMappingInformation;
 import com.android.tools.r8.position.TextPosition;
 import com.android.tools.r8.utils.IdentifierUtils;
 import com.android.tools.r8.utils.StringUtils;
@@ -66,6 +66,7 @@
   private final JsonParser jsonParser = new JsonParser();
   private final DiagnosticsHandler diagnosticsHandler;
   private final boolean allowEmptyMappedRanges;
+  private final boolean allowExperimentalMapping;
 
   @Override
   public void close() throws IOException {
@@ -75,10 +76,12 @@
   ProguardMapReader(
       BufferedReader reader,
       DiagnosticsHandler diagnosticsHandler,
-      boolean allowEmptyMappedRanges) {
+      boolean allowEmptyMappedRanges,
+      boolean allowExperimentalMapping) {
     this.reader = reader;
     this.diagnosticsHandler = diagnosticsHandler;
     this.allowEmptyMappedRanges = allowEmptyMappedRanges;
+    this.allowExperimentalMapping = allowExperimentalMapping;
     assert reader != null;
     assert diagnosticsHandler != null;
   }
@@ -266,9 +269,18 @@
         diagnosticsHandler,
         lineNo,
         info -> {
-          MetaInfMappingInformation generatorInfo = info.asMetaInfMappingInformation();
+          MapVersionMappingInformation generatorInfo = info.asMetaInfMappingInformation();
           if (generatorInfo != null) {
-            version = generatorInfo.getMapVersion();
+            if (generatorInfo.getMapVersion().equals(MapVersion.MapVersionExperimental)) {
+              // A mapping file that is marked "experimental" will be treated as an unversioned
+              // file if the compiler/tool is not explicitly running with experimental support.
+              version =
+                  allowExperimentalMapping
+                      ? MapVersion.MapVersionExperimental
+                      : MapVersion.MapVersionNone;
+            } else {
+              version = generatorInfo.getMapVersion();
+            }
           }
           onMappingInfo.accept(info);
         });
diff --git a/src/main/java/com/android/tools/r8/naming/ProguardMapSupplier.java b/src/main/java/com/android/tools/r8/naming/ProguardMapSupplier.java
index 73ae362..ccc2ef7 100644
--- a/src/main/java/com/android/tools/r8/naming/ProguardMapSupplier.java
+++ b/src/main/java/com/android/tools/r8/naming/ProguardMapSupplier.java
@@ -7,7 +7,7 @@
 import com.android.tools.r8.StringConsumer;
 import com.android.tools.r8.Version;
 import com.android.tools.r8.errors.Unreachable;
-import com.android.tools.r8.naming.mappinginformation.MetaInfMappingInformation;
+import com.android.tools.r8.naming.mappinginformation.MapVersionMappingInformation;
 import com.android.tools.r8.utils.Box;
 import com.android.tools.r8.utils.ChainableStringConsumer;
 import com.android.tools.r8.utils.ExceptionUtils;
@@ -107,7 +107,7 @@
     if (mapVersion.isGreaterThan(MapVersion.MapVersionNone)) {
       builder
           .append("# ")
-          .append(new MetaInfMappingInformation(mapVersion).serialize())
+          .append(new MapVersionMappingInformation(mapVersion).serialize())
           .append("\n");
     }
     consumer.accept(builder.toString(), reporter);
diff --git a/src/main/java/com/android/tools/r8/naming/SeedMapper.java b/src/main/java/com/android/tools/r8/naming/SeedMapper.java
index 927cf6e..dee503b 100644
--- a/src/main/java/com/android/tools/r8/naming/SeedMapper.java
+++ b/src/main/java/com/android/tools/r8/naming/SeedMapper.java
@@ -74,7 +74,7 @@
   private static SeedMapper seedMapperFromInputStream(Reporter reporter, InputStream in)
       throws IOException {
     BufferedReader reader = new BufferedReader(new InputStreamReader(in, StandardCharsets.UTF_8));
-    try (ProguardMapReader proguardReader = new ProguardMapReader(reader, reporter, false)) {
+    try (ProguardMapReader proguardReader = new ProguardMapReader(reader, reporter, false, false)) {
       SeedMapper.Builder builder = SeedMapper.builder(reporter);
       proguardReader.parse(builder);
       return builder.build();
diff --git a/src/main/java/com/android/tools/r8/naming/mappinginformation/MetaInfMappingInformation.java b/src/main/java/com/android/tools/r8/naming/mappinginformation/MapVersionMappingInformation.java
similarity index 83%
rename from src/main/java/com/android/tools/r8/naming/mappinginformation/MetaInfMappingInformation.java
rename to src/main/java/com/android/tools/r8/naming/mappinginformation/MapVersionMappingInformation.java
index ea3824a..cd72831 100644
--- a/src/main/java/com/android/tools/r8/naming/mappinginformation/MetaInfMappingInformation.java
+++ b/src/main/java/com/android/tools/r8/naming/mappinginformation/MapVersionMappingInformation.java
@@ -12,14 +12,14 @@
 import com.google.gson.JsonPrimitive;
 import java.util.function.Consumer;
 
-public class MetaInfMappingInformation extends MappingInformation {
+public class MapVersionMappingInformation extends MappingInformation {
 
-  public static final String ID = "com.android.tools.r8.metainf";
-  public static final String MAP_VERSION_KEY = "map-version";
+  public static final String ID = "com.android.tools.r8.mapping";
+  public static final String MAP_VERSION_KEY = "version";
 
   private final MapVersion mapVersion;
 
-  public MetaInfMappingInformation(MapVersion mapVersion) {
+  public MapVersionMappingInformation(MapVersion mapVersion) {
     super();
     this.mapVersion = mapVersion;
   }
@@ -35,7 +35,7 @@
   }
 
   @Override
-  public MetaInfMappingInformation asMetaInfMappingInformation() {
+  public MapVersionMappingInformation asMetaInfMappingInformation() {
     return this;
   }
 
@@ -73,6 +73,6 @@
     if (mapVersion == null) {
       return;
     }
-    onMappingInfo.accept(new MetaInfMappingInformation(mapVersion));
+    onMappingInfo.accept(new MapVersionMappingInformation(mapVersion));
   }
 }
diff --git a/src/main/java/com/android/tools/r8/naming/mappinginformation/MappingInformation.java b/src/main/java/com/android/tools/r8/naming/mappinginformation/MappingInformation.java
index 145080f..f8f1e53 100644
--- a/src/main/java/com/android/tools/r8/naming/mappinginformation/MappingInformation.java
+++ b/src/main/java/com/android/tools/r8/naming/mappinginformation/MappingInformation.java
@@ -22,7 +22,7 @@
     return false;
   }
 
-  public MetaInfMappingInformation asMetaInfMappingInformation() {
+  public MapVersionMappingInformation asMetaInfMappingInformation() {
     return null;
   }
 
@@ -83,8 +83,8 @@
       int lineNumber,
       Consumer<MappingInformation> onMappingInfo) {
     switch (id) {
-      case MetaInfMappingInformation.ID:
-        MetaInfMappingInformation.deserialize(
+      case MapVersionMappingInformation.ID:
+        MapVersionMappingInformation.deserialize(
             version, object, diagnosticsHandler, lineNumber, onMappingInfo);
         return;
       case FileNameInformation.ID:
diff --git a/src/main/java/com/android/tools/r8/naming/signature/GenericSignatureRewriter.java b/src/main/java/com/android/tools/r8/naming/signature/GenericSignatureRewriter.java
index 2c57aa4..3893e32 100644
--- a/src/main/java/com/android/tools/r8/naming/signature/GenericSignatureRewriter.java
+++ b/src/main/java/com/android/tools/r8/naming/signature/GenericSignatureRewriter.java
@@ -9,7 +9,6 @@
 import com.android.tools.r8.graph.GenericSignatureTypeRewriter;
 import com.android.tools.r8.naming.NamingLens;
 import com.android.tools.r8.utils.InternalOptions;
-import com.android.tools.r8.utils.Reporter;
 import com.android.tools.r8.utils.ThreadUtils;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.ExecutorService;
@@ -20,13 +19,11 @@
   private final AppView<?> appView;
   private final NamingLens namingLens;
   private final InternalOptions options;
-  private final Reporter reporter;
 
   public GenericSignatureRewriter(AppView<?> appView, NamingLens namingLens) {
     this.appView = appView;
     this.namingLens = namingLens;
     this.options = appView.options();
-    this.reporter = options.reporter;
   }
 
   public void run(Iterable<? extends DexProgramClass> classes, ExecutorService executorService)
diff --git a/src/main/java/com/android/tools/r8/retrace/Retrace.java b/src/main/java/com/android/tools/r8/retrace/Retrace.java
index 71d88c3..faf8616 100644
--- a/src/main/java/com/android/tools/r8/retrace/Retrace.java
+++ b/src/main/java/com/android/tools/r8/retrace/Retrace.java
@@ -13,6 +13,7 @@
 import com.android.tools.r8.retrace.RetraceCommand.Builder;
 import com.android.tools.r8.retrace.internal.PlainStackTraceLineParser;
 import com.android.tools.r8.retrace.internal.RetraceAbortException;
+import com.android.tools.r8.retrace.internal.RetracerImpl;
 import com.android.tools.r8.retrace.internal.StackTraceRegularExpressionParser;
 import com.android.tools.r8.utils.ExceptionDiagnostic;
 import com.android.tools.r8.utils.ListUtils;
@@ -224,13 +225,25 @@
    * @param command The command that describes the desired behavior of this retrace invocation.
    */
   public static void run(RetraceCommand command) {
+    boolean allowExperimentalMapVersion =
+        System.getProperty("com.android.tools.r8.experimentalmapping") != null;
+    runForTesting(command, allowExperimentalMapVersion);
+  }
+
+  static void runForTesting(RetraceCommand command, boolean allowExperimentalMapping) {
     try {
       Timing timing = Timing.create("R8 retrace", command.printMemory());
       timing.begin("Read proguard map");
       RetraceOptions options = command.getOptions();
       DiagnosticsHandler diagnosticsHandler = options.getDiagnosticsHandler();
+      // The setup of a retracer should likely also follow a builder pattern instead of having
+      // static create methods. That would avoid the need to method overload the construction here
+      // and the default create would become the default build of a retracer.
       Retracer retracer =
-          Retracer.createDefault(options.getProguardMapProducer(), diagnosticsHandler);
+          RetracerImpl.create(
+              options.getProguardMapProducer(),
+              options.getDiagnosticsHandler(),
+              allowExperimentalMapping);
       timing.end();
       timing.begin("Report result");
       StringRetrace stringRetrace =
diff --git a/src/main/java/com/android/tools/r8/retrace/RetraceHelper.java b/src/main/java/com/android/tools/r8/retrace/RetraceHelper.java
new file mode 100644
index 0000000..c966897
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/retrace/RetraceHelper.java
@@ -0,0 +1,12 @@
+// 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.retrace;
+
+/** Non-kept class for internal access from tests. */
+public class RetraceHelper {
+
+  public static void runForTesting(RetraceCommand command, boolean allowExperimentalMapping) {
+    Retrace.runForTesting(command, allowExperimentalMapping);
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/retrace/Retracer.java b/src/main/java/com/android/tools/r8/retrace/Retracer.java
index 015dac8..b99f20c 100644
--- a/src/main/java/com/android/tools/r8/retrace/Retracer.java
+++ b/src/main/java/com/android/tools/r8/retrace/Retracer.java
@@ -28,6 +28,6 @@
 
   static Retracer createDefault(
       ProguardMapProducer proguardMapProducer, DiagnosticsHandler diagnosticsHandler) {
-    return RetracerImpl.create(proguardMapProducer, diagnosticsHandler);
+    return RetracerImpl.create(proguardMapProducer, diagnosticsHandler, false);
   }
 }
diff --git a/src/main/java/com/android/tools/r8/retrace/internal/RetracerImpl.java b/src/main/java/com/android/tools/r8/retrace/internal/RetracerImpl.java
index 33a7d80..d1c5874 100644
--- a/src/main/java/com/android/tools/r8/retrace/internal/RetracerImpl.java
+++ b/src/main/java/com/android/tools/r8/retrace/internal/RetracerImpl.java
@@ -26,7 +26,9 @@
   }
 
   public static RetracerImpl create(
-      ProguardMapProducer proguardMapProducer, DiagnosticsHandler diagnosticsHandler) {
+      ProguardMapProducer proguardMapProducer,
+      DiagnosticsHandler diagnosticsHandler,
+      boolean allowExperimentalMapping) {
     if (proguardMapProducer instanceof DirectClassNameMapperProguardMapProducer) {
       return new RetracerImpl(
           ((DirectClassNameMapperProguardMapProducer) proguardMapProducer).getClassNameMapper());
@@ -34,7 +36,10 @@
     try {
       ClassNameMapper classNameMapper =
           ClassNameMapper.mapperFromBufferedReader(
-              new BufferedReader(proguardMapProducer.get()), diagnosticsHandler, true);
+              new BufferedReader(proguardMapProducer.get()),
+              diagnosticsHandler,
+              true,
+              allowExperimentalMapping);
       return new RetracerImpl(classNameMapper);
     } catch (Throwable throwable) {
       throw new InvalidMappingFileException(throwable);
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 86c8ef6..7c2162c 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -2983,7 +2983,8 @@
       ImmutableSet<ProguardKeepRuleBase> keepAllSet =
           ImmutableSet.of(appView.options().getProguardConfiguration().getKeepAllRule());
       for (DexProgramClass clazz : appView.appInfo().classes()) {
-        if (appView.getSyntheticItems().isNonLegacySynthetic(clazz)) {
+        if (appView.getSyntheticItems().isSyntheticClass(clazz)
+            && !appView.getSyntheticItems().isSubjectToKeepRules(clazz)) {
           // Don't treat compiler synthesized classes as kept roots.
           continue;
         }
diff --git a/src/main/java/com/android/tools/r8/shaking/TreePruner.java b/src/main/java/com/android/tools/r8/shaking/TreePruner.java
index ef2f149..577ecc2 100644
--- a/src/main/java/com/android/tools/r8/shaking/TreePruner.java
+++ b/src/main/java/com/android/tools/r8/shaking/TreePruner.java
@@ -15,6 +15,7 @@
 import com.android.tools.r8.graph.DexTypeList;
 import com.android.tools.r8.graph.DirectMappedDexApplication;
 import com.android.tools.r8.graph.EnclosingMethodAttribute;
+import com.android.tools.r8.graph.GenericSignatureTypeVariableRemover;
 import com.android.tools.r8.graph.InnerClassAttribute;
 import com.android.tools.r8.graph.NestMemberClassAttribute;
 import com.android.tools.r8.ir.optimize.info.MutableFieldOptimizationInfo;
@@ -45,6 +46,7 @@
   private final UnusedItemsPrinter unusedItemsPrinter;
   private final Set<DexType> prunedTypes = Sets.newIdentityHashSet();
   private final Set<DexMethod> methodsToKeepForConfigurationDebugging = Sets.newIdentityHashSet();
+  private final GenericSignatureTypeVariableRemover typeVariableRemover;
 
   public TreePruner(AppView<AppInfoWithLiveness> appView) {
     this(appView, DefaultTreePrunerConfiguration.getInstance());
@@ -61,6 +63,11 @@
                     ExceptionUtils.withConsumeResourceHandler(
                         options.reporter, options.usageInformationConsumer, s))
             : UnusedItemsPrinter.DONT_PRINT;
+    this.typeVariableRemover =
+        new GenericSignatureTypeVariableRemover(
+            appView,
+            this::isAttributeReferencingMissingOrPrunedType,
+            this::isAttributeReferencingPrunedItem);
   }
 
   public DirectMappedDexApplication run(ExecutorService executorService) throws ExecutionException {
@@ -193,6 +200,7 @@
     if (reachableStaticFields != null) {
       clazz.setStaticFields(reachableStaticFields);
     }
+    typeVariableRemover.removeDeadGenericSignatureTypeVariables(clazz);
     clazz.removeInnerClasses(this::isAttributeReferencingMissingOrPrunedType);
     clazz.removeEnclosingMethodAttribute(this::isAttributeReferencingPrunedItem);
     rewriteNestAttributes(clazz);
diff --git a/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java b/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
index 3475116..800332f 100644
--- a/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
+++ b/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
@@ -9,6 +9,7 @@
 import static com.android.tools.r8.ir.code.Invoke.Type.STATIC;
 
 import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.CfCode;
 import com.android.tools.r8.graph.Code;
@@ -1793,10 +1794,11 @@
     private boolean foundIllegalAccess;
     private ProgramMethod context;
 
-    private final AppView<AppInfoWithLiveness> appView;
+    private final AppView<? extends AppInfoWithClassHierarchy> appView;
     private final DexClass source;
 
-    public IllegalAccessDetector(AppView<AppInfoWithLiveness> appView, DexClass source) {
+    public IllegalAccessDetector(
+        AppView<? extends AppInfoWithClassHierarchy> appView, DexClass source) {
       super(appView.dexItemFactory());
       this.appView = appView;
       this.source = source;
diff --git a/src/main/java/com/android/tools/r8/synthesis/CommittedSyntheticsCollection.java b/src/main/java/com/android/tools/r8/synthesis/CommittedSyntheticsCollection.java
index 7dbdec5..a201d6a 100644
--- a/src/main/java/com/android/tools/r8/synthesis/CommittedSyntheticsCollection.java
+++ b/src/main/java/com/android/tools/r8/synthesis/CommittedSyntheticsCollection.java
@@ -9,6 +9,7 @@
 import com.android.tools.r8.graph.PrunedItems;
 import com.android.tools.r8.utils.BooleanBox;
 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;
@@ -29,6 +30,7 @@
         null;
     private ImmutableMap.Builder<DexType, SyntheticMethodReference> newNonLegacyMethods = null;
     private ImmutableMap.Builder<DexType, LegacySyntheticReference> newLegacyClasses = null;
+    private ImmutableSet.Builder<DexType> newSyntheticInputs = null;
 
     public Builder(CommittedSyntheticsCollection parent) {
       this.parent = parent;
@@ -81,6 +83,11 @@
       return this;
     }
 
+    Builder addToSyntheticInputs() {
+      newSyntheticInputs = ImmutableSet.builder();
+      return this;
+    }
+
     public CommittedSyntheticsCollection build() {
       if (newNonLegacyClasses == null && newNonLegacyMethods == null && newLegacyClasses == null) {
         return parent;
@@ -97,13 +104,22 @@
           newLegacyClasses == null
               ? parent.legacyTypes
               : newLegacyClasses.putAll(parent.legacyTypes).build();
+      ImmutableSet<DexType> allSyntheticInputs =
+          newSyntheticInputs == null
+              ? parent.syntheticInputs
+              : newSyntheticInputs
+                  .addAll(allNonLegacyClasses.keySet())
+                  .addAll(allNonLegacyMethods.keySet())
+                  .addAll(allLegacyClasses.keySet())
+                  .build();
       return new CommittedSyntheticsCollection(
-          allLegacyClasses, allNonLegacyMethods, allNonLegacyClasses);
+          allLegacyClasses, allNonLegacyMethods, allNonLegacyClasses, allSyntheticInputs);
     }
   }
 
   private static final CommittedSyntheticsCollection EMPTY =
-      new CommittedSyntheticsCollection(ImmutableMap.of(), ImmutableMap.of(), ImmutableMap.of());
+      new CommittedSyntheticsCollection(
+          ImmutableMap.of(), ImmutableMap.of(), ImmutableMap.of(), ImmutableSet.of());
 
   /**
    * Immutable set of synthetic types in the application (eg, committed).
@@ -118,13 +134,18 @@
   /** Mapping from synthetic type to its synthetic class item description. */
   private final ImmutableMap<DexType, SyntheticProgramClassReference> nonLegacyClasses;
 
+  /** Set of synthetic types that were present in the input. */
+  private final ImmutableSet<DexType> syntheticInputs;
+
   public CommittedSyntheticsCollection(
       ImmutableMap<DexType, LegacySyntheticReference> legacyTypes,
       ImmutableMap<DexType, SyntheticMethodReference> nonLegacyMethods,
-      ImmutableMap<DexType, SyntheticProgramClassReference> nonLegacyClasses) {
+      ImmutableMap<DexType, SyntheticProgramClassReference> nonLegacyClasses,
+      ImmutableSet<DexType> syntheticInputs) {
     this.legacyTypes = legacyTypes;
     this.nonLegacyMethods = nonLegacyMethods;
     this.nonLegacyClasses = nonLegacyClasses;
+    this.syntheticInputs = syntheticInputs;
     assert legacyTypes.size() + nonLegacyMethods.size() + nonLegacyClasses.size()
         == Sets.union(
                 Sets.union(nonLegacyMethods.keySet(), nonLegacyClasses.keySet()),
@@ -140,8 +161,15 @@
     return new Builder(this);
   }
 
+  Builder builderForSyntheticInputs() {
+    return new Builder(this).addToSyntheticInputs();
+  }
+
   boolean isEmpty() {
-    return legacyTypes.isEmpty() && nonLegacyMethods.isEmpty() && nonLegacyClasses.isEmpty();
+    boolean empty =
+        legacyTypes.isEmpty() && nonLegacyMethods.isEmpty() && nonLegacyClasses.isEmpty();
+    assert !empty || syntheticInputs.isEmpty();
+    return empty;
   }
 
   boolean containsType(DexType type) {
@@ -156,6 +184,10 @@
     return nonLegacyMethods.containsKey(type) || nonLegacyClasses.containsKey(type);
   }
 
+  public boolean containsSyntheticInput(DexType type) {
+    return syntheticInputs.contains(type);
+  }
+
   public ImmutableMap<DexType, LegacySyntheticReference> getLegacyTypes() {
     return legacyTypes;
   }
@@ -176,6 +208,10 @@
     return nonLegacyClasses.get(type);
   }
 
+  public void forEachSyntheticInput(Consumer<DexType> fn) {
+    syntheticInputs.forEach(fn);
+  }
+
   public void forEachNonLegacyItem(Consumer<SyntheticReference<?, ?, ?>> fn) {
     nonLegacyMethods.forEach((t, r) -> fn.accept(r));
     nonLegacyClasses.forEach((t, r) -> fn.accept(r));
@@ -217,7 +253,16 @@
     return new CommittedSyntheticsCollection(
         rewriteItems(legacyTypes, lens),
         rewriteItems(nonLegacyMethods, lens),
-        rewriteItems(nonLegacyClasses, lens));
+        rewriteItems(nonLegacyClasses, lens),
+        rewriteItems(syntheticInputs, lens));
+  }
+
+  private static ImmutableSet<DexType> rewriteItems(Set<DexType> items, NonIdentityGraphLens lens) {
+    ImmutableSet.Builder<DexType> rewrittenItems = ImmutableSet.builder();
+    for (DexType item : items) {
+      rewrittenItems.add(lens.lookupType(item));
+    }
+    return rewrittenItems.build();
   }
 
   private static <R extends Rewritable<R>> ImmutableMap<DexType, R> rewriteItems(
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 8f883ea..5fa5498 100644
--- a/src/main/java/com/android/tools/r8/synthesis/SyntheticFinalization.java
+++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticFinalization.java
@@ -35,6 +35,7 @@
 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.Sets;
 import com.google.common.hash.HashCode;
 import java.util.ArrayList;
@@ -237,6 +238,12 @@
           }
         });
 
+    SyntheticFinalizationGraphLens syntheticFinalizationGraphLens = lensBuilder.build(appView);
+
+    ImmutableSet.Builder<DexType> finalInputSyntheticsBuilder = ImmutableSet.builder();
+    committed.forEachSyntheticInput(
+        type -> finalInputSyntheticsBuilder.add(syntheticFinalizationGraphLens.lookupType(type)));
+
     // TODO(b/181858113): Remove once deprecated main-dex-list is removed.
     MainDexInfo.Builder mainDexInfoBuilder = appView.appInfo().getMainDexInfo().builderFromCopy();
     derivedMainDexTypes.forEach(mainDexInfoBuilder::addList);
@@ -246,9 +253,12 @@
             SyntheticItems.INVALID_ID_AFTER_SYNTHETIC_FINALIZATION,
             application,
             new CommittedSyntheticsCollection(
-                committed.getLegacyTypes(), finalMethods, finalClasses),
+                committed.getLegacyTypes(),
+                finalMethods,
+                finalClasses,
+                finalInputSyntheticsBuilder.build()),
             ImmutableList.of()),
-        lensBuilder.build(appView),
+        syntheticFinalizationGraphLens,
         PrunedItems.builder().setPrunedApp(application).addRemovedClasses(prunedSynthetics).build(),
         mainDexInfoBuilder.build());
   }
diff --git a/src/main/java/com/android/tools/r8/synthesis/SyntheticItems.java b/src/main/java/com/android/tools/r8/synthesis/SyntheticItems.java
index d6c93aa..b90083a 100644
--- a/src/main/java/com/android/tools/r8/synthesis/SyntheticItems.java
+++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticItems.java
@@ -109,7 +109,7 @@
     this.committed = committed;
   }
 
-  public static void collectSyntheticInputs(AppView<AppInfo> appView) {
+  public static void collectSyntheticInputs(AppView<?> appView) {
     // Collecting synthetic items must be the very first task after application build.
     SyntheticItems synthetics = appView.getSyntheticItems();
     assert synthetics.nextSyntheticId == 0;
@@ -119,7 +119,8 @@
       // If the compilation is in intermediate mode the synthetics should just be passed through.
       return;
     }
-    CommittedSyntheticsCollection.Builder builder = synthetics.committed.builder();
+    CommittedSyntheticsCollection.Builder builder =
+        synthetics.committed.builderForSyntheticInputs();
     // TODO(b/158159959): Consider identifying synthetics in the input reader to speed this up.
     for (DexProgramClass clazz : appView.appInfo().classes()) {
       SyntheticMarker marker =
@@ -127,10 +128,9 @@
       if (marker.isSyntheticMethods()) {
         clazz.forEachProgramMethod(
             // TODO(b/158159959): Support having multiple methods per class.
-            method -> {
-              builder.addNonLegacyMethod(
-                  new SyntheticMethodDefinition(marker.getKind(), marker.getContext(), method));
-            });
+            method ->
+                builder.addNonLegacyMethod(
+                    new SyntheticMethodDefinition(marker.getKind(), marker.getContext(), method)));
       } else if (marker.isSyntheticClass()) {
         builder.addNonLegacyClass(
             new SyntheticProgramClassDefinition(marker.getKind(), marker.getContext(), clazz));
@@ -143,7 +143,15 @@
     CommittedItems commit =
         new CommittedItems(
             synthetics.nextSyntheticId, appView.appInfo().app(), committed, ImmutableList.of());
-    appView.setAppInfo(new AppInfo(commit, appView.appInfo().getMainDexInfo()));
+    if (appView.appInfo().hasClassHierarchy()) {
+      appView
+          .withClassHierarchy()
+          .setAppInfo(appView.appInfo().withClassHierarchy().rebuildWithClassHierarchy(commit));
+    } else {
+      appView
+          .withoutClassHierarchy()
+          .setAppInfo(new AppInfo(commit, appView.appInfo().getMainDexInfo()));
+    }
   }
 
   // Predicates and accessors.
@@ -239,6 +247,11 @@
     return null;
   }
 
+  public boolean isSubjectToKeepRules(DexProgramClass clazz) {
+    assert isSyntheticClass(clazz);
+    return committed.containsSyntheticInput(clazz.getType());
+  }
+
   public boolean isSyntheticClass(DexType type) {
     return isLegacySyntheticClass(type) || isNonLegacySynthetic(type);
   }
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 210ae9b..2cb1e9e 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -82,6 +82,7 @@
 import java.util.Set;
 import java.util.TreeSet;
 import java.util.function.BiConsumer;
+import java.util.function.BiFunction;
 import java.util.function.BiPredicate;
 import java.util.function.Consumer;
 import java.util.function.Function;
@@ -1268,6 +1269,8 @@
 
     public BiConsumer<DexItemFactory, HorizontallyMergedClasses> horizontallyMergedClassesConsumer =
         ConsumerUtils.emptyBiConsumer();
+    public BiFunction<Iterable<DexProgramClass>, DexProgramClass, DexProgramClass>
+        horizontalClassMergingTarget = (candidates, target) -> target;
 
     public BiConsumer<DexItemFactory, EnumDataMap> unboxedEnumsConsumer =
         ConsumerUtils.emptyBiConsumer();
@@ -1515,8 +1518,7 @@
   }
 
   public boolean canUseDexPcAsDebugInformation() {
-    // TODO(b/37830524): Enable for min-api 26 (OREO) and above.
-    return enablePcDebugInfoOutput;
+    return enablePcDebugInfoOutput && !debug && hasMinApi(AndroidApiLevel.O);
   }
 
   public boolean isInterfaceMethodDesugaringEnabled() {
diff --git a/src/main/java/com/android/tools/r8/utils/IterableUtils.java b/src/main/java/com/android/tools/r8/utils/IterableUtils.java
index 1354a33..edf9879 100644
--- a/src/main/java/com/android/tools/r8/utils/IterableUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/IterableUtils.java
@@ -51,6 +51,10 @@
     return defaultValue;
   }
 
+  public static <T> T first(Iterable<T> iterable) {
+    return iterable.iterator().next();
+  }
+
   public static <T> int firstIndexMatching(Iterable<T> iterable, Predicate<T> tester) {
     int i = 0;
     for (T element : iterable) {
diff --git a/src/test/java/com/android/tools/r8/D8TestRunResult.java b/src/test/java/com/android/tools/r8/D8TestRunResult.java
index c4427d1..4e54359 100644
--- a/src/test/java/com/android/tools/r8/D8TestRunResult.java
+++ b/src/test/java/com/android/tools/r8/D8TestRunResult.java
@@ -38,6 +38,6 @@
     if (proguardMap == null) {
       return super.getStackTrace();
     }
-    return super.getStackTrace().retrace(proguardMap);
+    return super.getStackTrace().retraceAllowExperimentalMapping(proguardMap);
   }
 }
diff --git a/src/test/java/com/android/tools/r8/KotlinTestParameters.java b/src/test/java/com/android/tools/r8/KotlinTestParameters.java
index 69a1cff..8c4d024 100644
--- a/src/test/java/com/android/tools/r8/KotlinTestParameters.java
+++ b/src/test/java/com/android/tools/r8/KotlinTestParameters.java
@@ -6,6 +6,7 @@
 import static org.junit.Assert.assertNotNull;
 
 import com.android.tools.r8.KotlinCompilerTool.KotlinCompiler;
+import com.android.tools.r8.KotlinCompilerTool.KotlinCompilerVersion;
 import com.android.tools.r8.ToolHelper.KotlinTargetVersion;
 import java.util.ArrayList;
 import java.util.List;
@@ -81,7 +82,12 @@
       int index = 0;
       for (KotlinCompiler kotlinc : compilers) {
         for (KotlinTargetVersion targetVersion : targetVersions) {
-          testParameters.add(new KotlinTestParameters(kotlinc, targetVersion, index++));
+          // KotlinTargetVersion java 6 is deprecated from kotlinc 1.5 and forward, no need to run
+          // tests on that target.
+          if (targetVersion != KotlinTargetVersion.JAVA_6
+              || kotlinc.isNot(KotlinCompilerVersion.KOTLINC_1_5_0_M2)) {
+            testParameters.add(new KotlinTestParameters(kotlinc, targetVersion, index++));
+          }
         }
       }
       return new KotlinTestParametersCollection(testParameters);
diff --git a/src/test/java/com/android/tools/r8/R8TestRunResult.java b/src/test/java/com/android/tools/r8/R8TestRunResult.java
index f35f686..3ef4901 100644
--- a/src/test/java/com/android/tools/r8/R8TestRunResult.java
+++ b/src/test/java/com/android/tools/r8/R8TestRunResult.java
@@ -49,7 +49,7 @@
 
   @Override
   public StackTrace getStackTrace() {
-    return super.getStackTrace().retrace(proguardMap);
+    return super.getStackTrace().retraceAllowExperimentalMapping(proguardMap);
   }
 
   public StackTrace getOriginalStackTrace() {
diff --git a/src/test/java/com/android/tools/r8/checkdiscarded/CheckDiscardModifyDiagnosticsLevelTest.java b/src/test/java/com/android/tools/r8/checkdiscarded/CheckDiscardModifyDiagnosticsLevelTest.java
index ffe8230..94b6238 100644
--- a/src/test/java/com/android/tools/r8/checkdiscarded/CheckDiscardModifyDiagnosticsLevelTest.java
+++ b/src/test/java/com/android/tools/r8/checkdiscarded/CheckDiscardModifyDiagnosticsLevelTest.java
@@ -78,7 +78,7 @@
   }
 
   @Test
-  public void dontFailCompilationOnCheckDiscardedFailure() {
+  public void dontFailCompilationIfCheckDiscardedFails() {
     try {
       testForR8(Backend.DEX)
           .addProgramClasses(
diff --git a/src/test/java/com/android/tools/r8/d8/IncompatiblePrimitiveTypesTest.java b/src/test/java/com/android/tools/r8/d8/IncompatiblePrimitiveTypesTest.java
index 1ecdaac..6117999 100644
--- a/src/test/java/com/android/tools/r8/d8/IncompatiblePrimitiveTypesTest.java
+++ b/src/test/java/com/android/tools/r8/d8/IncompatiblePrimitiveTypesTest.java
@@ -84,17 +84,13 @@
             .addProgramFiles(inputJar)
             .setMinApi(parameters.getApiLevel())
             .run(parameters.getRuntime(), "TestClass");
-    TestRunResult<?> dxResult =
-        testForDX().addProgramFiles(inputJar).run(parameters.getRuntime(), "TestClass");
     if (parameters.getRuntime().asDex().getVm().getVersion().isNewerThan(Version.V4_4_4)) {
       d8Result.assertSuccessWithOutput(expectedOutput);
-      dxResult.assertSuccessWithOutput(expectedOutput);
     } else {
       // TODO(b/119812046): On Art 4.0.4 and 4.4.4 it is a verification error to use one short type
-      // as another short type.
+      //  as another short type.
       Matcher<String> expectedError = containsString("java.lang.VerifyError");
       d8Result.assertFailureWithErrorThatMatches(expectedError);
-      dxResult.assertFailureWithErrorThatMatches(expectedError);
     }
   }
 }
diff --git a/src/test/java/com/android/tools/r8/debuginfo/EnsureNoDebugInfoEmittedForPcOnlyTestRunner.java b/src/test/java/com/android/tools/r8/debuginfo/EnsureNoDebugInfoEmittedForPcOnlyTestRunner.java
index 8c2ef82..270e7cd 100644
--- a/src/test/java/com/android/tools/r8/debuginfo/EnsureNoDebugInfoEmittedForPcOnlyTestRunner.java
+++ b/src/test/java/com/android/tools/r8/debuginfo/EnsureNoDebugInfoEmittedForPcOnlyTestRunner.java
@@ -4,29 +4,28 @@
 
 package com.android.tools.r8.debuginfo;
 
-import static com.android.tools.r8.Collectors.toSingle;
-import static com.android.tools.r8.utils.codeinspector.Matchers.isInlineFrame;
-import static com.android.tools.r8.utils.codeinspector.Matchers.isInlineStack;
-import static com.android.tools.r8.utils.codeinspector.Matchers.isTopOfStackTrace;
+import static com.android.tools.r8.naming.retrace.StackTrace.isSameExceptForFileNameAndLineNumber;
 import static junit.framework.TestCase.assertEquals;
 import static junit.framework.TestCase.assertNull;
 import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeTrue;
 
-import com.android.tools.r8.CompilationFailedException;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
-import com.android.tools.r8.ToolHelper.DexVm.Version;
-import com.android.tools.r8.references.Reference;
-import com.android.tools.r8.retrace.RetraceFrameResult;
+import com.android.tools.r8.graph.DexDebugEntry;
+import com.android.tools.r8.graph.DexDebugEntryBuilder;
+import com.android.tools.r8.naming.retrace.StackTrace;
+import com.android.tools.r8.naming.retrace.StackTrace.StackTraceLine;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
-import com.android.tools.r8.utils.codeinspector.InstructionSubject;
-import com.android.tools.r8.utils.codeinspector.Matchers.LinePosition;
 import com.android.tools.r8.utils.codeinspector.MethodSubject;
-import com.google.common.collect.ImmutableList;
-import java.io.IOException;
-import java.util.concurrent.ExecutionException;
+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;
@@ -43,76 +42,125 @@
 
   @Parameters(name = "{0}")
   public static TestParametersCollection data() {
-    return getTestParameters()
-        .withDexRuntimesStartingFromIncluding(Version.V8_1_0)
-        .withAllApiLevels()
-        .build();
+    return getTestParameters().withDexRuntimes().withAllApiLevels().build();
   }
 
   public EnsureNoDebugInfoEmittedForPcOnlyTestRunner(TestParameters parameters) {
     this.parameters = parameters;
   }
 
+  private boolean apiLevelSupportsPcOutput() {
+    return parameters.getApiLevel().isGreaterThanOrEqualTo(AndroidApiLevel.O);
+  }
+
+  // TODO(b/37830524): Remove when activated.
+  private void enablePcDebugInfoOutput(InternalOptions options) {
+    options.enablePcDebugInfoOutput = true;
+  }
+
   @Test
-  public void testNoEmittedDebugInfo()
-      throws ExecutionException, CompilationFailedException, IOException, NoSuchMethodException {
+  public void testD8Debug() throws Exception {
+    testForD8(parameters.getBackend())
+        .debug()
+        .addProgramClasses(MAIN)
+        .setMinApi(parameters.getApiLevel())
+        .internalEnableMappingOutput()
+        .addOptionsModification(this::enablePcDebugInfoOutput)
+        .run(parameters.getRuntime(), MAIN)
+        // For a debug build we always expect the output to have actual line information.
+        .inspectFailure(this::checkHasLineNumberInfo)
+        .inspectStackTrace(this::checkExpectedStackTrace);
+  }
+
+  @Test
+  public void testD8Release() throws Exception {
+    testForD8(parameters.getBackend())
+        .release()
+        .addProgramClasses(MAIN)
+        .setMinApi(parameters.getApiLevel())
+        .internalEnableMappingOutput()
+        .addOptionsModification(this::enablePcDebugInfoOutput)
+        .run(parameters.getRuntime(), MAIN)
+        .inspectFailure(
+            inspector -> {
+              if (apiLevelSupportsPcOutput()) {
+                checkNoDebugInfo(inspector, 5);
+              } else {
+                checkHasLineNumberInfo(inspector);
+              }
+            })
+        .inspectStackTrace(this::checkExpectedStackTrace);
+  }
+
+  @Test
+  public void testD8ReleaseWithoutMapOutput() throws Exception {
+    testForD8(parameters.getBackend())
+        .release()
+        .addProgramClasses(MAIN)
+        .setMinApi(parameters.getApiLevel())
+        .addOptionsModification(this::enablePcDebugInfoOutput)
+        .run(parameters.getRuntime(), MAIN)
+        // If compiling without a map output actual debug info should also be retained. Otherwise
+        // there would not be any way to obtain the actual lines.
+        .inspectFailure(this::checkHasLineNumberInfo)
+        .inspectStackTrace(this::checkExpectedStackTrace);
+  }
+
+  @Test
+  public void testNoEmittedDebugInfoR8() throws Exception {
+    assumeTrue(apiLevelSupportsPcOutput());
     testForR8(parameters.getBackend())
         .addProgramClasses(MAIN)
         .addKeepMainRule(MAIN)
         .addKeepAttributeLineNumberTable()
         .setMinApi(parameters.getApiLevel())
-        .addOptionsModification(
-            internalOptions -> {
-              // TODO(b/37830524): Remove when activated.
-              internalOptions.enablePcDebugInfoOutput = true;
-            })
+        .addOptionsModification(this::enablePcDebugInfoOutput)
         .run(parameters.getRuntime(), MAIN)
         .inspectOriginalStackTrace(
             (stackTrace, inspector) -> {
               assertEquals(MAIN.getTypeName(), stackTrace.get(0).className);
               assertEquals("main", stackTrace.get(0).methodName);
-              inspect(inspector);
+              checkNoDebugInfo(inspector, 1);
             })
-        .inspectStackTrace(
-            (stackTrace, codeInspector) -> {
-              MethodSubject mainSubject = codeInspector.clazz(MAIN).uniqueMethodWithName("main");
-              LinePosition inlineStack =
-                  LinePosition.stack(
-                      LinePosition.create(
-                          Reference.methodFromMethod(MAIN.getDeclaredMethod("a")),
-                          INLINED_DEX_PC,
-                          11,
-                          FILENAME_MAIN),
-                      LinePosition.create(
-                          Reference.methodFromMethod(MAIN.getDeclaredMethod("b")),
-                          INLINED_DEX_PC,
-                          18,
-                          FILENAME_MAIN),
-                      LinePosition.create(
-                          mainSubject.asFoundMethodSubject().asMethodReference(),
-                          INLINED_DEX_PC,
-                          23,
-                          FILENAME_MAIN));
-              RetraceFrameResult retraceResult =
-                  mainSubject
-                      .streamInstructions()
-                      .filter(InstructionSubject::isThrow)
-                      .collect(toSingle())
-                      .retracePcPosition(codeInspector.retrace(), mainSubject);
-              assertThat(retraceResult, isInlineFrame());
-              assertThat(retraceResult, isInlineStack(inlineStack));
-              assertThat(
-                  retraceResult,
-                  isTopOfStackTrace(
-                      stackTrace,
-                      ImmutableList.of(INLINED_DEX_PC, INLINED_DEX_PC, INLINED_DEX_PC)));
-            });
+        .inspectStackTrace(this::checkExpectedStackTrace);
   }
 
-  private void inspect(CodeInspector inspector) {
+  private void checkNoDebugInfo(CodeInspector inspector, int expectedMethodsInMain) {
     ClassSubject clazz = inspector.clazz(MAIN);
-    assertEquals(1, clazz.allMethods().size());
+    assertEquals(expectedMethodsInMain, clazz.allMethods().size());
     MethodSubject main = clazz.uniqueMethodWithName("main");
     assertNull(main.getMethod().getCode().asDexCode().getDebugInfo());
   }
+
+  private void checkHasLineNumberInfo(CodeInspector inspector) {
+    ClassSubject clazz = inspector.clazz(MAIN);
+    MethodSubject main = clazz.uniqueMethodWithName("main");
+    List<DexDebugEntry> entries =
+        new DexDebugEntryBuilder(main.getMethod(), inspector.getFactory()).build();
+    Set<Integer> lines = entries.stream().map(e -> e.line).collect(Collectors.toSet());
+    // Check some of the lines in main are present (not 27 as it may be optimized out).
+    assertTrue(lines.contains(22));
+    assertTrue(lines.contains(23));
+    assertTrue(lines.contains(25));
+  }
+
+  private void checkExpectedStackTrace(StackTrace stackTrace) {
+    assertThat(
+        stackTrace,
+        isSameExceptForFileNameAndLineNumber(
+            StackTrace.builder()
+                .add(line("a", 11))
+                .add(line("b", 18))
+                .add(line("main", 23))
+                .build()));
+  }
+
+  private StackTraceLine line(String method, int line) {
+    return StackTraceLine.builder()
+        .setClassName(MAIN.getTypeName())
+        .setMethodName(method)
+        .setLineNumber(line)
+        .setFileName(FILENAME_MAIN)
+        .build();
+  }
 }
diff --git a/src/test/java/com/android/tools/r8/desugar/backports/AbstractBackportTest.java b/src/test/java/com/android/tools/r8/desugar/backports/AbstractBackportTest.java
index 646dbf9..e4dd6d9 100644
--- a/src/test/java/com/android/tools/r8/desugar/backports/AbstractBackportTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/backports/AbstractBackportTest.java
@@ -17,6 +17,7 @@
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.android.tools.r8.utils.codeinspector.InstructionSubject;
 import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import com.google.common.collect.ImmutableList;
 import it.unimi.dsi.fastutil.ints.Int2IntAVLTreeMap;
 import it.unimi.dsi.fastutil.ints.Int2IntSortedMap;
 import java.io.IOException;
@@ -29,25 +30,71 @@
 
 abstract class AbstractBackportTest extends TestBase {
   protected final TestParameters parameters;
-  private final Class<?> targetClass;
-  private final Class<?> testClass;
+  private final ClassInfo targetClass;
+  private final ClassInfo testClass;
   private final Path testJar;
   private final String testClassName;
   private final Int2IntSortedMap invokeStaticCounts = new Int2IntAVLTreeMap();
   private final Set<String> ignoredInvokes = new HashSet<>();
 
+  private static class ClassInfo {
+    private final Class<?> clazz;
+    private final List<byte[]> classFileData;
+
+    private ClassInfo(Class<?> clazz) {
+      this.clazz = clazz;
+      this.classFileData = null;
+    }
+
+    private ClassInfo(byte[] classFileData) {
+      this.clazz = null;
+      this.classFileData = ImmutableList.of(classFileData);
+    }
+
+    private ClassInfo(List<byte[]> classFileData) {
+      this.clazz = null;
+      this.classFileData = classFileData;
+    }
+
+    String getName() {
+      return clazz != null ? clazz.getName() : extractClassName(classFileData.get(0));
+    }
+
+    TestBuilder<?, ?> addAsProgramClass(TestBuilder<?, ?> builder) throws IOException {
+      if (clazz != null) {
+        return builder.addProgramClassesAndInnerClasses(clazz);
+      } else {
+        return builder.addProgramClassFileData(classFileData);
+      }
+    }
+  }
+
   AbstractBackportTest(TestParameters parameters, Class<?> targetClass,
       Class<?> testClass) {
-    this(parameters, targetClass, testClass, null, null);
+    this(parameters, new ClassInfo(targetClass), new ClassInfo(testClass), null, null);
+  }
+
+  AbstractBackportTest(
+      TestParameters parameters, byte[] targetClassFileData, List<byte[]> testClassFileData) {
+    this(
+        parameters,
+        new ClassInfo(targetClassFileData),
+        new ClassInfo(testClassFileData),
+        null,
+        null);
   }
 
   AbstractBackportTest(TestParameters parameters, Class<?> targetClass,
       Path testJar, String testClassName) {
-    this(parameters, targetClass, null, testJar, testClassName);
+    this(parameters, new ClassInfo(targetClass), null, testJar, testClassName);
   }
 
-  private AbstractBackportTest(TestParameters parameters, Class<?> targetClass,
-      Class<?> testClass, Path testJar, String testClassName) {
+  private AbstractBackportTest(
+      TestParameters parameters,
+      ClassInfo targetClass,
+      ClassInfo testClass,
+      Path testJar,
+      String testClassName) {
     this.parameters = parameters;
     this.targetClass = targetClass;
     this.testClass = testClass;
@@ -83,7 +130,7 @@
   private void configureProgram(TestBuilder<?, ?> builder) throws IOException {
     builder.addProgramClasses(MiniAssert.class, IgnoreInvokes.class);
     if (testClass != null) {
-      builder.addProgramClassesAndInnerClasses(testClass);
+      testClass.addAsProgramClass(builder);
     } else {
       builder.addProgramFiles(testJar);
     }
@@ -188,5 +235,9 @@
             "Expected <" + expected + "> to be same instance as <" + actual + '>');
       }
     }
+
+    static void fail(String message) {
+      throw new AssertionError("Failed: " + message);
+    }
   }
 }
diff --git a/src/test/java/com/android/tools/r8/desugar/backports/SparseArrayBackportTest.java b/src/test/java/com/android/tools/r8/desugar/backports/SparseArrayBackportTest.java
new file mode 100644
index 0000000..61cf95d
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/backports/SparseArrayBackportTest.java
@@ -0,0 +1,78 @@
+// Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.desugar.backports;
+
+import com.android.tools.r8.TestParameters;
+import com.google.common.collect.ImmutableList;
+import java.io.IOException;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class SparseArrayBackportTest extends AbstractBackportTest {
+
+  private static final String SPARSE_ARRAY_DESCRIPTOR = "Landroid/util/SparseArray;";
+
+  @Parameters(name = "{0}")
+  public static Iterable<?> data() {
+    return getTestParameters().withDexRuntimes().withAllApiLevels().build();
+  }
+
+  public SparseArrayBackportTest(TestParameters parameters) throws IOException {
+    super(
+        parameters,
+        SparseArrayBackportTest.getSparseArray(),
+        ImmutableList.of(
+            SparseArrayBackportTest.getTestRunner(), SparseArrayBackportTest.getSparseArray()));
+
+    // The constructor is used by the test and put has been available since API 1 and is the
+    // method set is rewritten to.
+    ignoreInvokes("<init>");
+    ignoreInvokes("put");
+  }
+
+  private static byte[] getSparseArray() throws IOException {
+    return transformer(SparseArray.class).setClassDescriptor(SPARSE_ARRAY_DESCRIPTOR).transform();
+  }
+
+  private static byte[] getTestRunner() throws IOException {
+    return transformer(TestRunner.class)
+        .replaceClassDescriptorInMethodInstructions(
+            descriptor(SparseArray.class), SPARSE_ARRAY_DESCRIPTOR)
+        .transform();
+  }
+
+  public static class SparseArray {
+    public void set(int index, Object value) {
+      TestRunner.doFail("set should not be called");
+    }
+
+    public void put(int index, Object value) {
+      TestRunner.doAssertEquals(42, index);
+      TestRunner.doAssertEquals("Forty two", value);
+    }
+  }
+
+  public static class TestRunner extends MiniAssert {
+
+    public static void main(String[] args) {
+      new SparseArray().set(42, "Forty two");
+    }
+
+    // Forwards to MiniAssert to avoid having to make it public.
+    public static void doAssertEquals(int expected, int actual) {
+      MiniAssert.assertEquals(expected, actual);
+    }
+
+    public static void doAssertEquals(Object expected, Object actual) {
+      MiniAssert.assertEquals(expected, actual);
+    }
+
+    public static void doFail(String message) {
+      MiniAssert.fail(message);
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DesugaredLibraryCloneTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DesugaredLibraryCloneTest.java
new file mode 100644
index 0000000..73e5d67
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DesugaredLibraryCloneTest.java
@@ -0,0 +1,85 @@
+// 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.desugaredlibrary;
+
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.utils.BooleanUtils;
+import java.time.DayOfWeek;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class DesugaredLibraryCloneTest extends DesugaredLibraryTestBase {
+
+  private final TestParameters parameters;
+  private final boolean shrinkDesugaredLibrary;
+  private final String EXPECTED = "Just another manic MONDAY";
+
+  @Parameters(name = "{1}, shrinkDesugaredLibrary: {0}")
+  public static List<Object[]> data() {
+    return buildParameters(
+        getTestParameters().withDexRuntimes().withAllApiLevels().build(), BooleanUtils.values());
+  }
+
+  public DesugaredLibraryCloneTest(TestParameters parameters, boolean shrinkDesugaredLibrary) {
+    this.parameters = parameters;
+    this.shrinkDesugaredLibrary = shrinkDesugaredLibrary;
+  }
+
+  @Test
+  public void testD8() throws Exception {
+    KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters);
+    testForD8()
+        .addInnerClasses(getClass())
+        .setMinApi(parameters.getApiLevel())
+        .enableCoreLibraryDesugaring(parameters.getApiLevel(), keepRuleConsumer)
+        .compile()
+        .addDesugaredCoreLibraryRunClassPath(
+            this::buildDesugaredLibrary,
+            parameters.getApiLevel(),
+            keepRuleConsumer.get(),
+            shrinkDesugaredLibrary)
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines(EXPECTED);
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters);
+    testForR8(parameters.getBackend())
+        .addInnerClasses(getClass())
+        .addKeepMainRule(Main.class)
+        .setMinApi(parameters.getApiLevel())
+        .enableCoreLibraryDesugaring(parameters.getApiLevel(), keepRuleConsumer)
+        .compile()
+        .addDesugaredCoreLibraryRunClassPath(
+            this::buildDesugaredLibrary,
+            parameters.getApiLevel(),
+            keepRuleConsumer.get(),
+            shrinkDesugaredLibrary)
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines(EXPECTED);
+  }
+
+  public static class Main {
+
+    public static void main(String[] args) {
+      DayOfWeek[] dayOfWeeks = new DayOfWeek[args.length + 1];
+      if (args.length == 0) {
+        dayOfWeeks[0] = DayOfWeek.MONDAY;
+      }
+      print(dayOfWeeks.clone());
+    }
+
+    public static void print(DayOfWeek[] arr) {
+      if (arr.length > 0) {
+        System.out.println("Just another manic " + arr[0]);
+      }
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DesugaredLibraryDumpInputsTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DesugaredLibraryDumpInputsTest.java
index ea80848..03f861b 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DesugaredLibraryDumpInputsTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DesugaredLibraryDumpInputsTest.java
@@ -5,6 +5,7 @@
 
 import static org.hamcrest.CoreMatchers.containsString;
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 
 import com.android.tools.r8.TestParameters;
@@ -122,7 +123,17 @@
     assertTrue(Files.exists(unzipped.resolve("program.jar")));
     assertTrue(Files.exists(unzipped.resolve("library.jar")));
     assertTrue(Files.exists(unzipped.resolve("desugared-library.json")));
-    assertTrue(Files.exists(unzipped.resolve("build.properties")));
+    Path buildPropertiesPath = unzipped.resolve("build.properties");
+    assertTrue(Files.exists(buildPropertiesPath));
+    List<String> buildProperties = Files.readAllLines(buildPropertiesPath);
+    assertTrue(buildProperties.get(0).startsWith("tool="));
+    boolean isD8 = buildProperties.get(0).equals("tool=D8");
+    boolean isR8 = buildProperties.get(0).equals("tool=R8");
+    if ((shrinkDesugaredLibrary || isR8) && !isD8) {
+      assertTrue(Files.exists(unzipped.resolve("proguard.config")));
+    } else {
+      assertFalse(Files.exists(unzipped.resolve("proguard.config")));
+    }
   }
 
   static class TestClass {
diff --git a/src/test/java/com/android/tools/r8/dexsplitter/DexSplitterMergeRegression.java b/src/test/java/com/android/tools/r8/dexsplitter/DexSplitterMergeRegression.java
index aa298a7..1bfbaac 100644
--- a/src/test/java/com/android/tools/r8/dexsplitter/DexSplitterMergeRegression.java
+++ b/src/test/java/com/android/tools/r8/dexsplitter/DexSplitterMergeRegression.java
@@ -60,6 +60,13 @@
     Consumer<R8FullTestBuilder> configurator =
         r8FullTestBuilder ->
             r8FullTestBuilder
+                .addOptionsModification(
+                    options ->
+                        options.testing.horizontalClassMergingTarget =
+                            (candidates, target) -> candidates.iterator().next())
+                .addHorizontallyMergedClassesInspector(
+                    inspector ->
+                        inspector.assertMergedInto(BaseWithStatic.class, AFeatureWithStatic.class))
                 .enableNoVerticalClassMergingAnnotations()
                 .enableInliningAnnotations()
                 .noMinification();
@@ -127,7 +134,6 @@
     }
   }
 
-  // Name is important, see predicate in tests/
   public static class AFeatureWithStatic {
 
     @NeverInline
diff --git a/src/test/java/com/android/tools/r8/graph/GenericSignatureTest.java b/src/test/java/com/android/tools/r8/graph/GenericSignatureTest.java
index df62f93..86176a0 100644
--- a/src/test/java/com/android/tools/r8/graph/GenericSignatureTest.java
+++ b/src/test/java/com/android/tools/r8/graph/GenericSignatureTest.java
@@ -8,7 +8,6 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
 
 import com.android.tools.r8.D8TestCompileResult;
@@ -122,7 +121,7 @@
     assertEquals(1, classSignature.formalTypeParameters.size());
     FormalTypeParameter formalTypeParameter = classSignature.formalTypeParameters.get(0);
     assertEquals("T", formalTypeParameter.name);
-    assertNull(formalTypeParameter.interfaceBounds);
+    assertTrue(formalTypeParameter.interfaceBounds.isEmpty());
     assertTrue(formalTypeParameter.classBound.isClassTypeSignature());
     ClassTypeSignature classBoundSignature = formalTypeParameter.classBound.asClassTypeSignature();
     assertEquals(y.getDexProgramClass().type, classBoundSignature.type);
diff --git a/src/test/java/com/android/tools/r8/graph/genericsignature/ClassSignatureTest.java b/src/test/java/com/android/tools/r8/graph/genericsignature/ClassSignatureTest.java
index 239d924..12a819d 100644
--- a/src/test/java/com/android/tools/r8/graph/genericsignature/ClassSignatureTest.java
+++ b/src/test/java/com/android/tools/r8/graph/genericsignature/ClassSignatureTest.java
@@ -8,7 +8,9 @@
 import static com.google.common.base.Predicates.alwaysFalse;
 import static org.hamcrest.CoreMatchers.containsString;
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertThrows;
+import static org.junit.Assert.assertTrue;
 
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestDiagnosticMessages;
@@ -16,12 +18,15 @@
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.GenericSignature;
 import com.android.tools.r8.graph.GenericSignature.ClassSignature;
 import com.android.tools.r8.graph.GenericSignaturePrinter;
+import com.android.tools.r8.graph.GenericSignatureTypeRewriter;
 import com.android.tools.r8.naming.NamingLens;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.utils.Reporter;
+import java.util.function.Function;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
@@ -153,6 +158,39 @@
     assertThrows(AssertionError.class, () -> testParsingAndPrintingError("<>Lfoo/bar/baz<TT;>;"));
   }
 
+  @Test
+  public void testPruningInterfaceBound() {
+    DexItemFactory factory = new DexItemFactory();
+    DexType context = factory.createType("Lj$/util/stream/Node$OfPrimitive;");
+    String className = "j$.util.stream.Node$OfPrimitive";
+    TestDiagnosticMessagesImpl testDiagnosticMessages = new TestDiagnosticMessagesImpl();
+    ClassSignature parsedClassSignature =
+        GenericSignature.parseClassSignature(
+            className,
+            "<T_SPLITR::Lj$/util/Spliterator$OfPrimitive;T_NODE:Ljava/lang/Object;>"
+                + "Ljava/lang/Object;",
+            Origin.unknown(),
+            factory,
+            testDiagnosticMessages);
+    testDiagnosticMessages.assertNoMessages();
+    assertTrue(parsedClassSignature.hasSignature());
+    GenericSignatureTypeRewriter rewriter =
+        new GenericSignatureTypeRewriter(
+            factory,
+            dexType -> dexType.toDescriptorString().equals("Lj$/util/Spliterator$OfPrimitive;"),
+            Function.identity(),
+            context);
+    ClassSignature rewritten = rewriter.rewrite(parsedClassSignature);
+    assertNotNull(rewritten);
+    assertTrue(rewritten.hasSignature());
+    ClassSignature reparsed =
+        GenericSignature.parseClassSignature(
+            className, rewritten.toString(), Origin.unknown(), factory, testDiagnosticMessages);
+    assertTrue(reparsed.hasSignature());
+    testDiagnosticMessages.assertNoMessages();
+    assertEquals(rewritten.toString(), reparsed.toString());
+  }
+
   private void testParsingAndPrintingEqual(String signature) {
     ClassSignature parsed =
         GenericSignature.parseClassSignature(
diff --git a/src/test/java/com/android/tools/r8/graph/genericsignature/GenericSignatureKeepReferencesPruneTest.java b/src/test/java/com/android/tools/r8/graph/genericsignature/GenericSignatureKeepReferencesPruneTest.java
new file mode 100644
index 0000000..1c3ddbb
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/graph/genericsignature/GenericSignatureKeepReferencesPruneTest.java
@@ -0,0 +1,112 @@
+// Copyright (c) 2021, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.graph.genericsignature;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.graph.genericsignature.testclasses.Foo;
+import com.android.tools.r8.graph.genericsignature.testclasses.I;
+import com.android.tools.r8.graph.genericsignature.testclasses.J;
+import com.android.tools.r8.graph.genericsignature.testclasses.K;
+import com.android.tools.r8.graph.genericsignature.testclasses.L;
+import com.android.tools.r8.graph.genericsignature.testclasses.Main;
+import com.android.tools.r8.utils.BooleanUtils;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class GenericSignatureKeepReferencesPruneTest extends TestBase {
+
+  private final String[] EXPECTED =
+      new String[] {
+        Foo.class.getTypeName() + "<java.lang.String>",
+        I.class.getTypeName()
+            + "<java.lang.Integer, "
+            + Foo.class.getTypeName()
+            + "<java.lang.Integer>>",
+        I.class.getTypeName()
+            + "<java.lang.String, "
+            + Foo.class.getTypeName()
+            + "<java.lang.String>>",
+        "Hello world"
+      };
+
+  private final TestParameters parameters;
+  private final boolean isCompat;
+
+  @Parameters(name = "{0}, isCompat: {1}")
+  public static List<Object[]> data() {
+    return buildParameters(
+        getTestParameters().withAllRuntimesAndApiLevels().build(), BooleanUtils.values());
+  }
+
+  public GenericSignatureKeepReferencesPruneTest(TestParameters parameters, boolean isCompat) {
+    this.parameters = parameters;
+    this.isCompat = isCompat;
+  }
+
+  @Test
+  public void testJvm() throws Exception {
+    assumeTrue(parameters.isCfRuntime());
+    testForJvm()
+        .addProgramClasses(I.class, Foo.class, J.class, K.class, L.class)
+        .addProgramClassesAndInnerClasses(Main.class)
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines(EXPECTED);
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    (isCompat ? testForR8Compat(parameters.getBackend()) : testForR8(parameters.getBackend()))
+        .addProgramClasses(I.class, Foo.class, J.class, K.class, L.class)
+        .addProgramClassesAndInnerClasses(Main.class)
+        .addKeepClassAndMembersRules(Main.class)
+        .setMinApi(parameters.getApiLevel())
+        .addKeepAttributeSignature()
+        .addKeepAttributeInnerClassesAndEnclosingMethod()
+        .enableInliningAnnotations()
+        .enableNeverClassInliningAnnotations()
+        .noMinification()
+        .run(parameters.getRuntime(), Main.class)
+        // TODO(b/184927364): Should have different output due to pruning the inner class.
+        .assertSuccessWithOutputLines(EXPECTED)
+        .inspect(this::inspectSignatures);
+  }
+
+  private void inspectSignatures(CodeInspector inspector) {
+    ClassSubject fooClass = inspector.clazz(Foo.class);
+    assertThat(fooClass, isPresent());
+    // TODO(b/184927364): Fullmode should not keep the interface bound.
+    assertEquals(
+        "<T::Ljava/lang/Comparable<TT;>;>Ljava/lang/Object;",
+        fooClass.getFinalSignatureAttribute());
+    ClassSubject iClass = inspector.clazz(I.class);
+    assertThat(iClass, isPresent());
+    // TODO(b/184927364): Fullmode should not keep the interface and class bound.
+    assertEquals(
+        "<T::Ljava/lang/Comparable<TT;>;R:L" + binaryName(Foo.class) + "<TT;>;>Ljava/lang/Object;",
+        iClass.getFinalSignatureAttribute());
+    ClassSubject fooInnerClass = inspector.clazz(Main.class.getTypeName() + "$1");
+    assertThat(fooInnerClass, isPresent());
+    // TODO(b/184927364): Fullmode should completely remove this signature
+    assertEquals(
+        "Ljava/lang/Object;L"
+            + binaryName(I.class)
+            + "<Ljava/lang/String;L"
+            + binaryName(Foo.class)
+            + "<Ljava/lang/String;>;>;",
+        fooInnerClass.getFinalSignatureAttribute());
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/graph/genericsignature/GenericSignaturePrunedOuterTest.java b/src/test/java/com/android/tools/r8/graph/genericsignature/GenericSignaturePrunedOuterTest.java
new file mode 100644
index 0000000..608b8d3
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/graph/genericsignature/GenericSignaturePrunedOuterTest.java
@@ -0,0 +1,125 @@
+// Copyright (c) 2021, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.graph.genericsignature;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.utils.BooleanUtils;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class GenericSignaturePrunedOuterTest extends TestBase {
+
+  private final TestParameters parameters;
+  private final boolean isCompat;
+
+  @Parameters(name = "{0}, isCompat: {1}")
+  public static List<Object[]> data() {
+    return buildParameters(
+        getTestParameters().withAllRuntimesAndApiLevels().build(), BooleanUtils.values());
+  }
+
+  public GenericSignaturePrunedOuterTest(TestParameters parameters, boolean isCompat) {
+    this.parameters = parameters;
+    this.isCompat = isCompat;
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    (isCompat ? testForR8Compat(parameters.getBackend()) : testForR8(parameters.getBackend()))
+        .addInnerClasses(getClass())
+        .addKeepClassAndMembersRules(Foo.class)
+        .addKeepMainRule(Main.class)
+        .addKeepAttributeSignature()
+        .addKeepAttributeInnerClassesAndEnclosingMethod()
+        .setMinApi(parameters.getApiLevel())
+        .addOptionsModification(options -> options.horizontalClassMergerOptions().disable())
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines(
+            "Bar::enclosingMethod", "Hello World", "Bar::enclosingMethod2", "Hello World")
+        .inspect(this::checkSignatures);
+  }
+
+  public void checkSignatures(CodeInspector inspector) {
+    checkSignature(
+        inspector.clazz(Bar.class.getTypeName() + "$1"),
+        "L"
+            + binaryName(Foo.class)
+            + "<"
+            + descriptor(Object.class)
+            + descriptor(Main.class)
+            + ">;");
+    checkSignature(
+        inspector.clazz(Bar.class.getTypeName() + "$2"),
+        "L"
+            + binaryName(Foo.class)
+            + "<"
+            + descriptor(Object.class)
+            + descriptor(Object.class)
+            + ">;");
+  }
+
+  private void checkSignature(ClassSubject classSubject, String expectedSignature) {
+    assertThat(classSubject, isPresent());
+    // TODO(b/185098797): Make sure to work for full mode.
+    if (!isCompat) {
+      return;
+    }
+    assertEquals(expectedSignature, classSubject.getFinalSignatureAttribute());
+  }
+
+  public abstract static class Foo<T, R> {
+
+    R foo(T r) {
+      System.out.println("Hello World");
+      return null;
+    }
+  }
+
+  public static class Bar {
+
+    public static <T, R extends Main> Foo<T, R> enclosingMethod() {
+      return new Foo<T, R>() {
+        @Override
+        R foo(T r) {
+          System.out.println("Bar::enclosingMethod");
+          return super.foo(r);
+        }
+      };
+    }
+
+    public static <T, R> Foo<T, R> enclosingMethod2() {
+      return new Foo<T, R>() {
+        @Override
+        R foo(T r) {
+          System.out.println("Bar::enclosingMethod2");
+          return super.foo(r);
+        }
+      };
+    }
+
+    public static void run() {
+      enclosingMethod().foo(null);
+      enclosingMethod2().foo(null);
+    }
+  }
+
+  public static class Main {
+
+    public static void main(String[] args) {
+      Bar.run();
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/graph/genericsignature/testclasses/Foo.java b/src/test/java/com/android/tools/r8/graph/genericsignature/testclasses/Foo.java
new file mode 100644
index 0000000..81000d6
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/graph/genericsignature/testclasses/Foo.java
@@ -0,0 +1,19 @@
+// Copyright (c) 2021, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.graph.genericsignature.testclasses;
+
+import com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.NeverInline;
+
+@NeverClassInline
+public class Foo<T extends Comparable<T>> implements J, K<T> {
+
+  @Override
+  @NeverInline
+  public String bar(String t) {
+    System.out.println("Foo::bar");
+    return t;
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/graph/genericsignature/testclasses/I.java b/src/test/java/com/android/tools/r8/graph/genericsignature/testclasses/I.java
new file mode 100644
index 0000000..089286d
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/graph/genericsignature/testclasses/I.java
@@ -0,0 +1,12 @@
+// Copyright (c) 2021, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.graph.genericsignature.testclasses;
+
+import com.android.tools.r8.NeverInline;
+
+public interface I<T extends Comparable<T>, R extends Foo<T>> extends L<R> {
+  @NeverInline
+  T method(T t);
+}
diff --git a/src/test/java/com/android/tools/r8/graph/genericsignature/testclasses/J.java b/src/test/java/com/android/tools/r8/graph/genericsignature/testclasses/J.java
new file mode 100644
index 0000000..8deed10
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/graph/genericsignature/testclasses/J.java
@@ -0,0 +1,10 @@
+// Copyright (c) 2021, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.graph.genericsignature.testclasses;
+
+public interface J {
+
+  String bar(String t);
+}
diff --git a/src/test/java/com/android/tools/r8/graph/genericsignature/testclasses/K.java b/src/test/java/com/android/tools/r8/graph/genericsignature/testclasses/K.java
new file mode 100644
index 0000000..608c12d
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/graph/genericsignature/testclasses/K.java
@@ -0,0 +1,7 @@
+// Copyright (c) 2021, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.graph.genericsignature.testclasses;
+
+public interface K<T> {}
diff --git a/src/test/java/com/android/tools/r8/graph/genericsignature/testclasses/L.java b/src/test/java/com/android/tools/r8/graph/genericsignature/testclasses/L.java
new file mode 100644
index 0000000..bf3895b
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/graph/genericsignature/testclasses/L.java
@@ -0,0 +1,7 @@
+// Copyright (c) 2021, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.graph.genericsignature.testclasses;
+
+public interface L<T> {}
diff --git a/src/test/java/com/android/tools/r8/graph/genericsignature/testclasses/Main.java b/src/test/java/com/android/tools/r8/graph/genericsignature/testclasses/Main.java
new file mode 100644
index 0000000..ccf9f04
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/graph/genericsignature/testclasses/Main.java
@@ -0,0 +1,39 @@
+// Copyright (c) 2021, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.graph.genericsignature.testclasses;
+
+import java.lang.reflect.Type;
+
+public class Main extends Foo<String> implements I<Integer, Foo<Integer>> {
+
+  public static <T extends I<String, Foo<String>>> T test(T t) {
+    for (Type genericInterface : t.getClass().getGenericInterfaces()) {
+      System.out.println(genericInterface);
+    }
+    t.method("Hello world");
+    return t;
+  }
+
+  public static void main(String[] args) {
+    System.out.println(Main.class.getGenericSuperclass());
+    for (Type genericInterface : Main.class.getGenericInterfaces()) {
+      System.out.println(genericInterface);
+    }
+    test(
+        new I<String, Foo<String>>() {
+          @Override
+          public String method(String s) {
+            System.out.println(s);
+            return s;
+          }
+        });
+  }
+
+  @Override
+  public Integer method(Integer integer) {
+    System.out.println("Main::method");
+    return integer;
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/NonNullParamTest.java b/src/test/java/com/android/tools/r8/ir/optimize/NonNullParamTest.java
index 9ba1f75..f1f19ff 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/NonNullParamTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/NonNullParamTest.java
@@ -222,12 +222,21 @@
                 mainClass),
             builder ->
                 builder
+                    .addHorizontallyMergedClassesInspector(
+                        horizontallyMergedClassesInspector ->
+                            horizontallyMergedClassesInspector
+                                .assertIsCompleteMergeGroup(
+                                    NonNullParamInterfaceImpl.class,
+                                    NonNullParamAfterInvokeInterface.class)
+                                .assertMergedInto(
+                                    NonNullParamAfterInvokeInterface.class,
+                                    NonNullParamInterfaceImpl.class))
                     .addOptionsModification(this::disableDevirtualization)
                     .enableInliningAnnotations()
                     .enableNeverClassInliningAnnotations()
                     .enableNoVerticalClassMergingAnnotations());
 
-    ClassSubject mainSubject = inspector.clazz(NonNullParamAfterInvokeInterface.class);
+    ClassSubject mainSubject = inspector.clazz(NonNullParamInterfaceImpl.class);
     assertThat(mainSubject, isPresent());
 
     MethodSubject checkViaCall = mainSubject.uniqueMethodWithName("checkViaCall");
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..9cb52da 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
@@ -91,8 +91,10 @@
             .addHorizontallyMergedClassesInspector(
                 inspector ->
                     inspector
-                        .assertMergedInto(Iface1Impl.class, CycleReferenceBA.class)
-                        .assertMergedInto(Iface2Impl.class, CycleReferenceBA.class))
+                        .assertIsCompleteMergeGroup(
+                            Iface1Impl.class, Iface2Impl.class, CycleReferenceBA.class)
+                        .assertMergedInto(CycleReferenceBA.class, Iface1Impl.class)
+                        .assertMergedInto(Iface2Impl.class, Iface1Impl.class))
             .allowAccessModification()
             .noMinification()
             .setMinApi(parameters.getApiLevel())
diff --git a/src/test/java/com/android/tools/r8/kotlin/AbstractR8KotlinTestBase.java b/src/test/java/com/android/tools/r8/kotlin/AbstractR8KotlinTestBase.java
index a4b4d35..495eae6 100644
--- a/src/test/java/com/android/tools/r8/kotlin/AbstractR8KotlinTestBase.java
+++ b/src/test/java/com/android/tools/r8/kotlin/AbstractR8KotlinTestBase.java
@@ -53,12 +53,6 @@
 
   protected final TestParameters testParameters;
 
-  // Some tests defined in subclasses, e.g., Metadata tests, don't care about access relaxation.
-  protected AbstractR8KotlinTestBase(
-      TestParameters parameters, KotlinTestParameters kotlinParameters) {
-    this(parameters, kotlinParameters, false);
-  }
-
   protected AbstractR8KotlinTestBase(
       TestParameters parameters,
       KotlinTestParameters kotlinParameters,
diff --git a/src/test/java/com/android/tools/r8/kotlin/KotlinClassInlinerTest.java b/src/test/java/com/android/tools/r8/kotlin/KotlinClassInlinerTest.java
index 65e33d1..c8af414 100644
--- a/src/test/java/com/android/tools/r8/kotlin/KotlinClassInlinerTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/KotlinClassInlinerTest.java
@@ -71,6 +71,8 @@
                 testBuilder
                     // TODO(jsjeon): Introduce @NeverInline to kotlinR8TestResources
                     .addKeepRules("-neverinline class * { void test*State*(...); }")
+                    .addNoHorizontalClassMergingRule(
+                        "class_inliner_lambda_j_style.SamIface$Consumer")
                     .addHorizontallyMergedClassesInspector(
                         inspector ->
                             inspector
@@ -102,7 +104,9 @@
             testBuilder ->
                 testBuilder
                     // TODO(jsjeon): Introduce @NeverInline to kotlinR8TestResources
-                    .addKeepRules("-neverinline class * { void test*State*(...); }"))
+                    .addKeepRules("-neverinline class * { void test*State*(...); }")
+                    .addNoHorizontalClassMergingRule(
+                        "class_inliner_lambda_j_style.SamIface$Consumer"))
         .inspect(
             inspector -> {
               // TODO(b/173337498): MainKt$testStateless$1 should always be class inlined.
diff --git a/src/test/java/com/android/tools/r8/kotlin/SimplifyIfNotNullKotlinTest.java b/src/test/java/com/android/tools/r8/kotlin/SimplifyIfNotNullKotlinTest.java
index 00c9978..4769427 100644
--- a/src/test/java/com/android/tools/r8/kotlin/SimplifyIfNotNullKotlinTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/SimplifyIfNotNullKotlinTest.java
@@ -48,7 +48,10 @@
 
     final String mainClassName = ex1.getClassName();
     final String extraRules = neverInlineMethod(mainClassName, testMethodSignature);
-    runTest(FOLDER, mainClassName, testBuilder -> testBuilder.addKeepRules(extraRules))
+    runTest(
+            FOLDER,
+            mainClassName,
+            testBuilder -> testBuilder.addKeepRules(extraRules).allowAccessModification())
         .inspect(
             inspector -> {
               ClassSubject clazz = checkClassIsKept(inspector, ex1.getClassName());
diff --git a/src/test/java/com/android/tools/r8/naming/MapReaderVersionTest.java b/src/test/java/com/android/tools/r8/naming/MapReaderVersionTest.java
index c678a70..5a4db90 100644
--- a/src/test/java/com/android/tools/r8/naming/MapReaderVersionTest.java
+++ b/src/test/java/com/android/tools/r8/naming/MapReaderVersionTest.java
@@ -5,12 +5,14 @@
 
 import static junit.framework.TestCase.assertEquals;
 
+import com.android.tools.r8.DiagnosticsHandler;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestDiagnosticMessagesImpl;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.naming.mappinginformation.MappingInformation;
 import com.android.tools.r8.utils.StringUtils;
+import com.google.common.io.CharSource;
 import java.io.IOException;
 import org.junit.Assert;
 import org.junit.Test;
@@ -30,23 +32,33 @@
     parameters.assertNoneRuntime();
   }
 
+  private static ClassNameMapper read(DiagnosticsHandler diagnosticsHandler, String... lines)
+      throws IOException {
+    return ClassNameMapper.mapperFromBufferedReader(
+        CharSource.wrap(StringUtils.joinLines(lines)).openBufferedStream(),
+        diagnosticsHandler,
+        false,
+        true);
+  }
+
+  private static ClassNameMapper read(String... lines) throws IOException {
+    return read(null, lines);
+  }
+
   @Test
   public void testNoVersion() throws IOException {
     ClassNameMapper mapper =
-        ClassNameMapper.mapperFromString(
-            StringUtils.joinLines(
-                "pkg.Foo -> a.a:", "# { id: \"com.android.tools.r8.synthesized\" }"));
+        read("pkg.Foo -> a.a:", "# { id: \"com.android.tools.r8.synthesized\" }");
     assertMapping("a.a", "pkg.Foo", false, mapper);
   }
 
   @Test
   public void testExperimentalVersion() throws IOException {
     ClassNameMapper mapper =
-        ClassNameMapper.mapperFromString(
-            StringUtils.joinLines(
-                "# { id: 'com.android.tools.r8.metainf', map-version: 'experimental' }",
-                "pkg.Foo -> a.a:",
-                "# { id: 'com.android.tools.r8.synthesized' }"));
+        read(
+            "# { id: 'com.android.tools.r8.mapping', version: 'experimental' }",
+            "pkg.Foo -> a.a:",
+            "# { id: 'com.android.tools.r8.synthesized' }");
     assertMapping("a.a", "pkg.Foo", true, mapper);
   }
 
@@ -54,21 +66,20 @@
   public void testConcatMapFiles() throws IOException {
     TestDiagnosticMessagesImpl diagnostics = new TestDiagnosticMessagesImpl();
     ClassNameMapper mapper =
-        ClassNameMapper.mapperFromString(
-            StringUtils.joinLines(
-                // Default map-version is none.
-                "pkg.Foo -> a.a:",
-                "# { id: 'com.android.tools.r8.synthesized' }",
-                // Section with map-version experimental.
-                "# { id: 'com.android.tools.r8.metainf', map-version: 'experimental' }",
-                "pkg.Bar -> a.b:",
-                "# { id: 'com.android.tools.r8.synthesized' }",
-                // Section reverting map-version back to none (to support tooling that
-                // concatenates).
-                "# { id: 'com.android.tools.r8.metainf', map-version: 'none' }",
-                "pkg.Baz -> a.c:",
-                "# { id: 'com.android.tools.r8.synthesized' }"),
-            diagnostics);
+        read(
+            diagnostics,
+            // Default map-version is none.
+            "pkg.Foo -> a.a:",
+            "# { id: 'com.android.tools.r8.synthesized' }",
+            // Section with map-version experimental.
+            "# { id: 'com.android.tools.r8.mapping', version: 'experimental' }",
+            "pkg.Bar -> a.b:",
+            "# { id: 'com.android.tools.r8.synthesized' }",
+            // Section reverting map-version back to none (to support tooling that
+            // concatenates).
+            "# { id: 'com.android.tools.r8.mapping', version: 'none' }",
+            "pkg.Baz -> a.c:",
+            "# { id: 'com.android.tools.r8.synthesized' }");
     diagnostics.assertNoMessages();
     assertMapping("a.a", "pkg.Foo", false, mapper);
     assertMapping("a.b", "pkg.Bar", true, mapper);
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 f59b75d..1fbc7cf 100644
--- a/src/test/java/com/android/tools/r8/naming/retrace/DesugarLambdaRetraceTest.java
+++ b/src/test/java/com/android/tools/r8/naming/retrace/DesugarLambdaRetraceTest.java
@@ -4,7 +4,6 @@
 
 package com.android.tools.r8.naming.retrace;
 
-import static com.android.tools.r8.ir.desugar.LambdaClass.R8_LAMBDA_ACCESSOR_METHOD_PREFIX;
 import static com.android.tools.r8.naming.retrace.StackTrace.isSame;
 import static com.android.tools.r8.naming.retrace.StackTrace.isSameExceptForFileName;
 import static com.android.tools.r8.naming.retrace.StackTrace.isSameExceptForFileNameAndLineNumber;
@@ -13,10 +12,8 @@
 import static org.junit.Assume.assumeTrue;
 
 import com.android.tools.r8.CompilationMode;
+import com.android.tools.r8.R8TestBuilder;
 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,73 +56,32 @@
     return mode == CompilationMode.RELEASE ? 2 : 4;
   }
 
-  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))
-        || line.methodName.startsWith(R8_LAMBDA_ACCESSOR_METHOD_PREFIX);
-  }
-
-  private void checkLambdaFrames(StackTrace retracedStackTrace) {
-    StackTrace lambdaFrames = retracedStackTrace.filter(this::isSynthesizedLambdaFrame);
-    assertEquals(2, lambdaFrames.size());
-
-    StackTraceLine syntheticLambdaClassFrame = lambdaFrames.get(1);
-    if (syntheticLambdaClassFrame.hasLineNumber()) {
-      assertEquals(mode == CompilationMode.RELEASE ? 0 : 2, syntheticLambdaClassFrame.lineNumber);
-    }
-    // Proguard retrace will take the class name until the first $ to construct the file
-    // name, so for "-$$Lambda$...", the file name becomes "-.java".
-    // TODO(b/141287349): Format the class name of desugared lambda classes.
-    // assertEquals("-.java", syntheticLambdaClassFrame.fileName);
-  }
-
   private void checkIsSame(StackTrace actualStackTrace, StackTrace retracedStackTrace) {
     // Even when SourceFile is present retrace replaces the file name in the stack trace.
-    if (parameters.isCfRuntime()) {
-      assertThat(retracedStackTrace, isSame(expectedStackTrace));
-    } else {
-      // With the frame from the lambda class filtered out the stack trace is the same.
-      assertThat(
-          retracedStackTrace.filter(line -> !isSynthesizedLambdaFrame(line)),
-          isSame(expectedStackTrace));
-      // Check the frame from the lambda class.
-      checkLambdaFrames(retracedStackTrace);
-    }
+    assertThat(retracedStackTrace, isSame(expectedStackTrace));
     assertEquals(expectedActualStackTraceHeight(), actualStackTrace.size());
   }
 
   private void checkIsSameExceptForFileName(
       StackTrace actualStackTrace, StackTrace retracedStackTrace) {
     // Even when SourceFile is present retrace replaces the file name in the stack trace.
-    if (parameters.isCfRuntime()) {
-      assertThat(retracedStackTrace, isSameExceptForFileName(expectedStackTrace));
-    } else {
-      // With the frames from the lambda class filtered out the stack trace is the same.
-      assertThat(
-          retracedStackTrace.filter(line -> !isSynthesizedLambdaFrame(line)),
-          isSameExceptForFileName(expectedStackTrace));
-      // Check the frame from the lambda class.
-      checkLambdaFrames(retracedStackTrace);
-    }
+    assertThat(retracedStackTrace, isSameExceptForFileName(expectedStackTrace));
     assertEquals(expectedActualStackTraceHeight(), actualStackTrace.size());
   }
 
   private void checkIsSameExceptForFileNameAndLineNumber(
       StackTrace actualStackTrace, StackTrace retracedStackTrace) {
     // Even when SourceFile is present retrace replaces the file name in the stack trace.
-    if (parameters.isCfRuntime()) {
-      assertThat(retracedStackTrace, isSameExceptForFileNameAndLineNumber(expectedStackTrace));
-    } else {
-      // With the frame from the lambda class filtered out the stack trace is the same.
-      assertThat(
-          retracedStackTrace.filter(line -> !isSynthesizedLambdaFrame(line)),
-          isSameExceptForFileNameAndLineNumber(expectedStackTrace));
-      // Check the frame from the lambda class.
-      checkLambdaFrames(retracedStackTrace);
-    }
+    assertThat(retracedStackTrace, isSameExceptForFileNameAndLineNumber(expectedStackTrace));
     assertEquals(expectedActualStackTraceHeight(), actualStackTrace.size());
   }
 
+  @Override
+  public void configure(R8TestBuilder<?> builder) {
+    // Enable pruning of lambda synthetics in retrace.
+    builder.enableExperimentalMapFileVersion();
+  }
+
   @Test
   public void testSourceFileAndLineNumberTable() throws Exception {
     runTest(ImmutableList.of("-keepattributes SourceFile,LineNumberTable"), this::checkIsSame);
diff --git a/src/test/java/com/android/tools/r8/naming/retrace/StackTrace.java b/src/test/java/com/android/tools/r8/naming/retrace/StackTrace.java
index 74eeed2..0cc94d7 100644
--- a/src/test/java/com/android/tools/r8/naming/retrace/StackTrace.java
+++ b/src/test/java/com/android/tools/r8/naming/retrace/StackTrace.java
@@ -12,8 +12,8 @@
 import com.android.tools.r8.ToolHelper.DexVm;
 import com.android.tools.r8.references.ClassReference;
 import com.android.tools.r8.retrace.ProguardMapProducer;
-import com.android.tools.r8.retrace.Retrace;
 import com.android.tools.r8.retrace.RetraceCommand;
+import com.android.tools.r8.retrace.RetraceHelper;
 import com.android.tools.r8.utils.StringUtils;
 import com.google.common.base.Equivalence;
 import java.util.ArrayList;
@@ -227,7 +227,6 @@
   private final String originalStderr;
 
   private StackTrace(List<StackTraceLine> stackTraceLines, String originalStderr) {
-    assert stackTraceLines.size() > 0;
     this.stackTraceLines = stackTraceLines;
     this.originalStderr = originalStderr;
   }
@@ -301,16 +300,25 @@
     return extractFromJvm(result.getStdErr());
   }
 
+  public StackTrace retraceAllowExperimentalMapping(String map) {
+    return retrace(map, null, true);
+  }
+
   public StackTrace retrace(String map) {
-    return retrace(map, null);
+    return retrace(map, null, true);
   }
 
   public StackTrace retrace(String map, String regularExpression) {
+    return retrace(map, regularExpression, true);
+  }
+
+  public StackTrace retrace(
+      String map, String regularExpression, boolean allowExperimentalMapping) {
     class Box {
       List<String> result;
     }
     Box box = new Box();
-    Retrace.run(
+    RetraceHelper.runForTesting(
         RetraceCommand.builder()
             .setProguardMapProducer(ProguardMapProducer.fromString(map))
             .setStackTrace(
@@ -319,7 +327,8 @@
                     .collect(Collectors.toList()))
             .setRegularExpression(regularExpression)
             .setRetracedStackTraceConsumer(retraced -> box.result = retraced)
-            .build());
+            .build(),
+        allowExperimentalMapping);
     // Keep the original stderr in the retraced stacktrace.
     return new StackTrace(internalExtractFromJvm(StringUtils.lines(box.result)), originalStderr);
   }
diff --git a/src/test/java/com/android/tools/r8/retrace/RetraceLambdaTest.java b/src/test/java/com/android/tools/r8/retrace/RetraceLambdaTest.java
index 38f7725..f7aae45 100644
--- a/src/test/java/com/android/tools/r8/retrace/RetraceLambdaTest.java
+++ b/src/test/java/com/android/tools/r8/retrace/RetraceLambdaTest.java
@@ -92,7 +92,7 @@
             stackTrace -> {
               int frames = parameters.isCfRuntime() ? 2 : 1;
               checkRawStackTraceFrameCount(stackTrace, frames, "Expected everything to be inlined");
-              checkCurrentlyIncorrectStackTrace(stackTrace);
+              checkExpectedStackTrace(stackTrace);
             });
   }
 
@@ -173,29 +173,6 @@
                 .build()));
   }
 
-  private void checkCurrentlyIncorrectStackTrace(StackTrace stackTrace) {
-    assertThat(
-        stackTrace,
-        isSameExceptForFileNameAndLineNumber(
-            StackTrace.builder()
-                .addWithoutFileNameAndLineNumber(Main.class, RetraceLambdaTest.JAVAC_LAMBDA_METHOD)
-                .applyIf(
-                    parameters.isDexRuntime(),
-                    b ->
-                        b
-                            // TODO(b/172014416): Lambda bridges should be marked synthetic
-                            //  and removed.
-                            .addWithoutFileNameAndLineNumber(Main.class, LAMBDA_BRIDGE_METHOD)
-                            // TODO(b/172014416): The frame mapping should have removed this
-                            //  entry.
-                            // TODO(b/172014416): Synthetics should not map back to internal
-                            //  names.
-                            .addWithoutFileNameAndLineNumber(INTERNAL_LAMBDA_CLASS, "run"))
-                .addWithoutFileNameAndLineNumber(Main.class, "runIt")
-                .addWithoutFileNameAndLineNumber(Main.class, "main")
-                .build()));
-  }
-
   public interface MyRunner {
     void run();
   }
diff --git a/src/test/java/com/android/tools/r8/retrace/RetraceTests.java b/src/test/java/com/android/tools/r8/retrace/RetraceTests.java
index a1707c5..d7a13c8 100644
--- a/src/test/java/com/android/tools/r8/retrace/RetraceTests.java
+++ b/src/test/java/com/android/tools/r8/retrace/RetraceTests.java
@@ -258,12 +258,12 @@
 
   @Test
   public void testRetraceSynthesizedLambda() throws Exception {
-    runRetraceTest(new SyntheticLambdaMethodStackTrace());
+    runExperimentalRetraceTest(new SyntheticLambdaMethodStackTrace());
   }
 
   @Test
   public void testRetraceSynthesizedLambdaWithInlining() throws Exception {
-    runRetraceTest(new SyntheticLambdaMethodWithInliningStackTrace());
+    runExperimentalRetraceTest(new SyntheticLambdaMethodWithInliningStackTrace());
   }
 
   private void inspectRetraceTest(
@@ -276,6 +276,16 @@
 
   private TestDiagnosticMessagesImpl runRetraceTest(StackTraceForTest stackTraceForTest)
       throws Exception {
+    return runRetraceTest(stackTraceForTest, false);
+  }
+
+  private TestDiagnosticMessagesImpl runExperimentalRetraceTest(StackTraceForTest stackTraceForTest)
+      throws Exception {
+    return runRetraceTest(stackTraceForTest, true);
+  }
+
+  private TestDiagnosticMessagesImpl runRetraceTest(
+      StackTraceForTest stackTraceForTest, boolean allowExperimentalMapping) throws Exception {
     if (external) {
       assumeTrue(useRegExpParsing);
       assumeTrue(testParameters.isCfRuntime());
@@ -296,6 +306,9 @@
       command.add("-ea");
       command.add("-cp");
       command.add(ToolHelper.R8_RETRACE_JAR.toString());
+      if (allowExperimentalMapping) {
+        command.add("-Dcom.android.tools.r8.experimentalmapping");
+      }
       command.add("com.android.tools.r8.retrace.Retrace");
       command.add(mappingFile.toString());
       command.add(stackTraceFile.toString());
@@ -321,7 +334,7 @@
                           StringUtils.joinLines(stackTraceForTest.retracedStackTrace()),
                           StringUtils.joinLines(retraced)))
               .build();
-      Retrace.run(retraceCommand);
+      Retrace.runForTesting(retraceCommand, allowExperimentalMapping);
       return diagnosticsHandler;
     }
   }
diff --git a/src/test/java/com/android/tools/r8/retrace/stacktraces/SyntheticLambdaMethodStackTrace.java b/src/test/java/com/android/tools/r8/retrace/stacktraces/SyntheticLambdaMethodStackTrace.java
index efe3f0f..793fc0c 100644
--- a/src/test/java/com/android/tools/r8/retrace/stacktraces/SyntheticLambdaMethodStackTrace.java
+++ b/src/test/java/com/android/tools/r8/retrace/stacktraces/SyntheticLambdaMethodStackTrace.java
@@ -34,7 +34,7 @@
   @Override
   public String mapping() {
     return StringUtils.lines(
-        "# {'id':'com.android.tools.r8.metainf','map-version':'experimental'}",
+        "# {'id':'com.android.tools.r8.mapping','version':'experimental'}",
         "example.Main -> example.Main:",
         "  1:1:void main(java.lang.String[]):123 -> main",
         "example.Foo -> a.a:",
diff --git a/src/test/java/com/android/tools/r8/retrace/stacktraces/SyntheticLambdaMethodWithInliningStackTrace.java b/src/test/java/com/android/tools/r8/retrace/stacktraces/SyntheticLambdaMethodWithInliningStackTrace.java
index 5fda85f..c132733 100644
--- a/src/test/java/com/android/tools/r8/retrace/stacktraces/SyntheticLambdaMethodWithInliningStackTrace.java
+++ b/src/test/java/com/android/tools/r8/retrace/stacktraces/SyntheticLambdaMethodWithInliningStackTrace.java
@@ -33,7 +33,7 @@
   @Override
   public String mapping() {
     return StringUtils.lines(
-        "# {'id':'com.android.tools.r8.metainf','map-version':'experimental'}",
+        "# {'id':'com.android.tools.r8.mapping','version':'experimental'}",
         "example.Main -> example.Main:",
         "  1:1:void main(java.lang.String[]):123 -> main",
         "example.Foo -> a.a:",
diff --git a/src/test/java/com/android/tools/r8/shaking/ifrule/verticalclassmerging/MergedParameterTypeTest.java b/src/test/java/com/android/tools/r8/shaking/ifrule/verticalclassmerging/MergedParameterTypeTest.java
index 3961905..32d30d9 100644
--- a/src/test/java/com/android/tools/r8/shaking/ifrule/verticalclassmerging/MergedParameterTypeTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/ifrule/verticalclassmerging/MergedParameterTypeTest.java
@@ -10,6 +10,8 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotEquals;
 
+import com.android.tools.r8.NoHorizontalClassMerging;
+import com.android.tools.r8.R8FullTestBuilder;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
@@ -52,6 +54,7 @@
 
   public static class MergedParameterTypeWithCollisionTest extends MergedTypeBaseTest {
 
+    @NoHorizontalClassMerging
     static class SuperTestClass {
 
       public static void method(A obj) {
@@ -80,6 +83,12 @@
     }
 
     @Override
+    public void configure(R8FullTestBuilder builder) {
+      super.configure(builder);
+      builder.enableNoHorizontalClassMergingAnnotations();
+    }
+
+    @Override
     public Class<?> getTestClass() {
       return TestClass.class;
     }
diff --git a/src/test/java/com/android/tools/r8/shaking/ifrule/verticalclassmerging/MergedReturnTypeTest.java b/src/test/java/com/android/tools/r8/shaking/ifrule/verticalclassmerging/MergedReturnTypeTest.java
index 9173e66..5a03792 100644
--- a/src/test/java/com/android/tools/r8/shaking/ifrule/verticalclassmerging/MergedReturnTypeTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/ifrule/verticalclassmerging/MergedReturnTypeTest.java
@@ -11,6 +11,7 @@
 import static org.junit.Assert.assertNotEquals;
 
 import com.android.tools.r8.AssumeMayHaveSideEffects;
+import com.android.tools.r8.NoHorizontalClassMerging;
 import com.android.tools.r8.R8FullTestBuilder;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
@@ -61,6 +62,7 @@
 
   public static class MergedReturnTypeWithCollisionTest extends MergedTypeBaseTest {
 
+    @NoHorizontalClassMerging
     static class SuperTestClass {
 
       @AssumeMayHaveSideEffects
@@ -92,7 +94,7 @@
     @Override
     public void configure(R8FullTestBuilder builder) {
       super.configure(builder);
-      builder.enableSideEffectAnnotations();
+      builder.enableNoHorizontalClassMergingAnnotations().enableSideEffectAnnotations();
     }
 
     @Override
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/CodeInspector.java b/src/test/java/com/android/tools/r8/utils/codeinspector/CodeInspector.java
index 077af2f..0533c6b 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/CodeInspector.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/CodeInspector.java
@@ -139,6 +139,8 @@
       // The inspector allows building IR for a method. An output type must be defined for that.
       internalOptions.programConsumer = DexIndexedConsumer.emptyConsumer();
     }
+    // Always allow use of experimental map-file reading in the inspector.
+    internalOptions.testing.enableExperimentalMapFileVersion = true;
     return internalOptions;
   }
 
diff --git a/tools/compiledump.py b/tools/compiledump.py
index 1e336c1..15b4f85 100755
--- a/tools/compiledump.py
+++ b/tools/compiledump.py
@@ -204,7 +204,7 @@
   return args.version
 
 def determine_compiler(args, dump):
-  compilers = ['d8', 'r8', 'r8full']
+  compilers = ['d8', 'r8', 'r8full', 'l8']
   if args.compiler not in compilers:
     error("Unable to determine a compiler to use. Specified %s,"
           " Valid options: %s" % (args.compiler, ', '.join(compilers)))
@@ -301,6 +301,8 @@
     cmd.extend(['-cp', '%s:%s' % (wrapper_dir, jar)])
     if compiler == 'd8':
       cmd.append('com.android.tools.r8.D8')
+    if compiler == 'l8':
+      cmd.append('com.android.tools.r8.L8')
     if compiler.startswith('r8'):
       cmd.append('com.android.tools.r8.utils.CompileDumpCompatR8')
     if compiler == 'r8':
@@ -313,7 +315,7 @@
                  determine_feature_output(feature_jar, temp)])
     if dump.library_jar():
       cmd.extend(['--lib', dump.library_jar()])
-    if dump.classpath_jar():
+    if dump.classpath_jar() and compiler != 'l8':
       cmd.extend(['--classpath', dump.classpath_jar()])
     if dump.desugared_library_json() and not args.disable_desugared_lib:
       cmd.extend(['--desugared-lib', dump.desugared_library_json()])
@@ -323,7 +325,10 @@
       cmd.extend(['--pg-conf', dump.config_file()])
     if dump.main_dex_rules_resource():
       cmd.extend(['--main-dex-rules', dump.main_dex_rules_resource()])
-    if compiler != 'd8':
+    if compiler == 'l8':
+      if dump.config_file():
+        cmd.extend(['--pg-map-output', '%s.map' % out])
+    elif compiler != 'd8':
       cmd.extend(['--pg-map-output', '%s.map' % out])
     if min_api:
       cmd.extend(['--min-api', min_api])
diff --git a/tools/d8.py b/tools/d8.py
index 18a4a67..833f7f5 100755
--- a/tools/d8.py
+++ b/tools/d8.py
@@ -3,8 +3,38 @@
 # for details. All rights reserved. Use of this source code is governed by a
 # BSD-style license that can be found in the LICENSE file.
 
+import utils
+
+import optparse
 import sys
 import toolhelper
 
+def ParseOptions(argv):
+  parser = optparse.OptionParser(usage='%prog [options] -- [D8 options]')
+  parser.add_option(
+      '-c',
+      '--commit-hash',
+      '--commit_hash',
+      help='Commit hash of D8 to use.',
+      default=None)
+  parser.add_option(
+      '--version',
+      help='Version of D8 to use.',
+      default=None)
+  parser.add_option(
+      '--tag',
+      help='Tag of D8 to use.',
+      default=None)
+  return parser.parse_args(argv)
+
+def main(argv):
+  (options, args) = ParseOptions(sys.argv)
+  d8_args = args[1:]
+  return toolhelper.run(
+      'd8',
+      d8_args,
+      jar=utils.find_r8_jar_from_options(options),
+      main='com.android.tools.r8.D8')
+
 if __name__ == '__main__':
-  sys.exit(toolhelper.run('d8', sys.argv[1:]))
+  sys.exit(main(sys.argv[1:]))
diff --git a/tools/retrace.py b/tools/retrace.py
index b0a1ec8..ea06566 100755
--- a/tools/retrace.py
+++ b/tools/retrace.py
@@ -53,52 +53,24 @@
   return parser.parse_args()
 
 
-def find_version_or_hash_from_tag(tag_or_hash):
-  info = subprocess.check_output([
-      'git',
-      'show',
-      tag_or_hash,
-      '-s',
-      '--format=oneline']).splitlines()[-1].split()
-  # The info should be on the following form [hash,"Version",version]
-  if len(info) == 3 and len(info[0]) == 40 and info[1] == "Version":
-    return info[2]
-  return None
-
-
 def main():
   args = parse_arguments()
-  if args.tag:
-    hash_or_version = find_version_or_hash_from_tag(args.tag)
-  else:
-    hash_or_version = args.commit_hash or args.version
+  map_path = utils.find_cloud_storage_file_from_options(
+      'r8lib.jar.map', args, orElse=args.map)
   return run(
-      args.map,
-      hash_or_version,
+      map_path,
       args.stacktrace,
       args.commit_hash is not None,
       args.no_r8lib,
       quiet=args.quiet,
       debug=args.debug_agent)
 
-def run(map_path, hash_or_version, stacktrace, is_hash, no_r8lib, quiet=False,
-        debug=False):
-  if hash_or_version:
-    download_path = archive.GetUploadDestination(
-        hash_or_version,
-        'r8lib.jar.map',
-        is_hash)
-    if utils.file_exists_on_cloud_storage(download_path):
-      map_path = tempfile.NamedTemporaryFile().name
-      utils.download_file_from_cloud_storage(download_path, map_path)
-    else:
-      print('Could not find map file from argument: %s.' % hash_or_version)
-      return 1
-
+def run(map_path, stacktrace, is_hash, no_r8lib, quiet=False, debug=False):
   retrace_args = [jdk.GetJavaExecutable()]
 
   if debug:
-    retrace_args.append('-agentlib:jdwp=transport=dt_socket,server=y,suspend=y,address=5005')
+    retrace_args.append(
+        '-agentlib:jdwp=transport=dt_socket,server=y,suspend=y,address=5005')
 
   retrace_args += [
     '-cp',
diff --git a/tools/test.py b/tools/test.py
index ea6a880..6734106 100755
--- a/tools/test.py
+++ b/tools/test.py
@@ -7,6 +7,8 @@
 # if an argument is given, run only tests with that pattern. This script will
 # force the tests to run, even if no input changed.
 
+import archive_desugar_jdk_libs
+import notify
 import optparse
 import os
 import shutil
@@ -16,9 +18,7 @@
 import time
 import uuid
 
-import archive_desugar_jdk_libs
 import gradle
-import notify
 import utils
 
 ALL_ART_VMS = [
@@ -386,14 +386,9 @@
   for art_vm in vms_to_test:
     vm_suffix = "_" + options.dex_vm_kind if art_vm != "default" else ""
     runtimes = ['dex-' + art_vm]
-    # Only append the "none" runtime and JVMs if running on the "default" DEX VM.
+    # Append the "none" runtime and default JVM if running the "default" DEX VM.
     if art_vm == "default":
-      # TODO(b/170454076): Remove special casing for bot when rex-script has
-      #  been migrated to account for runtimes.
-      if utils.is_bot():
         runtimes.extend(['jdk11', 'none'])
-      else:
-        runtimes.extend(['jdk8', 'jdk9', 'jdk11', 'none'])
     return_code = gradle.RunGradle(
         gradle_args + [
           '-Pdex_vm=%s' % art_vm + vm_suffix,
diff --git a/tools/utils.py b/tools/utils.py
index 5c1b32b..bd28ef3 100644
--- a/tools/utils.py
+++ b/tools/utils.py
@@ -103,6 +103,46 @@
       f.write(str(value))
     archive_file(name, gs_dir, tempfile)
 
+def find_cloud_storage_file_from_options(name, options, orElse=None):
+  # Import archive on-demand since archive depends on utils.
+  from archive import GetUploadDestination
+  hash_or_version = find_hash_or_version_from_options(options)
+  if not hash_or_version:
+    return orElse
+  is_hash = options.commit_hash is not None
+  download_path = GetUploadDestination(hash_or_version, name, is_hash)
+  if file_exists_on_cloud_storage(download_path):
+    out = tempfile.NamedTemporaryFile().name
+    download_file_from_cloud_storage(download_path, out)
+    return out
+  else:
+    raise Exception('Could not find file {} from hash/version: {}.'
+                  .format(name, hash_or_version))
+
+def find_r8_jar_from_options(options):
+  return find_cloud_storage_file_from_options('r8.jar', options)
+
+def find_r8_lib_jar_from_options(options):
+  return find_cloud_storage_file_from_options('r8lib.jar', options)
+
+def find_hash_or_version_from_options(options):
+  if options.tag:
+    return find_hash_or_version_from_tag(options.tag)
+  else:
+    return options.commit_hash or options.version
+
+def find_hash_or_version_from_tag(tag_or_hash):
+  info = subprocess.check_output([
+      'git',
+      'show',
+      tag_or_hash,
+      '-s',
+      '--format=oneline']).splitlines()[-1].split()
+  # The info should be on the following form [hash,"Version",version]
+  if len(info) == 3 and len(info[0]) == 40 and info[1] == "Version":
+    return info[2]
+  return None
+
 def getAndroidHome():
   return os.environ.get(
       ANDROID_HOME_ENVIROMENT_NAME, os.path.join(USER_HOME, 'Android', 'Sdk'))