Merge commit 'e80cf5909fb4d2ab373e95bfe57cb36aa605d873' into dev-release
diff --git a/src/main/java/com/android/tools/r8/graph/DexEncodedField.java b/src/main/java/com/android/tools/r8/graph/DexEncodedField.java
index 7095940..ada1c8a 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedField.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedField.java
@@ -346,6 +346,7 @@
     this.genericSignature = genericSignature;
   }
 
+  @Override
   public void clearGenericSignature() {
     this.genericSignature = FieldTypeSignature.noSignature();
   }
diff --git a/src/main/java/com/android/tools/r8/graph/DexEncodedMember.java b/src/main/java/com/android/tools/r8/graph/DexEncodedMember.java
index f596146..e1bb25e 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedMember.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedMember.java
@@ -28,6 +28,8 @@
 
   public abstract void clearKotlinInfo();
 
+  public abstract void clearGenericSignature();
+
   public DexType getHolderType() {
     return getReference().getHolderType();
   }
diff --git a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
index 9051374..434b091 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
@@ -650,10 +650,6 @@
     this.kotlinMemberInfo = kotlinMemberInfo;
   }
 
-  public void clearKotlinMemberInfo() {
-    kotlinMemberInfo = getNoKotlinInfo();
-  }
-
   public boolean isKotlinFunction() {
     return kotlinMemberInfo.isFunction();
   }
@@ -1504,6 +1500,7 @@
     this.genericSignature = genericSignature;
   }
 
+  @Override
   public void clearGenericSignature() {
     this.genericSignature = MethodTypeSignature.noSignature();
   }
diff --git a/src/main/java/com/android/tools/r8/graph/DexProgramClass.java b/src/main/java/com/android/tools/r8/graph/DexProgramClass.java
index 315b8a1..6be5551 100644
--- a/src/main/java/com/android/tools/r8/graph/DexProgramClass.java
+++ b/src/main/java/com/android/tools/r8/graph/DexProgramClass.java
@@ -472,10 +472,14 @@
 
   public void setKotlinInfo(KotlinClassLevelInfo kotlinInfo) {
     assert kotlinInfo != null;
-    assert this.kotlinInfo == getNoKotlinInfo() || kotlinInfo == getNoKotlinInfo();
+    assert this.kotlinInfo == getNoKotlinInfo();
     this.kotlinInfo = kotlinInfo;
   }
 
+  public void clearKotlinInfo() {
+    this.kotlinInfo = getNoKotlinInfo();
+  }
+
   @Override
   boolean internalClassOrInterfaceMayHaveInitializationSideEffects(
       AppView<?> appView,
diff --git a/src/main/java/com/android/tools/r8/graph/GenericSignatureContextBuilder.java b/src/main/java/com/android/tools/r8/graph/GenericSignatureContextBuilder.java
index 1a05232..00534ba 100644
--- a/src/main/java/com/android/tools/r8/graph/GenericSignatureContextBuilder.java
+++ b/src/main/java/com/android/tools/r8/graph/GenericSignatureContextBuilder.java
@@ -269,16 +269,13 @@
     if (enclosingClass == null || enclosedClass == null) {
       return true;
     }
-    if (enclosingReference.isDexMethod()) {
-      return enclosedClass.getEnclosingMethodAttribute() == null
-          || enclosedClass.getEnclosingMethodAttribute().getEnclosingMethod() != enclosingReference;
+    if (enclosedClass.getEnclosingMethodAttribute() != null) {
+      return enclosingReference.isDexMethod()
+          ? enclosedClass.getEnclosingMethodAttribute().getEnclosingMethod() != enclosingReference
+          : enclosedClass.getEnclosingMethodAttribute().getEnclosingClass() != enclosingReference;
     } else {
       InnerClassAttribute innerClassAttribute = enclosedClass.getInnerClassAttributeForThisClass();
-      if (innerClassAttribute != null) {
-        return innerClassAttribute.getOuter() != enclosingReference;
-      }
-      return enclosedClass.getEnclosingMethodAttribute() == null
-          || enclosedClass.getEnclosingMethodAttribute().getEnclosingClass() != enclosingReference;
+      return innerClassAttribute == null || innerClassAttribute.getOuter() != enclosingReference;
     }
   }
 
diff --git a/src/main/java/com/android/tools/r8/graph/GenericSignatureCorrectnessHelper.java b/src/main/java/com/android/tools/r8/graph/GenericSignatureCorrectnessHelper.java
index 7dcfb37..ec86d3e 100644
--- a/src/main/java/com/android/tools/r8/graph/GenericSignatureCorrectnessHelper.java
+++ b/src/main/java/com/android/tools/r8/graph/GenericSignatureCorrectnessHelper.java
@@ -104,13 +104,11 @@
     if (appView.options().disableGenericSignatureValidation) {
       return VALID;
     }
+    SignatureEvaluationResult evaluationResult = VALID;
     for (DexProgramClass clazz : programClasses) {
-      SignatureEvaluationResult evaluationResult = evaluateSignaturesForClass(clazz);
-      if (evaluationResult.isInvalid()) {
-        return evaluationResult;
-      }
+      evaluationResult = evaluationResult.combine(evaluateSignaturesForClass(clazz));
     }
-    return VALID;
+    return evaluationResult;
   }
 
   public SignatureEvaluationResult evaluateSignaturesForClass(DexProgramClass clazz) {
diff --git a/src/main/java/com/android/tools/r8/graph/ProgramMethod.java b/src/main/java/com/android/tools/r8/graph/ProgramMethod.java
index f4aa43b..5a58189 100644
--- a/src/main/java/com/android/tools/r8/graph/ProgramMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/ProgramMethod.java
@@ -101,7 +101,7 @@
 
   @Override
   public void clearKotlinInfo() {
-    getDefinition().clearKotlinMemberInfo();
+    getDefinition().clearKotlinInfo();
   }
 
   public MethodPosition getPosition() {
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 dfd4f77..89e9f74 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/ClassMerger.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/ClassMerger.java
@@ -17,7 +17,6 @@
 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;
 import com.android.tools.r8.graph.DexTypeList;
 import com.android.tools.r8.graph.FieldAccessFlags;
@@ -41,7 +40,6 @@
 import it.unimi.dsi.fastutil.objects.Reference2IntOpenHashMap;
 import java.util.ArrayList;
 import java.util.Collection;
-import java.util.Comparator;
 import java.util.Iterator;
 import java.util.LinkedHashMap;
 import java.util.List;
@@ -72,28 +70,22 @@
   private final ClassStaticFieldsMerger classStaticFieldsMerger;
   private final ClassInstanceFieldsMerger classInstanceFieldsMerger;
   private final Collection<VirtualMethodMerger> virtualMethodMergers;
-  private final Collection<InstanceInitializerMerger> instanceInitializerMergers;
 
   private ClassMerger(
       AppView<? extends AppInfoWithClassHierarchy> appView,
       Mode mode,
       HorizontalClassMergerGraphLens.Builder lensBuilder,
       MergeGroup group,
-      Collection<VirtualMethodMerger> virtualMethodMergers,
-      Collection<InstanceInitializerMerger> instanceInitializerMergers,
-      ClassInitializerMerger classInitializerMerger) {
+      Collection<VirtualMethodMerger> virtualMethodMergers) {
     this.appView = appView;
-    this.mode = mode;
-    this.lensBuilder = lensBuilder;
-    this.group = group;
-    this.virtualMethodMergers = virtualMethodMergers;
-    this.instanceInitializerMergers = instanceInitializerMergers;
-
     this.dexItemFactory = appView.dexItemFactory();
-    this.classInitializerMerger = classInitializerMerger;
+    this.group = group;
+    this.lensBuilder = lensBuilder;
+    this.mode = mode;
+    this.virtualMethodMergers = virtualMethodMergers;
+    this.classInitializerMerger = ClassInitializerMerger.create(group);
     this.classStaticFieldsMerger = new ClassStaticFieldsMerger(appView, lensBuilder, group);
     this.classInstanceFieldsMerger = new ClassInstanceFieldsMerger(appView, lensBuilder, group);
-
     buildClassIdentifierMap();
   }
 
@@ -105,10 +97,9 @@
   void mergeDirectMethods(
       SyntheticArgumentClass syntheticArgumentClass,
       SyntheticClassInitializerConverter.Builder syntheticClassInitializerConverterBuilder) {
+    mergeInstanceInitializers(syntheticArgumentClass);
     mergeStaticClassInitializers(syntheticClassInitializerConverterBuilder);
-    mergeDirectMethods(group.getTarget());
-    group.forEachSource(this::mergeDirectMethods);
-    mergeConstructors(syntheticArgumentClass);
+    group.forEach(this::mergeDirectMethods);
   }
 
   void mergeStaticClassInitializers(
@@ -183,13 +174,23 @@
         classMethodsBuilder::isFresh);
   }
 
-  void mergeConstructors(SyntheticArgumentClass syntheticArgumentClass) {
+  void mergeInstanceInitializers(SyntheticArgumentClass syntheticArgumentClass) {
+    InstanceInitializerMergerCollection instanceInitializerMergers =
+        InstanceInitializerMergerCollection.create(appView, group, lensBuilder, mode);
     instanceInitializerMergers.forEach(
         merger ->
             merger.merge(
                 classMethodsBuilder, lensBuilder, classIdentifiers, syntheticArgumentClass));
   }
 
+  void mergeMethods(
+      SyntheticArgumentClass syntheticArgumentClass,
+      SyntheticClassInitializerConverter.Builder syntheticClassInitializerConverterBuilder) {
+    mergeVirtualMethods();
+    mergeDirectMethods(syntheticArgumentClass, syntheticClassInitializerConverterBuilder);
+    classMethodsBuilder.setClassMethods(group.getTarget());
+  }
+
   void mergeVirtualMethods() {
     virtualMethodMergers.forEach(
         merger -> merger.merge(classMethodsBuilder, lensBuilder, classIdentifiers));
@@ -225,12 +226,6 @@
     classInstanceFieldsMerger.setClassIdField(classIdField);
   }
 
-  void mergeStaticFields() {
-    group.forEachSource(classStaticFieldsMerger::addFields);
-    classStaticFieldsMerger.merge(group.getTarget());
-    group.forEachSource(clazz -> clazz.setStaticFields(null));
-  }
-
   void fixAccessFlags() {
     if (Iterables.any(group.getSources(), not(DexProgramClass::isAbstract))) {
       group.getTarget().getAccessFlags().demoteFromAbstract();
@@ -291,6 +286,14 @@
     group.getTarget().setInterfaces(DexTypeList.create(interfaces));
   }
 
+  void mergeFields() {
+    if (group.hasClassIdField()) {
+      appendClassIdField();
+    }
+    mergeInstanceFields();
+    mergeStaticFields();
+  }
+
   void mergeInstanceFields() {
     group.forEachSource(
         clazz -> {
@@ -300,25 +303,21 @@
     group.getTarget().setInstanceFields(classInstanceFieldsMerger.merge());
   }
 
+  void mergeStaticFields() {
+    group.forEachSource(classStaticFieldsMerger::addFields);
+    classStaticFieldsMerger.merge(group.getTarget());
+    group.forEachSource(clazz -> clazz.setStaticFields(null));
+  }
+
   public void mergeGroup(
       SyntheticArgumentClass syntheticArgumentClass,
       SyntheticClassInitializerConverter.Builder syntheticClassInitializerConverterBuilder) {
     fixAccessFlags();
     fixNestMemberAttributes();
-
-    if (group.hasClassIdField()) {
-      appendClassIdField();
-    }
-
     mergeAnnotations();
     mergeInterfaces();
-
-    mergeVirtualMethods();
-    mergeDirectMethods(syntheticArgumentClass, syntheticClassInitializerConverterBuilder);
-    classMethodsBuilder.setClassMethods(group.getTarget());
-
-    mergeStaticFields();
-    mergeInstanceFields();
+    mergeFields();
+    mergeMethods(syntheticArgumentClass, syntheticClassInitializerConverterBuilder);
   }
 
   public static class Builder {
@@ -359,53 +358,6 @@
           appView.testing().horizontalClassMergingTarget.apply(appView, candidates, target));
     }
 
-    private ClassInitializerMerger createClassInitializerMerger() {
-      ClassInitializerMerger.Builder builder = new ClassInitializerMerger.Builder();
-      group.forEach(
-          clazz -> {
-            if (clazz.hasClassInitializer()) {
-              builder.add(clazz.getProgramClassInitializer());
-            }
-          });
-      return builder.build();
-    }
-
-    private List<InstanceInitializerMerger> createInstanceInitializerMergers() {
-      List<InstanceInitializerMerger> instanceInitializerMergers = new ArrayList<>();
-      if (appView.options().horizontalClassMergerOptions().isConstructorMergingEnabled()) {
-        Map<DexProto, InstanceInitializerMerger.Builder> buildersByProto = new LinkedHashMap<>();
-        group.forEach(
-            clazz ->
-                clazz.forEachProgramDirectMethodMatching(
-                    DexEncodedMethod::isInstanceInitializer,
-                    method ->
-                        buildersByProto
-                            .computeIfAbsent(
-                                method.getDefinition().getProto(),
-                                ignore -> new InstanceInitializerMerger.Builder(appView, mode))
-                            .add(method)));
-        for (InstanceInitializerMerger.Builder builder : buildersByProto.values()) {
-          instanceInitializerMergers.addAll(builder.build(group));
-        }
-      } else {
-        group.forEach(
-            clazz ->
-                clazz.forEachProgramDirectMethodMatching(
-                    DexEncodedMethod::isInstanceInitializer,
-                    method ->
-                        instanceInitializerMergers.addAll(
-                            new InstanceInitializerMerger.Builder(appView, mode)
-                                .add(method)
-                                .build(group))));
-      }
-
-      // Try and merge the constructors with the most arguments first, to avoid using synthetic
-      // arguments if possible.
-      instanceInitializerMergers.sort(
-          Comparator.comparing(InstanceInitializerMerger::getArity).reversed());
-      return instanceInitializerMergers;
-    }
-
     private List<VirtualMethodMerger> createVirtualMethodMergers() {
       Map<DexMethodSignature, VirtualMethodMerger.Builder> virtualMethodMergerBuilders =
           new LinkedHashMap<>();
@@ -448,14 +400,7 @@
         createClassIdField();
       }
 
-      return new ClassMerger(
-          appView,
-          mode,
-          lensBuilder,
-          group,
-          virtualMethodMergers,
-          createInstanceInitializerMergers(),
-          createClassInitializerMerger());
+      return new ClassMerger(appView, mode, lensBuilder, group, virtualMethodMergers);
     }
   }
 }
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/InstanceInitializerAnalysis.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/InstanceInitializerAnalysis.java
new file mode 100644
index 0000000..febdbe6
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/InstanceInitializerAnalysis.java
@@ -0,0 +1,17 @@
+// Copyright (c) 2021, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.horizontalclassmerging;
+
+import com.android.tools.r8.graph.ProgramMethod;
+
+public class InstanceInitializerAnalysis {
+
+  public static InstanceInitializerDescription analyze(
+      ProgramMethod instanceInitializer, HorizontalClassMergerGraphLens.Builder lensBuilder) {
+    // TODO(b/189296638): Return an InstanceInitializerDescription if the given instance initializer
+    //  is parent constructor call followed by a sequence of instance field puts.
+    return null;
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/InstanceInitializerDescription.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/InstanceInitializerDescription.java
new file mode 100644
index 0000000..41f2c4e
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/InstanceInitializerDescription.java
@@ -0,0 +1,91 @@
+// 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.horizontalclassmerging;
+
+import com.android.tools.r8.graph.DexField;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.ir.optimize.info.field.InstanceFieldInitializationInfo;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+
+/**
+ * A simple abstraction of an instance initializer's code, which allows a parent constructor call
+ * followed by a sequence of instance-put instructions.
+ */
+public class InstanceInitializerDescription {
+
+  private final int arity;
+  private final Map<DexField, InstanceFieldInitializationInfo> instanceFieldAssignments;
+  private final DexMethod parentConstructor;
+  private final List<InstanceFieldInitializationInfo> parentConstructorArguments;
+
+  InstanceInitializerDescription(
+      int arity,
+      Map<DexField, InstanceFieldInitializationInfo> instanceFieldAssignments,
+      DexMethod parentConstructor,
+      List<InstanceFieldInitializationInfo> parentConstructorArguments) {
+    this.arity = arity;
+    this.instanceFieldAssignments = instanceFieldAssignments;
+    this.parentConstructor = parentConstructor;
+    this.parentConstructorArguments = parentConstructorArguments;
+  }
+
+  public static Builder builder(ProgramMethod instanceInitializer) {
+    return new Builder(instanceInitializer);
+  }
+
+  @Override
+  public boolean equals(Object obj) {
+    if (obj == null || getClass() != obj.getClass()) {
+      return false;
+    }
+    InstanceInitializerDescription description = (InstanceInitializerDescription) obj;
+    return arity == description.arity
+        && instanceFieldAssignments.equals(description.instanceFieldAssignments)
+        && parentConstructor == description.parentConstructor
+        && parentConstructorArguments.equals(description.parentConstructorArguments);
+  }
+
+  @Override
+  public int hashCode() {
+    return Objects.hash(
+        arity, instanceFieldAssignments, parentConstructor, parentConstructorArguments);
+  }
+
+  public static class Builder {
+
+    private final int arity;
+
+    private Map<DexField, InstanceFieldInitializationInfo> instanceFieldAssignments =
+        new LinkedHashMap<>();
+    private DexMethod parentConstructor;
+    private List<InstanceFieldInitializationInfo> parentConstructorArguments;
+
+    Builder(ProgramMethod method) {
+      this.arity = method.getReference().getArity();
+    }
+
+    public void addInstancePut(DexField field, InstanceFieldInitializationInfo value) {
+      instanceFieldAssignments.put(field, value);
+    }
+
+    public void addInvokeConstructor(
+        DexMethod method, List<InstanceFieldInitializationInfo> arguments) {
+      assert parentConstructor == null;
+      assert parentConstructorArguments == null;
+      parentConstructor = method;
+      parentConstructorArguments = arguments;
+    }
+
+    public InstanceInitializerDescription build() {
+      assert parentConstructor != null;
+      return new InstanceInitializerDescription(
+          arity, instanceFieldAssignments, parentConstructor, parentConstructorArguments);
+    }
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/InstanceInitializerMerger.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/InstanceInitializerMerger.java
index b1f02a9..c2063f3 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/InstanceInitializerMerger.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/InstanceInitializerMerger.java
@@ -43,14 +43,25 @@
   private final DexItemFactory dexItemFactory;
   private final MergeGroup group;
   private final List<ProgramMethod> instanceInitializers;
+  private final InstanceInitializerDescription instanceInitializerDescription;
   private final Mode mode;
 
   InstanceInitializerMerger(
       AppView<?> appView, MergeGroup group, List<ProgramMethod> instanceInitializers, Mode mode) {
+    this(appView, group, instanceInitializers, mode, null);
+  }
+
+  InstanceInitializerMerger(
+      AppView<?> appView,
+      MergeGroup group,
+      List<ProgramMethod> instanceInitializers,
+      Mode mode,
+      InstanceInitializerDescription instanceInitializerDescription) {
     this.appView = appView;
     this.dexItemFactory = appView.dexItemFactory();
     this.group = group;
     this.instanceInitializers = instanceInitializers;
+    this.instanceInitializerDescription = instanceInitializerDescription;
     this.mode = mode;
 
     // Constructors should not be empty and all constructors should have the same prototype.
@@ -74,6 +85,14 @@
     return instanceInitializers.iterator().next().getReference().getArity();
   }
 
+  public List<ProgramMethod> getInstanceInitializers() {
+    return instanceInitializers;
+  }
+
+  public int size() {
+    return instanceInitializers.size();
+  }
+
   public static class Builder {
 
     private final AppView<? extends AppInfoWithClassHierarchy> appView;
@@ -107,6 +126,11 @@
       return this;
     }
 
+    public Builder addEquivalent(ProgramMethod instanceInitializer) {
+      ListUtils.last(instanceInitializerGroups).add(instanceInitializer);
+      return this;
+    }
+
     public List<InstanceInitializerMerger> build(MergeGroup group) {
       assert instanceInitializerGroups.stream().noneMatch(List::isEmpty);
       return ListUtils.map(
@@ -114,6 +138,15 @@
           instanceInitializers ->
               new InstanceInitializerMerger(appView, group, instanceInitializers, mode));
     }
+
+    public InstanceInitializerMerger buildSingle(
+        MergeGroup group, InstanceInitializerDescription instanceInitializerDescription) {
+      assert instanceInitializerGroups.stream().noneMatch(List::isEmpty);
+      assert instanceInitializerGroups.size() == 1;
+      List<ProgramMethod> instanceInitializers = ListUtils.first(instanceInitializerGroups);
+      return new InstanceInitializerMerger(
+          appView, group, instanceInitializers, mode, instanceInitializerDescription);
+    }
   }
 
   // Returns true if we can simply use an existing constructor as the new constructor.
@@ -198,6 +231,8 @@
       HorizontalClassMergerGraphLens.Builder lensBuilder,
       Reference2IntMap<DexType> classIdentifiers,
       SyntheticArgumentClass syntheticArgumentClass) {
+    // TODO(b/189296638): Handle merging of equivalent constructors when
+    //  `instanceInitializerDescription` is set.
     if (isTrivialMerge(classMethodsBuilder)) {
       mergeTrivial(classMethodsBuilder, lensBuilder);
       return;
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/InstanceInitializerMergerCollection.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/InstanceInitializerMergerCollection.java
new file mode 100644
index 0000000..ed7e099
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/InstanceInitializerMergerCollection.java
@@ -0,0 +1,114 @@
+// Copyright (c) 2021, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.horizontalclassmerging;
+
+import static com.android.tools.r8.utils.MapUtils.ignoreKey;
+
+import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexProto;
+import com.android.tools.r8.horizontalclassmerging.HorizontalClassMerger.Mode;
+import com.android.tools.r8.horizontalclassmerging.InstanceInitializerMerger.Builder;
+import com.android.tools.r8.utils.collections.ProgramMethodSet;
+import java.util.ArrayList;
+import java.util.Comparator;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.function.Consumer;
+
+public class InstanceInitializerMergerCollection {
+
+  private final List<InstanceInitializerMerger> instanceInitializerMergers;
+  private final Map<InstanceInitializerDescription, InstanceInitializerMerger>
+      equivalentInstanceInitializerMergers;
+
+  private InstanceInitializerMergerCollection(
+      List<InstanceInitializerMerger> instanceInitializerMergers,
+      Map<InstanceInitializerDescription, InstanceInitializerMerger>
+          equivalentInstanceInitializerMergers) {
+    assert equivalentInstanceInitializerMergers.isEmpty();
+    this.instanceInitializerMergers = instanceInitializerMergers;
+    this.equivalentInstanceInitializerMergers = equivalentInstanceInitializerMergers;
+  }
+
+  public static InstanceInitializerMergerCollection create(
+      AppView<? extends AppInfoWithClassHierarchy> appView,
+      MergeGroup group,
+      HorizontalClassMergerGraphLens.Builder lensBuilder,
+      Mode mode) {
+    // Create an instance initializer merger for each group of instance initializers that are
+    // equivalent.
+    Map<InstanceInitializerDescription, Builder> buildersByDescription = new LinkedHashMap<>();
+    ProgramMethodSet buildersWithoutDescription = ProgramMethodSet.createLinked();
+    group.forEach(
+        clazz ->
+            clazz.forEachProgramDirectMethodMatching(
+                DexEncodedMethod::isInstanceInitializer,
+                instanceInitializer -> {
+                  InstanceInitializerDescription description =
+                      InstanceInitializerAnalysis.analyze(instanceInitializer, lensBuilder);
+                  if (description != null) {
+                    buildersByDescription
+                        .computeIfAbsent(
+                            description,
+                            ignoreKey(() -> new InstanceInitializerMerger.Builder(appView, mode)))
+                        .addEquivalent(instanceInitializer);
+                  } else {
+                    buildersWithoutDescription.add(instanceInitializer);
+                  }
+                }));
+
+    Map<InstanceInitializerDescription, InstanceInitializerMerger>
+        equivalentInstanceInitializerMergers = new LinkedHashMap<>();
+    buildersByDescription.forEach(
+        (description, builder) -> {
+          InstanceInitializerMerger instanceInitializerMerger =
+              builder.buildSingle(group, description);
+          if (instanceInitializerMerger.size() == 1) {
+            // If there is only one constructor with a specific behavior, then consider it for
+            // normal instance initializer merging below.
+            buildersWithoutDescription.addAll(instanceInitializerMerger.getInstanceInitializers());
+          } else {
+            equivalentInstanceInitializerMergers.put(description, instanceInitializerMerger);
+          }
+        });
+
+    // Merge instance initializers with different behavior.
+    List<InstanceInitializerMerger> instanceInitializerMergers = new ArrayList<>();
+    if (appView.options().horizontalClassMergerOptions().isConstructorMergingEnabled()) {
+      Map<DexProto, Builder> buildersByProto = new LinkedHashMap<>();
+      buildersWithoutDescription.forEach(
+          instanceInitializer ->
+              buildersByProto
+                  .computeIfAbsent(
+                      instanceInitializer.getDefinition().getProto(),
+                      ignore -> new InstanceInitializerMerger.Builder(appView, mode))
+                  .add(instanceInitializer));
+      for (InstanceInitializerMerger.Builder builder : buildersByProto.values()) {
+        instanceInitializerMergers.addAll(builder.build(group));
+      }
+    } else {
+      buildersWithoutDescription.forEach(
+          instanceInitializer ->
+              instanceInitializerMergers.addAll(
+                  new InstanceInitializerMerger.Builder(appView, mode)
+                      .add(instanceInitializer)
+                      .build(group)));
+    }
+
+    // Try and merge the constructors with the most arguments first, to avoid using synthetic
+    // arguments if possible.
+    instanceInitializerMergers.sort(
+        Comparator.comparing(InstanceInitializerMerger::getArity).reversed());
+    return new InstanceInitializerMergerCollection(
+        instanceInitializerMergers, equivalentInstanceInitializerMergers);
+  }
+
+  public void forEach(Consumer<InstanceInitializerMerger> consumer) {
+    instanceInitializerMergers.forEach(consumer);
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/code/ClassInitializerMerger.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/code/ClassInitializerMerger.java
index a720407..11fa11e 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/code/ClassInitializerMerger.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/code/ClassInitializerMerger.java
@@ -23,6 +23,7 @@
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.graph.RewrittenPrototypeDescription;
 import com.android.tools.r8.graph.UseRegistry;
+import com.android.tools.r8.horizontalclassmerging.MergeGroup;
 import com.android.tools.r8.ir.code.BasicBlock;
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.IRMetadata;
@@ -56,6 +57,17 @@
     this.classInitializers = classInitializers;
   }
 
+  public static ClassInitializerMerger create(MergeGroup group) {
+    ClassInitializerMerger.Builder builder = new ClassInitializerMerger.Builder();
+    group.forEach(
+        clazz -> {
+          if (clazz.hasClassInitializer()) {
+            builder.add(clazz.getProgramClassInitializer());
+          }
+        });
+    return builder.build();
+  }
+
   public boolean isEmpty() {
     return classInitializers.isEmpty();
   }
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/OnlyDirectlyConnectedOrUnrelatedInterfaces.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/OnlyDirectlyConnectedOrUnrelatedInterfaces.java
index fc457f7..c53395c 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/OnlyDirectlyConnectedOrUnrelatedInterfaces.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/OnlyDirectlyConnectedOrUnrelatedInterfaces.java
@@ -95,16 +95,20 @@
       if (newGroup != null) {
         newGroup.add(clazz, superInterfaces, subInterfaces);
       } else {
-        newGroupsWithInfo.add(new MergeGroupWithInfo(clazz, superInterfaces, subInterfaces));
+        newGroup = new MergeGroupWithInfo(clazz, superInterfaces, subInterfaces);
+        newGroupsWithInfo.add(newGroup);
       }
+      committed.put(clazz, newGroup.getGroup());
     }
 
     List<MergeGroup> newGroups = new LinkedList<>();
     for (MergeGroupWithInfo newGroupWithInfo : newGroupsWithInfo) {
       MergeGroup newGroup = newGroupWithInfo.getGroup();
-      if (!newGroup.isTrivial()) {
+      if (newGroup.isTrivial()) {
+        assert !newGroup.isEmpty();
+        committed.remove(newGroup.getClasses().getFirst());
+      } else {
         newGroups.add(newGroup);
-        newGroup.forEach(clazz -> committed.put(clazz, newGroup));
       }
     }
     return newGroups;
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/itf/EmulatedInterfaceProcessor.java b/src/main/java/com/android/tools/r8/ir/desugar/itf/EmulatedInterfaceProcessor.java
index ff28c27..5929db5 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/itf/EmulatedInterfaceProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/itf/EmulatedInterfaceProcessor.java
@@ -139,15 +139,11 @@
   DexProgramClass ensureEmulateInterfaceLibrary(
       DexProgramClass emulatedInterface, ProgramMethodSet synthesizedMethods) {
     assert rewriter.isEmulatedInterface(emulatedInterface.type);
-    DexType emulateLibraryClassType =
-        InterfaceMethodRewriter.getEmulateLibraryInterfaceClassType(
-            emulatedInterface.type, appView.dexItemFactory());
     DexProgramClass emulateInterfaceClass =
         appView
             .getSyntheticItems()
-            .ensureFixedClassWhileMigrating(
+            .ensureFixedClass(
                 SyntheticNaming.SyntheticKind.EMULATED_INTERFACE_CLASS,
-                emulateLibraryClassType,
                 emulatedInterface,
                 appView,
                 builder ->
@@ -159,6 +155,9 @@
                                     synthesizeEmulatedInterfaceMethod(
                                         method, emulatedInterface, methodBuilder))));
     emulateInterfaceClass.forEachProgramMethod(synthesizedMethods::add);
+    assert emulateInterfaceClass.getType()
+        == InterfaceMethodRewriter.getEmulateLibraryInterfaceClassType(
+            emulatedInterface.type, appView.dexItemFactory());
     return emulateInterfaceClass;
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceMethodRewriter.java b/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceMethodRewriter.java
index 782ff63..bdd6064 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceMethodRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceMethodRewriter.java
@@ -345,8 +345,8 @@
       // behavior.
       if (cfInvoke.isInterface()) {
         leavingStaticInvokeToInterface(context);
+        warnMissingType(context, invokedMethod.holder);
       }
-      warnMissingType(context, invokedMethod.holder);
       return;
     }
 
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceProcessor.java b/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceProcessor.java
index bd95f96..6ba6863 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceProcessor.java
@@ -126,9 +126,8 @@
     DexProgramClass companionClass =
         appView
             .getSyntheticItems()
-            .ensureFixedClassWhileMigrating(
+            .ensureFixedClass(
                 SyntheticNaming.SyntheticKind.COMPANION_CLASS,
-                rewriter.getCompanionClassType(iface.type),
                 iface,
                 appView,
                 builder -> {
@@ -139,7 +138,7 @@
                   processVirtualInterfaceMethods(iface, builder);
                   processDirectInterfaceMethods(iface, builder);
                 });
-
+    assert companionClass.getType() == rewriter.getCompanionClassType(iface.type);
     assert companionClass.hasMethods();
 
     // D8 and R8 don't need to optimize the methods since they are just moved from interfaces and
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinClassMetadataReader.java b/src/main/java/com/android/tools/r8/kotlin/KotlinClassMetadataReader.java
index bfabd6c..7f4c47d 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinClassMetadataReader.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinClassMetadataReader.java
@@ -33,6 +33,8 @@
 
 public final class KotlinClassMetadataReader {
 
+  private static final int SYNTHETIC_CLASS_KIND = 3;
+
   public static KotlinClassLevelInfo getKotlinInfo(
       DexClass clazz, AppView<?> appView, Consumer<DexEncodedMethod> keepByteCode) {
     DexAnnotation meta =
@@ -75,16 +77,25 @@
   public static boolean isLambda(AppView<?> appView, DexClass clazz) {
     DexItemFactory dexItemFactory = appView.dexItemFactory();
     Kotlin kotlin = dexItemFactory.kotlin;
+    Flavour flavour = getFlavour(clazz, kotlin);
+    if (flavour == Flavour.Unclassified) {
+      return false;
+    }
     DexAnnotation metadataAnnotation =
         clazz.annotations().getFirstMatching(dexItemFactory.kotlinMetadataType);
-    if (metadataAnnotation != null) {
-      KotlinClassMetadata kMetadata = toKotlinClassMetadata(kotlin, metadataAnnotation.annotation);
+    if (metadataAnnotation == null) {
+      return false;
+    }
+    Map<DexString, DexAnnotationElement> elementMap = toElementMap(metadataAnnotation.annotation);
+    if (getKind(kotlin, elementMap) == SYNTHETIC_CLASS_KIND) {
+      KotlinClassMetadata kMetadata = toKotlinClassMetadata(kotlin, elementMap);
       if (kMetadata instanceof SyntheticClass) {
-        SyntheticClass syntheticClass = (SyntheticClass) kMetadata;
-        return syntheticClass.isLambda()
-            && getFlavour(syntheticClass, clazz, kotlin) != Flavour.Unclassified;
+        return ((SyntheticClass) kMetadata).isLambda();
       }
     }
+    assert toKotlinClassMetadata(kotlin, elementMap) instanceof SyntheticClass
+            == (getKind(kotlin, elementMap) == SYNTHETIC_CLASS_KIND)
+        : "Synthetic class kinds should agree";
     return false;
   }
 
@@ -98,16 +109,21 @@
 
   public static KotlinClassMetadata toKotlinClassMetadata(
       Kotlin kotlin, DexEncodedAnnotation metadataAnnotation) {
+    return toKotlinClassMetadata(kotlin, toElementMap(metadataAnnotation));
+  }
+
+  private static Map<DexString, DexAnnotationElement> toElementMap(
+      DexEncodedAnnotation metadataAnnotation) {
     Map<DexString, DexAnnotationElement> elementMap = new IdentityHashMap<>();
     for (DexAnnotationElement element : metadataAnnotation.elements) {
       elementMap.put(element.name, element);
     }
+    return elementMap;
+  }
 
-    DexAnnotationElement kind = elementMap.get(kotlin.metadata.kind);
-    if (kind == null) {
-      throw new MetadataError("element 'k' is missing.");
-    }
-    Integer k = (Integer) kind.value.getBoxedValue();
+  private static KotlinClassMetadata toKotlinClassMetadata(
+      Kotlin kotlin, Map<DexString, DexAnnotationElement> elementMap) {
+    int k = getKind(kotlin, elementMap);
     DexAnnotationElement metadataVersion = elementMap.get(kotlin.metadata.metadataVersion);
     int[] mv = metadataVersion == null ? null : getUnboxedIntArray(metadataVersion.value, "mv");
     DexAnnotationElement bytecodeVersion = elementMap.get(kotlin.metadata.bytecodeVersion);
@@ -127,6 +143,14 @@
     return KotlinClassMetadata.read(header);
   }
 
+  private static int getKind(Kotlin kotlin, Map<DexString, DexAnnotationElement> elementMap) {
+    DexAnnotationElement kind = elementMap.get(kotlin.metadata.kind);
+    if (kind == null) {
+      throw new MetadataError("element 'k' is missing.");
+    }
+    return (Integer) kind.value.getBoxedValue();
+  }
+
   public static KotlinClassLevelInfo createKotlinInfo(
       Kotlin kotlin,
       DexClass clazz,
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataEnqueuerExtension.java b/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataEnqueuerExtension.java
index fc56e4e..8a4d337 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataEnqueuerExtension.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataEnqueuerExtension.java
@@ -23,6 +23,7 @@
 import com.android.tools.r8.ir.optimize.info.OptimizationFeedbackSimple;
 import com.android.tools.r8.shaking.Enqueuer;
 import com.android.tools.r8.shaking.Enqueuer.EnqueuerDefinitionSupplier;
+import com.android.tools.r8.shaking.KeepClassInfo;
 import com.google.common.collect.Sets;
 import java.util.Set;
 
@@ -49,28 +50,31 @@
 
   @Override
   public void done(Enqueuer enqueuer) {
-    DexType kotlinMetadataType = appView.dexItemFactory().kotlinMetadataType;
-    DexClass kotlinMetadataClass =
-        appView.appInfo().definitionForWithoutExistenceAssert(kotlinMetadataType);
     // In the first round of tree shaking build up all metadata such that it can be traced later.
-    boolean keepMetadata =
-        kotlinMetadataClass == null
-            || kotlinMetadataClass.isNotProgramClass()
-            || enqueuer.isPinned(kotlinMetadataType);
+    boolean keepKotlinMetadata =
+        KeepClassInfo.isKotlinMetadataClassKept(
+            appView.dexItemFactory(),
+            appView.appInfo()::definitionForWithoutExistenceAssert,
+            enqueuer::getKeepInfo);
+    // In the first round of tree shaking build up all metadata such that it can be traced later.
     if (enqueuer.getMode().isInitialTreeShaking()) {
       Set<DexMethod> keepByteCodeFunctions = Sets.newIdentityHashSet();
       Set<DexProgramClass> localOrAnonymousClasses = Sets.newIdentityHashSet();
       enqueuer.forAllLiveClasses(
           clazz -> {
             assert clazz.getKotlinInfo().isNoKotlinInformation();
-            if (!keepMetadata || !enqueuer.isPinned(clazz.getType())) {
+            if (enqueuer
+                .getKeepInfo(clazz)
+                .isKotlinMetadataRemovalAllowed(appView.options(), keepKotlinMetadata)) {
               if (KotlinClassMetadataReader.isLambda(appView, clazz)
                   && clazz.hasClassInitializer()) {
                 feedback.classInitializerMayBePostponed(clazz.getClassInitializer());
               }
-              clazz.setKotlinInfo(getNoKotlinInfo());
+              clazz.clearKotlinInfo();
               clazz.removeAnnotations(
-                  annotation -> annotation.getAnnotationType() == kotlinMetadataType);
+                  annotation ->
+                      annotation.getAnnotationType()
+                          == appView.dexItemFactory().kotlinMetadataType);
             } else {
               clazz.setKotlinInfo(
                   KotlinClassMetadataReader.getKotlinInfo(
@@ -104,15 +108,20 @@
       assert enqueuer.getMode().isFinalTreeShaking();
       enqueuer.forAllLiveClasses(
           clazz -> {
-            if (!enqueuer.isPinned(clazz.getType())) {
-              clazz.setKotlinInfo(getNoKotlinInfo());
+            if (enqueuer
+                .getKeepInfo(clazz)
+                .isKotlinMetadataRemovalAllowed(appView.options(), keepKotlinMetadata)) {
+              clazz.clearKotlinInfo();
               clazz.members().forEach(DexEncodedMember::clearKotlinInfo);
               clazz.removeAnnotations(
-                  annotation -> annotation.getAnnotationType() == kotlinMetadataType);
+                  annotation ->
+                      annotation.getAnnotationType()
+                          == appView.dexItemFactory().kotlinMetadataType);
             } else {
-              assert !hasKotlinClassMetadataAnnotation(clazz, definitionsForContext(clazz))
-                  || !keepMetadata
-                  || clazz.getKotlinInfo() != getNoKotlinInfo();
+              // Use the concrete getNoKotlinInfo() instead of isNoKotlinInformation() to handle
+              // invalid kotlin info as well.
+              assert hasKotlinClassMetadataAnnotation(clazz, definitionsForContext(clazz))
+                  == (clazz.getKotlinInfo() != getNoKotlinInfo());
             }
           });
     }
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinSyntheticClassInfo.java b/src/main/java/com/android/tools/r8/kotlin/KotlinSyntheticClassInfo.java
index 08e95b3..ee55f8b 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinSyntheticClassInfo.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinSyntheticClassInfo.java
@@ -11,7 +11,6 @@
 import com.android.tools.r8.utils.Pair;
 import kotlinx.metadata.KmLambda;
 import kotlinx.metadata.jvm.KotlinClassHeader;
-import kotlinx.metadata.jvm.KotlinClassMetadata;
 import kotlinx.metadata.jvm.KotlinClassMetadata.SyntheticClass;
 import kotlinx.metadata.jvm.KotlinClassMetadata.SyntheticClass.Writer;
 
@@ -55,7 +54,7 @@
             ? KotlinLambdaInfo.create(
                 clazz, lambda, appView.dexItemFactory(), appView.reporter(), extensionInformation)
             : null,
-        getFlavour(syntheticClass, clazz, kotlin),
+        getFlavour(clazz, kotlin),
         packageName,
         metadataVersion);
   }
@@ -64,14 +63,6 @@
     return lambda != null && flavour != Flavour.Unclassified;
   }
 
-  public boolean isKotlinStyleLambda() {
-    return flavour == Flavour.KotlinStyleLambda;
-  }
-
-  public boolean isJavaStyleLambda() {
-    return flavour == Flavour.JavaStyleLambda;
-  }
-
   @Override
   public boolean isSyntheticClass() {
     return true;
@@ -112,23 +103,17 @@
     return metadataVersion;
   }
 
-  public static Flavour getFlavour(
-      KotlinClassMetadata.SyntheticClass metadata, DexClass clazz, Kotlin kotlin) {
-    // Returns KotlinStyleLambda if the given clazz is a Kotlin-style lambda:
-    //   a class that
-    //     1) is recognized as lambda in its Kotlin metadata;
-    //     2) directly extends kotlin.jvm.internal.Lambda
-    if (metadata.isLambda() && clazz.superType == kotlin.functional.lambdaType) {
+  public static Flavour getFlavour(DexClass clazz, Kotlin kotlin) {
+    // Returns KotlinStyleLambda if the given clazz has shape of a Kotlin-style lambda:
+    //   a class that directly extends kotlin.jvm.internal.Lambda
+    if (clazz.superType == kotlin.functional.lambdaType) {
       return Flavour.KotlinStyleLambda;
     }
-    // Returns JavaStyleLambda if the given clazz is a Java-style lambda:
+    // Returns JavaStyleLambda if the given clazz has shape of a Java-style lambda:
     //  a class that
-    //    1) is recognized as lambda in its Kotlin metadata;
-    //    2) doesn't extend any other class;
-    //    3) directly implements only one Java SAM.
-    if (metadata.isLambda()
-        && clazz.superType == kotlin.factory.objectType
-        && clazz.interfaces.size() == 1) {
+    //    1) doesn't extend any other class;
+    //    2) directly implements only one Java SAM.
+    if (clazz.superType == kotlin.factory.objectType && clazz.interfaces.size() == 1) {
       return Flavour.JavaStyleLambda;
     }
     return Flavour.Unclassified;
diff --git a/src/main/java/com/android/tools/r8/shaking/AnnotationRemover.java b/src/main/java/com/android/tools/r8/shaking/AnnotationRemover.java
index e1eabf2..567b7cb 100644
--- a/src/main/java/com/android/tools/r8/shaking/AnnotationRemover.java
+++ b/src/main/java/com/android/tools/r8/shaking/AnnotationRemover.java
@@ -10,7 +10,7 @@
 import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexDefinition;
 import com.android.tools.r8.graph.DexEncodedAnnotation;
-import com.android.tools.r8.graph.DexEncodedField;
+import com.android.tools.r8.graph.DexEncodedMember;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexProgramClass;
@@ -168,11 +168,12 @@
       stripAttributes(clazz);
       clazz.setAnnotations(
           clazz.annotations().rewrite(annotation -> rewriteAnnotation(clazz, annotation)));
-      // Kotlin properties are split over fields and methods. Check if any is pinned before pruning
-      // the information.
+      // Kotlin metadata for classes are removed in the KotlinMetadataEnqueuerExtension. Kotlin
+      // properties are split over fields and methods. Check if any is pinned before pruning the
+      // information.
       Set<KotlinPropertyInfo> pinnedKotlinProperties = Sets.newIdentityHashSet();
-      clazz.forEachMethod(method -> processMethod(method, clazz, pinnedKotlinProperties));
-      clazz.forEachField(field -> processField(field, clazz, pinnedKotlinProperties));
+      clazz.forEachProgramMember(
+          member -> processMember(member.getDefinition(), clazz, pinnedKotlinProperties));
       clazz.forEachProgramMember(
           member -> {
             KotlinMemberLevelInfo kotlinInfo = member.getKotlinInfo();
@@ -182,40 +183,43 @@
             }
           });
     }
+    assert verifyNoKeptKotlinMembersForClassesWithNoKotlinInfo();
   }
 
-  private void processMethod(
-      DexEncodedMethod method,
-      DexProgramClass clazz,
-      Set<KotlinPropertyInfo> pinnedKotlinProperties) {
-    method.setAnnotations(
-        method.annotations().rewrite(annotation -> rewriteAnnotation(method, annotation)));
-    method.parameterAnnotationsList =
-        method.parameterAnnotationsList.keepIf(this::filterParameterAnnotations);
-    KeepMethodInfo methodInfo = appView.getKeepInfo().getMethodInfo(method, clazz);
-    if (methodInfo.isSignatureAttributeRemovalAllowed(options)) {
-      method.clearGenericSignature();
+  private boolean verifyNoKeptKotlinMembersForClassesWithNoKotlinInfo() {
+    for (DexProgramClass clazz : appView.appInfo().classes()) {
+      if (clazz.getKotlinInfo().isNoKotlinInformation()) {
+        clazz.forEachProgramMember(
+            member -> {
+              assert member.getKotlinInfo().isNoKotlinInformation()
+                  : "Should have pruned kotlin info";
+            });
+      }
     }
-    if (!methodInfo.isPinned() && method.getKotlinInfo().isFunction()) {
-      method.clearKotlinMemberInfo();
-    }
-    if (methodInfo.isPinned() && method.getKotlinInfo().isProperty()) {
-      pinnedKotlinProperties.add(method.getKotlinInfo().asProperty());
-    }
+    return true;
   }
 
-  private void processField(
-      DexEncodedField field,
+  private void processMember(
+      DexEncodedMember<?, ?> member,
       DexProgramClass clazz,
       Set<KotlinPropertyInfo> pinnedKotlinProperties) {
-    field.setAnnotations(
-        field.annotations().rewrite(annotation -> rewriteAnnotation(field, annotation)));
-    KeepFieldInfo fieldInfo = appView.getKeepInfo().getFieldInfo(field, clazz);
-    if (fieldInfo.isSignatureAttributeRemovalAllowed(options)) {
-      field.clearGenericSignature();
+    member.setAnnotations(
+        member.annotations().rewrite(annotation -> rewriteAnnotation(member, annotation)));
+    if (member.isDexEncodedMethod()) {
+      DexEncodedMethod method = member.asDexEncodedMethod();
+      method.parameterAnnotationsList =
+          method.parameterAnnotationsList.keepIf(this::filterParameterAnnotations);
     }
-    if (fieldInfo.isPinned() && field.getKotlinInfo().isProperty()) {
-      pinnedKotlinProperties.add(field.getKotlinInfo().asProperty());
+    KeepMemberInfo<?, ?> memberInfo = appView.getKeepInfo().getMemberInfo(member, clazz);
+    if (memberInfo.isSignatureAttributeRemovalAllowed(options)) {
+      member.clearGenericSignature();
+    }
+    if (!member.getKotlinInfo().isProperty() && memberInfo.isKotlinMetadataRemovalAllowed(clazz)) {
+      member.clearKotlinInfo();
+    }
+    // Postpone removal of kotlin property info until we have seen all fields, setters and getters.
+    if (member.getKotlinInfo().isProperty() && !memberInfo.isKotlinMetadataRemovalAllowed(clazz)) {
+      pinnedKotlinProperties.add(member.getKotlinInfo().asProperty());
     }
   }
 
@@ -266,20 +270,18 @@
     // need to keep the enclosing method and inner classes attributes, if requested. In Proguard
     // compatibility mode we keep these attributes independent of whether the given class is kept.
     // In full mode we remove the attribute if not both sides are kept.
+    KeepClassInfo keepInfo = appView.getKeepInfo().getClassInfo(clazz);
     clazz.removeEnclosingMethodAttribute(
         enclosingMethodAttribute ->
-            appView
-                .getKeepInfo()
-                .getClassInfo(clazz)
-                .isEnclosingMethodAttributeRemovalAllowed(
-                    options, enclosingMethodAttribute, appView));
+            keepInfo.isEnclosingMethodAttributeRemovalAllowed(
+                options, enclosingMethodAttribute, appView));
     // It is important that the call to getEnclosingMethodAttribute is done after we potentially
     // pruned it above.
     clazz.removeInnerClasses(
         attribute ->
             canRemoveInnerClassAttribute(clazz, attribute, clazz.getEnclosingMethodAttribute()));
     if (clazz.getClassSignature().isValid()
-        && appView.getKeepInfo().getClassInfo(clazz).isSignatureAttributeRemovalAllowed(options)) {
+        && keepInfo.isSignatureAttributeRemovalAllowed(options)) {
       clazz.clearClassSignature();
     }
   }
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 1023e70..61db1bf 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -653,8 +653,8 @@
     return clazz;
   }
 
-  public boolean isPinned(DexType type) {
-    return keepInfo.isPinned(type, appInfo);
+  public KeepClassInfo getKeepInfo(DexProgramClass clazz) {
+    return keepInfo.getClassInfo(clazz);
   }
 
   private void addLiveNonProgramType(
diff --git a/src/main/java/com/android/tools/r8/shaking/GlobalKeepInfoConfiguration.java b/src/main/java/com/android/tools/r8/shaking/GlobalKeepInfoConfiguration.java
index cc11777..07f625d 100644
--- a/src/main/java/com/android/tools/r8/shaking/GlobalKeepInfoConfiguration.java
+++ b/src/main/java/com/android/tools/r8/shaking/GlobalKeepInfoConfiguration.java
@@ -21,4 +21,6 @@
   boolean isKeepEnclosingMethodAttributeEnabled();
 
   boolean isKeepInnerClassesAttributeEnabled();
+
+  boolean isKeepRuntimeVisibleAnnotationsEnabled();
 }
diff --git a/src/main/java/com/android/tools/r8/shaking/KeepClassInfo.java b/src/main/java/com/android/tools/r8/shaking/KeepClassInfo.java
index 40b2c9e..a12ca25 100644
--- a/src/main/java/com/android/tools/r8/shaking/KeepClassInfo.java
+++ b/src/main/java/com/android/tools/r8/shaking/KeepClassInfo.java
@@ -3,6 +3,13 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.shaking;
 
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.DexType;
+import java.util.function.Function;
+
 /** Immutable keep requirements for a class. */
 public final class KeepClassInfo extends KeepInfo<KeepClassInfo.Builder, KeepClassInfo> {
 
@@ -41,6 +48,31 @@
         && !internalIsAccessModificationRequiredForRepackaging();
   }
 
+  public boolean isKotlinMetadataRemovalAllowed(
+      GlobalKeepInfoConfiguration configuration, boolean kotlinMetadataKept) {
+    return !kotlinMetadataKept
+        || !isPinned()
+        || !configuration.isKeepRuntimeVisibleAnnotationsEnabled();
+  }
+
+  public static boolean isKotlinMetadataClassKept(AppView<?> appView) {
+    return isKotlinMetadataClassKept(
+        appView.dexItemFactory(),
+        appView.appInfo()::definitionForWithoutExistenceAssert,
+        appView.getKeepInfo()::getClassInfo);
+  }
+
+  public static boolean isKotlinMetadataClassKept(
+      DexItemFactory factory,
+      Function<DexType, DexClass> definitionForWithoutExistenceAssert,
+      Function<DexProgramClass, KeepClassInfo> getClassInfo) {
+    DexType kotlinMetadataType = factory.kotlinMetadataType;
+    DexClass kotlinMetadataClass = definitionForWithoutExistenceAssert.apply(kotlinMetadataType);
+    return kotlinMetadataClass == null
+        || kotlinMetadataClass.isNotProgramClass()
+        || getClassInfo.apply(kotlinMetadataClass.asProgramClass()).isPinned();
+  }
+
   @Override
   public boolean isTop() {
     return this.equals(top());
diff --git a/src/main/java/com/android/tools/r8/shaking/KeepMemberInfo.java b/src/main/java/com/android/tools/r8/shaking/KeepMemberInfo.java
index 770e84c..46d8fa2 100644
--- a/src/main/java/com/android/tools/r8/shaking/KeepMemberInfo.java
+++ b/src/main/java/com/android/tools/r8/shaking/KeepMemberInfo.java
@@ -3,6 +3,7 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.shaking;
 
+import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.shaking.KeepInfo.Builder;
 
 /** Immutable keep requirements for a member. */
@@ -18,4 +19,10 @@
     return configuration.isRepackagingEnabled()
         && !internalIsAccessModificationRequiredForRepackaging();
   }
+
+  public boolean isKotlinMetadataRemovalAllowed(DexProgramClass holder) {
+    // Checking the holder for missing kotlin information relies on the holder being processed
+    // before members.
+    return holder.getKotlinInfo().isNoKotlinInformation() || !isPinned();
+  }
 }
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 a07b61b..89552fb 100644
--- a/src/main/java/com/android/tools/r8/synthesis/SyntheticFinalization.java
+++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticFinalization.java
@@ -566,12 +566,16 @@
                     groups.get(i - 1), group, appView.graphLens(), classToFeatureSplitMap);
             SyntheticKind kind = group.getRepresentative().getKind();
             DexType representativeType =
-                createExternalType(
-                    kind,
-                    externalSyntheticTypePrefix,
-                    generators,
-                    appView,
-                    equivalences::containsKey);
+                intermediate
+                        && synthetics.isSyntheticInput(
+                            group.getRepresentative().getHolder().asProgramClass())
+                    ? group.getRepresentative().getHolder().getType()
+                    : createExternalType(
+                        kind,
+                        externalSyntheticTypePrefix,
+                        generators,
+                        appView,
+                        equivalences::containsKey);
             equivalences.put(representativeType, group);
           }
         });
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 78c046f..15719ce 100644
--- a/src/main/java/com/android/tools/r8/synthesis/SyntheticItems.java
+++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticItems.java
@@ -26,6 +26,7 @@
 import com.android.tools.r8.synthesis.SyntheticNaming.SyntheticKind;
 import com.android.tools.r8.utils.IterableUtils;
 import com.android.tools.r8.utils.ListUtils;
+import com.android.tools.r8.utils.StringDiagnostic;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.Iterables;
 import java.util.ArrayList;
@@ -126,6 +127,21 @@
     // TODO(b/158159959): Consider populating the input synthetics when identified.
     for (DexProgramClass clazz : appView.appInfo().classes()) {
       SyntheticMarker marker = SyntheticMarker.stripMarkerFromClass(clazz, appView);
+      if (!appView.options().intermediate && marker.getContext() != null) {
+        DexClass contextClass =
+            appView
+                .appInfo()
+                .definitionForWithoutExistenceAssert(
+                    marker.getContext().getSynthesizingContextType());
+        if (contextClass == null || contextClass.isNotProgramClass()) {
+          appView
+              .reporter()
+              .error(
+                  new StringDiagnostic(
+                      "Attempt at compiling intermediate artifact without its context",
+                      clazz.getOrigin()));
+        }
+      }
       if (marker.isSyntheticMethods()) {
         clazz.forEachProgramMethod(
             // TODO(b/158159959): Support having multiple methods per class.
@@ -439,6 +455,20 @@
     return legacyItem;
   }
 
+  private DexProgramClass internalCreateClass(
+      SyntheticKind kind,
+      Consumer<SyntheticProgramClassBuilder> fn,
+      SynthesizingContext outerContext,
+      DexType type,
+      DexItemFactory factory) {
+    SyntheticProgramClassBuilder classBuilder =
+        new SyntheticProgramClassBuilder(type, outerContext, factory);
+    fn.accept(classBuilder);
+    DexProgramClass clazz = classBuilder.build();
+    addPendingDefinition(new SyntheticProgramClassDefinition(kind, outerContext, clazz));
+    return clazz;
+  }
+
   public DexProgramClass createClass(
       SyntheticKind kind,
       UniqueContext context,
@@ -450,12 +480,7 @@
     DexType type =
         SyntheticNaming.createInternalType(
             kind, outerContext, context.getSyntheticSuffix(), appView.dexItemFactory());
-    SyntheticProgramClassBuilder classBuilder =
-        new SyntheticProgramClassBuilder(type, outerContext, appView.dexItemFactory());
-    fn.accept(classBuilder);
-    DexProgramClass clazz = classBuilder.build();
-    addPendingDefinition(new SyntheticProgramClassDefinition(kind, outerContext, clazz));
-    return clazz;
+    return internalCreateClass(kind, fn, outerContext, type, appView.dexItemFactory());
   }
 
   // TODO(b/172194101): Make this take a unique context.
@@ -468,38 +493,42 @@
     // This is to ensure a flat input-type -> synthetic-item mapping.
     SynthesizingContext outerContext = getSynthesizingContext(context, appView);
     DexType type = SyntheticNaming.createFixedType(kind, outerContext, appView.dexItemFactory());
-    SyntheticProgramClassBuilder classBuilder =
-        new SyntheticProgramClassBuilder(type, outerContext, appView.dexItemFactory());
-    fn.accept(classBuilder);
-    DexProgramClass clazz = classBuilder.build();
-    addPendingDefinition(new SyntheticProgramClassDefinition(kind, outerContext, clazz));
-    return clazz;
+    return internalCreateClass(kind, fn, outerContext, type, appView.dexItemFactory());
   }
 
-  // This is a temporary API for migration to the hygienic synthetic, the classes created behave
-  // like a hygienic synthetic, but use the legacyType passed as parameter instead of the
-  // hygienic type.
-  public DexProgramClass ensureFixedClassWhileMigrating(
+  /**
+   * Ensure that a fixed synthetic class exists.
+   *
+   * <p>This method is thread safe and will synchronize based on the context of the fixed synthetic.
+   */
+  public DexProgramClass ensureFixedClass(
       SyntheticKind kind,
-      DexType legacyType,
       DexProgramClass context,
       AppView<?> appView,
       Consumer<SyntheticProgramClassBuilder> fn) {
+    assert kind.isFixedSuffixSynthetic;
+    // Obtain the outer synthesizing context in the case the context itself is synthetic.
+    // This is to ensure a flat input-type -> synthetic-item mapping.
+    SynthesizingContext outerContext = getSynthesizingContext(context, appView);
+    DexType type = SyntheticNaming.createFixedType(kind, outerContext, appView.dexItemFactory());
+    // Fast path is that the synthetic is already present. If so it must be a program class.
+    DexClass clazz = appView.definitionFor(type, context);
+    if (clazz != null) {
+      assert isSyntheticClass(type);
+      assert clazz.isProgramClass();
+      return clazz.asProgramClass();
+    }
+    // Slow path creates the class using the context to make it thread safe.
     synchronized (context) {
-      DexClass dexClass = appView.definitionFor(legacyType);
-      if (dexClass != null) {
-        assert dexClass.isProgramClass();
-        return dexClass.asProgramClass();
+      // Recheck if it is present now the lock is held.
+      clazz = appView.definitionFor(type, context);
+      if (clazz != null) {
+        assert isSyntheticClass(type);
+        assert clazz.isProgramClass();
+        return clazz.asProgramClass();
       }
-      // Obtain the outer synthesizing context in the case the context itself is synthetic.
-      // This is to ensure a flat input-type -> synthetic-item mapping.
-      SynthesizingContext outerContext = getSynthesizingContext(context, appView);
-      SyntheticProgramClassBuilder classBuilder =
-          new SyntheticProgramClassBuilder(legacyType, outerContext, appView.dexItemFactory());
-      fn.accept(classBuilder);
-      DexProgramClass clazz = classBuilder.build();
-      addPendingDefinition(new SyntheticProgramClassDefinition(kind, outerContext, clazz));
-      return clazz;
+      assert !isSyntheticClass(type);
+      return internalCreateClass(kind, fn, outerContext, type, appView.dexItemFactory());
     }
   }
 
@@ -570,12 +599,7 @@
     // This is to ensure a flat input-type -> synthetic-item mapping.
     SynthesizingContext outerContext = SynthesizingContext.fromType(contextType);
     DexType type = SyntheticNaming.createFixedType(kind, outerContext, factory);
-    SyntheticProgramClassBuilder classBuilder =
-        new SyntheticProgramClassBuilder(type, outerContext, factory);
-    fn.accept(classBuilder);
-    DexProgramClass clazz = classBuilder.build();
-    addPendingDefinition(new SyntheticProgramClassDefinition(kind, outerContext, clazz));
-    return clazz;
+    return internalCreateClass(kind, fn, outerContext, type, factory);
   }
 
   /** Create a single synthetic method item. */
diff --git a/src/main/java/com/android/tools/r8/synthesis/SyntheticNaming.java b/src/main/java/com/android/tools/r8/synthesis/SyntheticNaming.java
index 20f0ab4..6ed6c0e 100644
--- a/src/main/java/com/android/tools/r8/synthesis/SyntheticNaming.java
+++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticNaming.java
@@ -24,8 +24,8 @@
   public enum SyntheticKind {
     // Class synthetics.
     RECORD_TAG("", 1, false, true, true),
-    COMPANION_CLASS("-CC", 2, false, true),
-    EMULATED_INTERFACE_CLASS("-EL", 3, false, true),
+    COMPANION_CLASS("$-CC", 2, false, true),
+    EMULATED_INTERFACE_CLASS("$-EL", 3, false, true),
     LAMBDA("Lambda", 4, false),
     INIT_TYPE_ARGUMENT("-IA", 5, false, true),
     HORIZONTAL_INIT_TYPE_ARGUMENT_1(SYNTHETIC_CLASS_SEPARATOR + "IA$1", 6, false, true),
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 9003eca..e08fe12 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -615,6 +615,11 @@
     return proguardConfiguration.getKeepAttributes().innerClasses;
   }
 
+  @Override
+  public boolean isKeepRuntimeVisibleAnnotationsEnabled() {
+    return proguardConfiguration.getKeepAttributes().runtimeVisibleAnnotations;
+  }
+
   /**
    * If any non-static class merging is enabled, information about types referred to by instanceOf
    * and check cast instructions needs to be collected.
@@ -1218,7 +1223,6 @@
         !Version.isDevelopmentVersion()
             || System.getProperty("com.android.tools.r8.disableHorizontalClassMerging") == null;
     private boolean enableInterfaceMergingInInitial = false;
-    private boolean enableInterfaceMergingInFinal = false;
     private boolean enableSyntheticMerging = true;
     private boolean ignoreRuntimeTypeChecksForTesting = false;
     private boolean restrictToSynthetics = false;
@@ -1273,7 +1277,7 @@
         return enableInterfaceMergingInInitial;
       }
       assert mode.isFinal();
-      return enableInterfaceMergingInFinal;
+      return true;
     }
 
     public boolean isRestrictedToSynthetics() {
@@ -1284,10 +1288,6 @@
       enableInterfaceMergingInInitial = true;
     }
 
-    public void setEnableInterfaceMergingInFinal() {
-      enableInterfaceMergingInFinal = true;
-    }
-
     public void setIgnoreRuntimeTypeChecksForTesting() {
       ignoreRuntimeTypeChecksForTesting = true;
     }
diff --git a/src/test/java/com/android/tools/r8/D8IncrementalRunExamplesAndroidOTest.java b/src/test/java/com/android/tools/r8/D8IncrementalRunExamplesAndroidOTest.java
index dfca4e2..1a9800a 100644
--- a/src/test/java/com/android/tools/r8/D8IncrementalRunExamplesAndroidOTest.java
+++ b/src/test/java/com/android/tools/r8/D8IncrementalRunExamplesAndroidOTest.java
@@ -8,6 +8,7 @@
 import static org.junit.Assert.assertEquals;
 
 import com.android.tools.r8.D8Command.Builder;
+import com.android.tools.r8.Disassemble.DisassembleCommand;
 import com.android.tools.r8.errors.CompilationError;
 import com.android.tools.r8.errors.InternalCompilerError;
 import com.android.tools.r8.errors.Unimplemented;
@@ -20,6 +21,7 @@
 import com.android.tools.r8.utils.AndroidApp;
 import com.android.tools.r8.utils.DescriptorUtils;
 import com.android.tools.r8.utils.OffOrAuto;
+import com.android.tools.r8.utils.StringUtils;
 import com.beust.jcommander.internal.Lists;
 import com.google.common.io.ByteStreams;
 import java.io.File;
@@ -292,6 +294,16 @@
     mergedFromCompiledSeparately.writeToZip(out2, OutputMode.DexIndexed);
     ToolHelper.runArtNoVerificationErrors(out2.toString(), testPackage + "." + mainClass);
 
+    Path dissasemble1 = temp.newFolder().toPath().resolve("disassemble1.txt");
+    Path dissasemble2 = temp.newFolder().toPath().resolve("disassemble2.txt");
+    Disassemble.disassemble(
+        DisassembleCommand.builder().addProgramFiles(out1).setOutputPath(dissasemble1).build());
+    Disassemble.disassemble(
+        DisassembleCommand.builder().addProgramFiles(out2).setOutputPath(dissasemble2).build());
+    String content1 = StringUtils.join("\n", Files.readAllLines(dissasemble1));
+    String content2 = StringUtils.join("\n", Files.readAllLines(dissasemble2));
+    assertEquals(content1, content2);
+
     Assert.assertArrayEquals(
         readResource(mergedFromCompiledSeparately.getDexProgramResourcesForTesting().get(0)),
         readResource(mergedFromCompiledTogether.getDexProgramResourcesForTesting().get(0)));
diff --git a/src/test/java/com/android/tools/r8/cf/bootstrap/BootstrapCurrentEqualityTest.java b/src/test/java/com/android/tools/r8/cf/bootstrap/BootstrapCurrentEqualityTest.java
index 4e9353b..a8d57c0 100644
--- a/src/test/java/com/android/tools/r8/cf/bootstrap/BootstrapCurrentEqualityTest.java
+++ b/src/test/java/com/android/tools/r8/cf/bootstrap/BootstrapCurrentEqualityTest.java
@@ -105,9 +105,7 @@
           .setMode(mode)
           .addProgramFiles(ToolHelper.R8_WITH_RELOCATED_DEPS_JAR)
           .addKeepRuleFiles(MAIN_KEEP)
-          .allowDiagnosticInfoMessages()
           .compile()
-          .apply(TestBase::verifyAllInfoFromGenericSignatureTypeParameterValidation)
           .apply(c -> FileUtils.writeTextFile(map, c.getProguardMap()))
           .writeToZip(jar);
     }
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/dispatch/OverrideAbstractMethodWithDefaultTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/dispatch/OverrideAbstractMethodWithDefaultTest.java
index 874c095..e2eef5f 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/dispatch/OverrideAbstractMethodWithDefaultTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/dispatch/OverrideAbstractMethodWithDefaultTest.java
@@ -35,8 +35,6 @@
                         !parameters.canUseDefaultAndStaticInterfaceMethods(),
                         i -> i.assertIsCompleteMergeGroup(B1.class, B2.class))
                     .assertNoOtherClassesMerged())
-        .addOptionsModification(
-            options -> options.horizontalClassMergerOptions().setEnableInterfaceMergingInFinal())
         .enableInliningAnnotations()
         .enableNeverClassInliningAnnotations()
         .enableNoVerticalClassMergingAnnotations()
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/dispatch/OverrideDefaultMethodTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/dispatch/OverrideDefaultMethodTest.java
index de8d7b2..0af1e85 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/dispatch/OverrideDefaultMethodTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/dispatch/OverrideDefaultMethodTest.java
@@ -26,8 +26,6 @@
     testForR8(parameters.getBackend())
         .addInnerClasses(getClass())
         .addKeepMainRule(Main.class)
-        .addOptionsModification(
-            options -> options.horizontalClassMergerOptions().setEnableInterfaceMergingInFinal())
         .enableInliningAnnotations()
         .enableNeverClassInliningAnnotations()
         .enableNoVerticalClassMergingAnnotations()
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/dispatch/OverrideDefaultOnSuperMethodTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/dispatch/OverrideDefaultOnSuperMethodTest.java
index 56be50b..7b5437e 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/dispatch/OverrideDefaultOnSuperMethodTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/dispatch/OverrideDefaultOnSuperMethodTest.java
@@ -31,8 +31,6 @@
     testForR8(parameters.getBackend())
         .addInnerClasses(getClass())
         .addKeepMainRule(Main.class)
-        .addOptionsModification(
-            options -> options.horizontalClassMergerOptions().setEnableInterfaceMergingInFinal())
         .enableInliningAnnotations()
         .enableNeverClassInliningAnnotations()
         .enableNoUnusedInterfaceRemovalAnnotations()
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/ClassHierarchyCycleCrossGroupMergingTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/ClassHierarchyCycleCrossGroupMergingTest.java
new file mode 100644
index 0000000..bbbec17
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/ClassHierarchyCycleCrossGroupMergingTest.java
@@ -0,0 +1,93 @@
+// 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.classmerging.horizontal.interfaces;
+
+import com.android.tools.r8.NoHorizontalClassMerging;
+import com.android.tools.r8.NoUnusedInterfaceRemoval;
+import com.android.tools.r8.NoVerticalClassMerging;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class ClassHierarchyCycleCrossGroupMergingTest extends TestBase {
+
+  private final TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  public ClassHierarchyCycleCrossGroupMergingTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void test() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(getClass())
+        .addKeepMainRule(Main.class)
+        .addHorizontallyMergedClassesInspector(
+            inspector ->
+                inspector.assertIsCompleteMergeGroup(I.class, J.class).assertNoOtherClassesMerged())
+        .enableNoHorizontalClassMergingAnnotations()
+        .enableNoUnusedInterfaceRemovalAnnotations()
+        .enableNoVerticalClassMergingAnnotations()
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithEmptyOutput();
+  }
+
+  static class Main implements I, J, JSub, K, KSub, L {
+
+    public static void main(String[] args) {}
+  }
+
+  // As inputs to the interface merging policy that is supposed to avoid that merging leads to
+  // cycles in the class hierarchy, we will have the merge group {I, J, K, L}. Note that the
+  // classes JSub and KSub are not eligible for class merging (@NoHorizontalClassMerging).
+  //
+  // From this, we will form the merge group {I, J}. Note that the classes K and L are not
+  // eligible for being added to {I, J}, since this would lead to a cycle in the class hierarchy:
+  // I inherits from KSub, which inherits from K, and L inherits from JSub, which inherits from J.
+  //
+  // As a result of this, we create a new merge group {K}. Note that L is not eligible for being
+  // merged into {K}, since that would also lead to a cycle in the class hierarchy. In particular,
+  // if we form {K, L}, then {K, L} inherits from JSub, which inherits from {I, J}, which
+  // inherits from KSub, which inherits from {K, L}.
+  //
+  // Therefore, none of K and L should be eligible for merging in this case.
+  @NoUnusedInterfaceRemoval
+  @NoVerticalClassMerging
+  interface I extends KSub {}
+
+  @NoUnusedInterfaceRemoval
+  @NoVerticalClassMerging
+  interface J {}
+
+  @NoHorizontalClassMerging
+  @NoUnusedInterfaceRemoval
+  @NoVerticalClassMerging
+  interface JSub extends J {}
+
+  @NoUnusedInterfaceRemoval
+  @NoVerticalClassMerging
+  interface K {}
+
+  @NoHorizontalClassMerging
+  @NoUnusedInterfaceRemoval
+  @NoVerticalClassMerging
+  interface KSub extends K {}
+
+  @NoUnusedInterfaceRemoval
+  @NoVerticalClassMerging
+  interface L extends JSub {}
+}
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/CollisionWithDefaultMethodOutsideMergeGroupAfterSubclassMergingTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/CollisionWithDefaultMethodOutsideMergeGroupAfterSubclassMergingTest.java
index c1912b5..cc19dd2 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/CollisionWithDefaultMethodOutsideMergeGroupAfterSubclassMergingTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/CollisionWithDefaultMethodOutsideMergeGroupAfterSubclassMergingTest.java
@@ -66,7 +66,6 @@
               if (enableInterfaceMergingInInitial) {
                 options.horizontalClassMergerOptions().setEnableInterfaceMergingInInitial();
               }
-              options.horizontalClassMergerOptions().setEnableInterfaceMergingInFinal();
             })
         .enableInliningAnnotations()
         .enableNeverClassInliningAnnotations()
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/CollisionWithDefaultMethodOutsideMergeGroupClassTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/CollisionWithDefaultMethodOutsideMergeGroupClassTest.java
index 1c64d9d..e18ce93 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/CollisionWithDefaultMethodOutsideMergeGroupClassTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/CollisionWithDefaultMethodOutsideMergeGroupClassTest.java
@@ -50,8 +50,6 @@
                 inspector.assertIsCompleteMergeGroup(I.class, J.class);
               }
             })
-        .addOptionsModification(
-            options -> options.horizontalClassMergerOptions().setEnableInterfaceMergingInFinal())
         .enableInliningAnnotations()
         .enableNeverClassInliningAnnotations()
         .enableNoHorizontalClassMergingAnnotations()
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/CollisionWithDefaultMethodOutsideMergeGroupLambdaTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/CollisionWithDefaultMethodOutsideMergeGroupLambdaTest.java
index 3a33b0f..2324f03 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/CollisionWithDefaultMethodOutsideMergeGroupLambdaTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/CollisionWithDefaultMethodOutsideMergeGroupLambdaTest.java
@@ -51,8 +51,6 @@
                 inspector.assertNoClassesMerged();
               }
             })
-        .addOptionsModification(
-            options -> options.horizontalClassMergerOptions().setEnableInterfaceMergingInFinal())
         .enableInliningAnnotations()
         .enableNeverClassInliningAnnotations()
         .enableNoHorizontalClassMergingAnnotations()
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/DisjointFunctionalInterfacesMergingTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/DisjointFunctionalInterfacesMergingTest.java
index c28bf26..05c63a7 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/DisjointFunctionalInterfacesMergingTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/DisjointFunctionalInterfacesMergingTest.java
@@ -4,6 +4,7 @@
 
 package com.android.tools.r8.classmerging.horizontal.interfaces;
 
+
 import com.android.tools.r8.NoUnusedInterfaceRemoval;
 import com.android.tools.r8.NoVerticalClassMerging;
 import com.android.tools.r8.TestBase;
@@ -34,8 +35,6 @@
         .addKeepMainRule(Main.class)
         .addHorizontallyMergedClassesInspector(
             inspector -> inspector.assertIsCompleteMergeGroup(I.class, J.class))
-        .addOptionsModification(
-            options -> options.horizontalClassMergerOptions().setEnableInterfaceMergingInFinal())
         .enableNoUnusedInterfaceRemovalAnnotations()
         .enableNoVerticalClassMergingAnnotations()
         .noClassInliningOfSynthetics()
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/DisjointFunctionalInterfacesWithIntersectionMergingTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/DisjointFunctionalInterfacesWithIntersectionMergingTest.java
index 8cfb7af..91f0fbe 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/DisjointFunctionalInterfacesWithIntersectionMergingTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/DisjointFunctionalInterfacesWithIntersectionMergingTest.java
@@ -35,8 +35,6 @@
         .addKeepMainRule(Main.class)
         .addHorizontallyMergedClassesInspector(
             inspector -> inspector.assertIsCompleteMergeGroup(I.class, J.class))
-        .addOptionsModification(
-            options -> options.horizontalClassMergerOptions().setEnableInterfaceMergingInFinal())
         .enableNoUnusedInterfaceRemovalAnnotations()
         .enableNoVerticalClassMergingAnnotations()
         .noClassInliningOfSynthetics()
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/DisjointFunctionalInterfacesWithSameNameAndDifferentParametersMergingTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/DisjointFunctionalInterfacesWithSameNameAndDifferentParametersMergingTest.java
index 13940fe..4588182 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/DisjointFunctionalInterfacesWithSameNameAndDifferentParametersMergingTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/DisjointFunctionalInterfacesWithSameNameAndDifferentParametersMergingTest.java
@@ -37,8 +37,6 @@
         .addKeepMainRule(Main.class)
         .addHorizontallyMergedClassesInspector(
             inspector -> inspector.assertIsCompleteMergeGroup(I.class, J.class))
-        .addOptionsModification(
-            options -> options.horizontalClassMergerOptions().setEnableInterfaceMergingInFinal())
         .enableNoUnusedInterfaceRemovalAnnotations()
         .enableNoVerticalClassMergingAnnotations()
         .noClassInliningOfSynthetics()
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/DisjointFunctionalInterfacesWithSameNameAndDifferentReturnTypeMergingTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/DisjointFunctionalInterfacesWithSameNameAndDifferentReturnTypeMergingTest.java
index 3236862..c4af611 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/DisjointFunctionalInterfacesWithSameNameAndDifferentReturnTypeMergingTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/DisjointFunctionalInterfacesWithSameNameAndDifferentReturnTypeMergingTest.java
@@ -37,8 +37,6 @@
         .addKeepMainRule(Main.class)
         .addHorizontallyMergedClassesInspector(
             inspector -> inspector.assertIsCompleteMergeGroup(I.class, J.class))
-        .addOptionsModification(
-            options -> options.horizontalClassMergerOptions().setEnableInterfaceMergingInFinal())
         .enableNoUnusedInterfaceRemovalAnnotations()
         .enableNoVerticalClassMergingAnnotations()
         .noClassInliningOfSynthetics()
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/DisjointInterfacesWithDefaultMethodsMergingTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/DisjointInterfacesWithDefaultMethodsMergingTest.java
index 2d1ac21..5a656d9 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/DisjointInterfacesWithDefaultMethodsMergingTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/DisjointInterfacesWithDefaultMethodsMergingTest.java
@@ -41,8 +41,6 @@
         .addKeepMainRule(Main.class)
         .addHorizontallyMergedClassesInspector(
             inspector -> inspector.assertIsCompleteMergeGroup(I.class, J.class))
-        .addOptionsModification(
-            options -> options.horizontalClassMergerOptions().setEnableInterfaceMergingInFinal())
         .enableInliningAnnotations()
         .enableNeverClassInliningAnnotations()
         .enableNoUnusedInterfaceRemovalAnnotations()
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/DisjointInterfacesWithoutDefaultMethodsMergingTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/DisjointInterfacesWithoutDefaultMethodsMergingTest.java
index 7ca2089..08d1d2c 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/DisjointInterfacesWithoutDefaultMethodsMergingTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/DisjointInterfacesWithoutDefaultMethodsMergingTest.java
@@ -41,8 +41,6 @@
         .addKeepMainRule(Main.class)
         .addHorizontallyMergedClassesInspector(
             inspector -> inspector.assertIsCompleteMergeGroup(I.class, J.class))
-        .addOptionsModification(
-            options -> options.horizontalClassMergerOptions().setEnableInterfaceMergingInFinal())
         .enableInliningAnnotations()
         .enableNeverClassInliningAnnotations()
         .enableNoUnusedInterfaceRemovalAnnotations()
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/EmptyInterfaceChainMergingTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/EmptyInterfaceChainMergingTest.java
index d059bbf..80160a4 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/EmptyInterfaceChainMergingTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/EmptyInterfaceChainMergingTest.java
@@ -42,8 +42,6 @@
                 inspector
                     .assertIsCompleteMergeGroup(I.class, J.class, K.class)
                     .assertNoOtherClassesMerged())
-        .addOptionsModification(
-            options -> options.horizontalClassMergerOptions().setEnableInterfaceMergingInFinal())
         .enableNoUnusedInterfaceRemovalAnnotations()
         .enableNoVerticalClassMergingAnnotations()
         .setMinApi(parameters.getApiLevel())
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/EmptyInterfacesMergingTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/EmptyInterfacesMergingTest.java
index e2b196c..53086a7 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/EmptyInterfacesMergingTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/EmptyInterfacesMergingTest.java
@@ -39,8 +39,6 @@
         .addKeepMainRule(Main.class)
         .addHorizontallyMergedClassesInspector(
             inspector -> inspector.assertIsCompleteMergeGroup(I.class, J.class))
-        .addOptionsModification(
-            options -> options.horizontalClassMergerOptions().setEnableInterfaceMergingInFinal())
         .enableNoUnusedInterfaceRemovalAnnotations()
         .enableNoVerticalClassMergingAnnotations()
         .setMinApi(parameters.getApiLevel())
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/IdenticalFunctionalInterfacesMergingTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/IdenticalFunctionalInterfacesMergingTest.java
index 100c54b..02239dd 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/IdenticalFunctionalInterfacesMergingTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/IdenticalFunctionalInterfacesMergingTest.java
@@ -35,8 +35,6 @@
         .addKeepMainRule(Main.class)
         .addHorizontallyMergedClassesInspector(
             inspector -> inspector.assertIsCompleteMergeGroup(I.class, J.class))
-        .addOptionsModification(
-            options -> options.horizontalClassMergerOptions().setEnableInterfaceMergingInFinal())
         .enableNoUnusedInterfaceRemovalAnnotations()
         .enableNoVerticalClassMergingAnnotations()
         .noClassInliningOfSynthetics()
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/IdenticalFunctionalInterfacesWithIntersectionMergingTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/IdenticalFunctionalInterfacesWithIntersectionMergingTest.java
index 645a0c8..44bda30 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/IdenticalFunctionalInterfacesWithIntersectionMergingTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/IdenticalFunctionalInterfacesWithIntersectionMergingTest.java
@@ -35,8 +35,6 @@
         .addKeepMainRule(Main.class)
         .addHorizontallyMergedClassesInspector(
             inspector -> inspector.assertIsCompleteMergeGroup(I.class, J.class))
-        .addOptionsModification(
-            options -> options.horizontalClassMergerOptions().setEnableInterfaceMergingInFinal())
         .enableNoUnusedInterfaceRemovalAnnotations()
         .enableNoVerticalClassMergingAnnotations()
         .noClassInliningOfSynthetics()
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/NoDefaultMethodMergingTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/NoDefaultMethodMergingTest.java
index 2284113..a84dbac 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/NoDefaultMethodMergingTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/NoDefaultMethodMergingTest.java
@@ -49,8 +49,6 @@
                 inspector.assertIsCompleteMergeGroup(I.class, J.class);
               }
             })
-        .addOptionsModification(
-            options -> options.horizontalClassMergerOptions().setEnableInterfaceMergingInFinal())
         .enableInliningAnnotations()
         .enableNeverClassInliningAnnotations()
         .enableNoHorizontalClassMergingAnnotations()
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontalstatic/StaticClassMergerInterfaceTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontalstatic/StaticClassMergerInterfaceTest.java
index 392c587..6905202 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontalstatic/StaticClassMergerInterfaceTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontalstatic/StaticClassMergerInterfaceTest.java
@@ -53,8 +53,6 @@
                     .assertNoOtherClassesMerged();
               }
             })
-        .addOptionsModification(
-            options -> options.horizontalClassMergerOptions().setEnableInterfaceMergingInFinal())
         .enableInliningAnnotations()
         .setMinApi(parameters.getApiLevel())
         .run(parameters.getRuntime(), TestClass.class)
diff --git a/src/test/java/com/android/tools/r8/d8/NonNamedMemberClassTest.java b/src/test/java/com/android/tools/r8/d8/NonNamedMemberClassTest.java
index a505c4f..9bb37c4 100644
--- a/src/test/java/com/android/tools/r8/d8/NonNamedMemberClassTest.java
+++ b/src/test/java/com/android/tools/r8/d8/NonNamedMemberClassTest.java
@@ -5,11 +5,9 @@
 
 import static org.hamcrest.CoreMatchers.containsString;
 
-import com.android.tools.r8.D8TestCompileResult;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
-import com.android.tools.r8.ToolHelper.DexVm;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
@@ -25,7 +23,7 @@
 
   @Parameterized.Parameters(name = "{0}")
   public static TestParametersCollection data() {
-    return getTestParameters().withDexRuntimes().build();
+    return getTestParameters().withDexRuntimes().withAllApiLevels().build();
   }
 
   public NonNamedMemberClassTest(TestParameters parameters) {
@@ -34,17 +32,12 @@
 
   @Test
   public void testD8() throws Exception {
-    D8TestCompileResult result =
-        testForD8()
-            .addProgramClassFileData(Dump.dump())
-            .setMinApi(parameters.getRuntime())
-            .compile();
-    if (parameters.getRuntime().asDex().getVm().isOlderThanOrEqual(DexVm.ART_6_0_1_HOST)) {
-      result.assertWarningMessageThatMatches(containsString("desugaring"));
-    } else {
-      result.assertOnlyInfos();
-    }
-    result.assertInfoMessageThatMatches(containsString("missing EnclosingMethod"));
+    testForD8()
+        .addProgramClassFileData(Dump.dump())
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .assertOnlyInfos()
+        .assertInfoMessageThatMatches(containsString("missing EnclosingMethod"));
   }
 
   // Compiled the following kt code:
diff --git a/src/test/java/com/android/tools/r8/desugar/backports/BackportDuplicationTest.java b/src/test/java/com/android/tools/r8/desugar/backports/BackportDuplicationTest.java
index 00d6d45..2453bef 100644
--- a/src/test/java/com/android/tools/r8/desugar/backports/BackportDuplicationTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/backports/BackportDuplicationTest.java
@@ -3,14 +3,24 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.desugar.backports;
 
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.hamcrest.MatcherAssert.assertThat;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
 import static org.junit.Assume.assumeTrue;
 
+import com.android.tools.r8.ByteDataView;
+import com.android.tools.r8.ClassFileConsumer;
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.DiagnosticsHandler;
 import com.android.tools.r8.OutputMode;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.ToolHelper.ArtCommandBuilder;
+import com.android.tools.r8.ToolHelper.ProcessResult;
 import com.android.tools.r8.desugar.backports.AbstractBackportTest.MiniAssert;
 import com.android.tools.r8.references.MethodReference;
 import com.android.tools.r8.synthesis.SyntheticItemsTestUtils;
@@ -24,6 +34,7 @@
 import com.google.common.collect.Sets;
 import com.google.common.collect.Sets.SetView;
 import java.nio.file.Path;
+import java.util.ArrayList;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Set;
@@ -191,6 +202,62 @@
         .inspect(this::checkExpectedSynthetics);
   }
 
+  @Test
+  public void testPerFileIntermediate() throws Exception {
+    ProcessResult result = runDoublePerFileCompilation(true);
+    assertEquals(result.toString(), 0, result.exitCode);
+    assertEquals(EXPECTED, result.stdout);
+  }
+
+  @Test
+  public void testPerFileNonIntermediate() throws Exception {
+    try {
+      runDoublePerFileCompilation(false);
+      fail("Should expect the compilation to fail.");
+    } catch (CompilationFailedException e) {
+      assertThat(
+          e.getCause().getMessage(),
+          containsString("Attempt at compiling intermediate artifact without its context"));
+    }
+  }
+
+  public ProcessResult runDoublePerFileCompilation(boolean intermediate) throws Exception {
+    List<byte[]> outputsRoundOne = new ArrayList<>();
+    testForD8(Backend.CF)
+        .addProgramClasses(CLASSES)
+        .setMinApi(parameters.getApiLevel())
+        .setIntermediate(true /* First round is always intermediate. */)
+        .setProgramConsumer(
+            new ClassFileConsumer.ForwardingConsumer(null) {
+              @Override
+              public void accept(ByteDataView data, String descriptor, DiagnosticsHandler handler) {
+                outputsRoundOne.add(data.copyByteData());
+              }
+            })
+        .compile();
+
+    List<Path> outputsRoundTwo = new ArrayList<>();
+    for (byte[] bytes : outputsRoundOne) {
+      outputsRoundTwo.add(
+          testForD8(parameters.getBackend())
+              .addProgramClassFileData(bytes)
+              .setMinApi(parameters.getApiLevel())
+              .setIntermediate(intermediate)
+              .compile()
+              .writeToZip());
+    }
+
+    if (parameters.isCfRuntime()) {
+      return ToolHelper.runJava(
+          parameters.getRuntime().asCf(), outputsRoundTwo, TestClass.class.getTypeName());
+    } else {
+      ArtCommandBuilder builder = new ArtCommandBuilder();
+      builder.setMainClass(TestClass.class.getTypeName());
+      outputsRoundTwo.forEach(p -> builder.appendClasspath(p.toAbsolutePath().toString()));
+      return ToolHelper.runArtRaw(builder);
+    }
+  }
+
   private void checkNoOriginalsAndNoInternalSynthetics(CodeInspector inspector) {
     inspector.forAllClasses(
         clazz -> {
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/SuperAPIConversionTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/SuperAPIConversionTest.java
new file mode 100644
index 0000000..d4e1c8f
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/SuperAPIConversionTest.java
@@ -0,0 +1,100 @@
+// 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.desugaredlibrary.conversiontests;
+
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.desugar.desugaredlibrary.DesugaredLibraryTestBase;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.BooleanUtils;
+import java.util.List;
+import java.util.Random;
+import java.util.stream.IntStream;
+import org.junit.Assume;
+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 SuperAPIConversionTest extends DesugaredLibraryTestBase {
+
+  private final TestParameters parameters;
+  private final boolean shrinkDesugaredLibrary;
+
+  private static final AndroidApiLevel MIN_SUPPORTED = AndroidApiLevel.N;
+
+  @Parameters(name = "{0}, shrinkDesugaredLibrary: {1}")
+  public static List<Object[]> data() {
+    return buildParameters(
+        getConversionParametersUpToExcluding(MIN_SUPPORTED), BooleanUtils.values());
+  }
+
+  public SuperAPIConversionTest(TestParameters parameters, boolean shrinkDesugaredLibrary) {
+    this.shrinkDesugaredLibrary = shrinkDesugaredLibrary;
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testAPIConversionNoDesugaring() throws Exception {
+    Assume.assumeTrue("No need to test twice", shrinkDesugaredLibrary);
+    testForD8()
+        .addInnerClasses(SuperAPIConversionTest.class)
+        .setMinApi(parameters.getApiLevel())
+        .run(parameters.getRuntime(), Executor.class)
+        .assertSuccessWithOutputLines("Head");
+  }
+
+  @Test
+  public void testAPIConversionDesugaringD8() throws Exception {
+    Assume.assumeFalse("TODO(b/189435770): fix", shrinkDesugaredLibrary);
+    KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters);
+    testForD8()
+        .addInnerClasses(SuperAPIConversionTest.class)
+        .setMinApi(parameters.getApiLevel())
+        .enableCoreLibraryDesugaring(parameters.getApiLevel(), keepRuleConsumer)
+        .compile()
+        .addDesugaredCoreLibraryRunClassPath(
+            this::buildDesugaredLibrary,
+            parameters.getApiLevel(),
+            keepRuleConsumer.get(),
+            shrinkDesugaredLibrary)
+        .run(parameters.getRuntime(), Executor.class)
+        .assertSuccessWithOutputLines("$r8$wrapper$java$util$stream$IntStream$-V-WRP");
+  }
+
+  @Test
+  public void testAPIConversionDesugaringR8() throws Exception {
+    Assume.assumeFalse("TODO(b/189435770): fix", shrinkDesugaredLibrary);
+    KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters);
+    testForR8(parameters.getBackend())
+        .addInnerClasses(SuperAPIConversionTest.class)
+        .setMinApi(parameters.getApiLevel())
+        .addKeepMainRule(Executor.class)
+        .enableCoreLibraryDesugaring(parameters.getApiLevel(), keepRuleConsumer)
+        .compile()
+        .addDesugaredCoreLibraryRunClassPath(
+            this::buildDesugaredLibrary,
+            parameters.getApiLevel(),
+            keepRuleConsumer.get(),
+            shrinkDesugaredLibrary)
+        .run(parameters.getRuntime(), Executor.class)
+        .assertSuccessWithOutputLines("$r8$wrapper$java$util$stream$IntStream$-V-WRP");
+  }
+
+  static class ParallelRandom extends Random {
+
+    @Override
+    public IntStream ints() {
+      return super.ints().parallel();
+    }
+  }
+
+  static class Executor {
+
+    public static void main(String[] args) {
+      IntStream intStream = new ParallelRandom().ints();
+      System.out.println(intStream.getClass().getSimpleName());
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/Java11R8BootstrapTest.java b/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/Java11R8BootstrapTest.java
index de1c949..1eac80e 100644
--- a/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/Java11R8BootstrapTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/Java11R8BootstrapTest.java
@@ -77,9 +77,7 @@
                       options.desugarState = DesugarState.ON;
                       options.cfToCfDesugar = true;
                     }))
-        .allowDiagnosticInfoMessages()
         .compile()
-        .apply(TestBase::verifyAllInfoFromGenericSignatureTypeParameterValidation)
         .inspect(inspector -> assertNests(inspector, desugar))
         .writeToZip();
   }
diff --git a/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/Java11R8CompilationTest.java b/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/Java11R8CompilationTest.java
index 31f7aba..cb549f5 100644
--- a/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/Java11R8CompilationTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/Java11R8CompilationTest.java
@@ -50,9 +50,7 @@
         .setMinApi(parameters.getApiLevel())
         .addProgramFiles(ToolHelper.R8_WITH_RELOCATED_DEPS_11_JAR)
         .addKeepRuleFiles(MAIN_KEEP)
-        .allowDiagnosticInfoMessages()
         .compile()
-        .apply(TestBase::verifyAllInfoFromGenericSignatureTypeParameterValidation)
         .inspect(this::assertNotEmpty)
         .inspect(Java11R8CompilationTest::assertNoNests);
   }
diff --git a/src/test/java/com/android/tools/r8/desugar/staticinterfacemethod/MutuallyRecursiveMethodsTest.java b/src/test/java/com/android/tools/r8/desugar/staticinterfacemethod/MutuallyRecursiveMethodsTest.java
new file mode 100644
index 0000000..ee911d3
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/staticinterfacemethod/MutuallyRecursiveMethodsTest.java
@@ -0,0 +1,54 @@
+// 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.staticinterfacemethod;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.StringUtils;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class MutuallyRecursiveMethodsTest extends TestBase {
+
+  static final String EXPECTED = StringUtils.lines("42 is even? true");
+
+  private final TestParameters parameters;
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimes().withAllApiLevels().build();
+  }
+
+  public MutuallyRecursiveMethodsTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void test() throws Exception {
+    testForRuntime(parameters)
+        .addInnerClasses(MutuallyRecursiveMethodsTest.class)
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutput(EXPECTED);
+  }
+
+  interface I {
+    static boolean isEven(int i) {
+      return i == 0 || isOdd(i - 1);
+    }
+
+    static boolean isOdd(int i) {
+      return i != 0 && isEven(i - 1);
+    }
+  }
+
+  static class TestClass {
+
+    public static void main(String[] args) {
+      System.out.println("42 is even? " + I.isEven(42));
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/desugar/staticinterfacemethod/NoMissingClassWarningForNonInterfaceInvoke.java b/src/test/java/com/android/tools/r8/desugar/staticinterfacemethod/NoMissingClassWarningForNonInterfaceInvoke.java
new file mode 100644
index 0000000..08df42c
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/staticinterfacemethod/NoMissingClassWarningForNonInterfaceInvoke.java
@@ -0,0 +1,46 @@
+// 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.staticinterfacemethod;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class NoMissingClassWarningForNonInterfaceInvoke extends TestBase {
+
+  private final TestParameters parameters;
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimes().withAllApiLevelsAlsoForCf().build();
+  }
+
+  public NoMissingClassWarningForNonInterfaceInvoke(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void test() throws Exception {
+    testForD8(Backend.CF)
+        .addProgramClasses(TestClass.class)
+        .setMinApi(parameters.getApiLevel())
+        .setIntermediate(true)
+        .compile()
+        .assertNoWarningMessages();
+  }
+
+  static class MissingClass {
+    static void test() {}
+  }
+
+  static class TestClass {
+    public static void main(String[] args) {
+      MissingClass.test();
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/internal/YouTubeV1612Test.java b/src/test/java/com/android/tools/r8/internal/YouTubeV1612Test.java
index 611cc70..f2fe2b8 100644
--- a/src/test/java/com/android/tools/r8/internal/YouTubeV1612Test.java
+++ b/src/test/java/com/android/tools/r8/internal/YouTubeV1612Test.java
@@ -130,7 +130,8 @@
         .assertAllInfoMessagesMatch(
             anyOf(
                 containsString("Ignoring option: -optimizations"),
-                containsString("Proguard configuration rule does not match anything")))
+                containsString("Proguard configuration rule does not match anything"),
+                containsString("Invalid signature")))
         .apply(this::printProtoStats);
   }
 
diff --git a/src/test/java/com/android/tools/r8/internal/proto/ChromeProtoRewritingTest.java b/src/test/java/com/android/tools/r8/internal/proto/ChromeProtoRewritingTest.java
index 4e784a3..ff19143 100644
--- a/src/test/java/com/android/tools/r8/internal/proto/ChromeProtoRewritingTest.java
+++ b/src/test/java/com/android/tools/r8/internal/proto/ChromeProtoRewritingTest.java
@@ -8,6 +8,7 @@
 import static com.android.tools.r8.internal.proto.ProtoShrinkingTestBase.keepAllProtosRule;
 import static com.android.tools.r8.internal.proto.ProtoShrinkingTestBase.keepDynamicMethodSignatureRule;
 import static com.android.tools.r8.internal.proto.ProtoShrinkingTestBase.keepNewMessageInfoSignatureRule;
+import static com.android.tools.r8.utils.codeinspector.Matchers.invalidGenericArgumentApplicationCount;
 import static com.android.tools.r8.utils.codeinspector.Matchers.proguardConfigurationRuleDoesNotMatch;
 import static com.android.tools.r8.utils.codeinspector.Matchers.typeVariableNotInScope;
 import static org.hamcrest.CoreMatchers.anyOf;
@@ -57,7 +58,10 @@
         .inspectDiagnosticMessages(
             diagnostics ->
                 diagnostics.assertAllInfosMatch(
-                    anyOf(typeVariableNotInScope(), proguardConfigurationRuleDoesNotMatch())))
+                    anyOf(
+                        typeVariableNotInScope(),
+                        invalidGenericArgumentApplicationCount(),
+                        proguardConfigurationRuleDoesNotMatch())))
         .inspect(this::inspect);
   }
 
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInnerClassTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInnerClassTest.java
new file mode 100644
index 0000000..c38a3d7
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInnerClassTest.java
@@ -0,0 +1,140 @@
+// 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.kotlin.metadata;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresentAndRenamed;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.ClassFileConsumer;
+import com.android.tools.r8.DexIndexedConsumer.ArchiveConsumer;
+import com.android.tools.r8.KotlinTestParameters;
+import com.android.tools.r8.ProgramConsumer;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.shaking.ProguardKeepAttributes;
+import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import java.nio.file.Path;
+import java.util.Collection;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class MetadataRewriteInnerClassTest extends KotlinMetadataTestBase {
+
+  private static final String EXPECTED =
+      StringUtils.lines(
+          "fun <init>(kotlin.Int):"
+              + " com.android.tools.r8.kotlin.metadata.nested_reflect.Outer.Nested",
+          "fun com.android.tools.r8.kotlin.metadata.nested_reflect.Outer.Inner.<init>(kotlin.Int):"
+              + " com.android.tools.r8.kotlin.metadata.nested_reflect.Outer.Inner");
+  private static final String EXPECTED_OUTER_RENAMED =
+      StringUtils.lines(
+          "fun <init>(kotlin.Int): com.android.tools.r8.kotlin.metadata.nested_reflect.Nested",
+          "fun <init>(kotlin.Int): com.android.tools.r8.kotlin.metadata.nested_reflect.Inner");
+  private static final String PKG_NESTED_REFLECT = PKG + ".nested_reflect";
+
+  private final TestParameters parameters;
+
+  @Parameterized.Parameters(name = "{0}, {1}")
+  public static Collection<Object[]> data() {
+    return buildParameters(
+        getTestParameters().withAllRuntimes().withAllApiLevelsAlsoForCf().build(),
+        getKotlinTestParameters().withAllCompilersAndTargetVersions().build());
+  }
+
+  public MetadataRewriteInnerClassTest(
+      TestParameters parameters, KotlinTestParameters kotlinParameters) {
+    super(kotlinParameters);
+    this.parameters = parameters;
+  }
+
+  private static final KotlinCompileMemoizer jarMap =
+      getCompileMemoizer(getKotlinFileInTest(PKG_PREFIX + "/nested_reflect", "main"));
+
+  @Test
+  public void smokeTest() throws Exception {
+    assumeTrue(parameters.isCfRuntime());
+    Path libJar = jarMap.getForConfiguration(kotlinc, targetVersion);
+    testForRuntime(parameters)
+        .addProgramFiles(
+            ToolHelper.getKotlinStdlibJar(kotlinc), ToolHelper.getKotlinReflectJar(kotlinc), libJar)
+        .run(parameters.getRuntime(), PKG_NESTED_REFLECT + ".MainKt")
+        .assertSuccessWithOutput(EXPECTED);
+  }
+
+  @Test
+  public void testMetadataOuterRenamed() throws Exception {
+    Path mainJar =
+        testForR8(parameters.getBackend())
+            .addClasspathFiles(ToolHelper.getKotlinStdlibJar(kotlinc))
+            .addClasspathFiles(ToolHelper.getKotlinReflectJar(kotlinc))
+            .addClasspathFiles(ToolHelper.getKotlinAnnotationJar(kotlinc))
+            .addProgramFiles(jarMap.getForConfiguration(kotlinc, targetVersion))
+            .addKeepRules("-keep public class " + PKG_NESTED_REFLECT + ".Outer$Nested { *; }")
+            .addKeepRules("-keep public class " + PKG_NESTED_REFLECT + ".Outer$Inner { *; }")
+            .addKeepMainRule(PKG_NESTED_REFLECT + ".MainKt")
+            .addKeepAttributes(ProguardKeepAttributes.RUNTIME_VISIBLE_ANNOTATIONS)
+            .setMinApi(parameters.getApiLevel())
+            .compile()
+            .inspect(inspector -> inspectPruned(inspector, true))
+            .writeToZip();
+
+    runD8(mainJar, EXPECTED_OUTER_RENAMED);
+  }
+
+  @Test
+  public void testMetadataOuterNotRenamed() throws Exception {
+    Path mainJar =
+        testForR8(parameters.getBackend())
+            .addClasspathFiles(ToolHelper.getKotlinStdlibJar(kotlinc))
+            .addClasspathFiles(ToolHelper.getKotlinReflectJar(kotlinc))
+            .addClasspathFiles(ToolHelper.getKotlinAnnotationJar(kotlinc))
+            .addProgramFiles(jarMap.getForConfiguration(kotlinc, targetVersion))
+            .addKeepAttributeInnerClassesAndEnclosingMethod()
+            .addKeepRules("-keep public class " + PKG_NESTED_REFLECT + ".Outer { *; }")
+            .addKeepRules("-keep public class " + PKG_NESTED_REFLECT + ".Outer$Nested { *; }")
+            .addKeepRules("-keep public class " + PKG_NESTED_REFLECT + ".Outer$Inner { *; }")
+            .addKeepMainRule(PKG_NESTED_REFLECT + ".MainKt")
+            .addKeepAttributes(ProguardKeepAttributes.RUNTIME_VISIBLE_ANNOTATIONS)
+            .setMinApi(parameters.getApiLevel())
+            .compile()
+            .inspect(inspector -> inspectPruned(inspector, false))
+            .writeToZip();
+
+    runD8(mainJar, EXPECTED);
+  }
+
+  private void runD8(Path jar, String expected) throws Exception {
+    Path output = temp.newFile("output.zip").toPath();
+    ProgramConsumer programConsumer =
+        parameters.isCfRuntime()
+            ? new ClassFileConsumer.ArchiveConsumer(output, true)
+            : new ArchiveConsumer(output, true);
+    testForD8(parameters.getBackend())
+        .addProgramFiles(
+            ToolHelper.getKotlinStdlibJar(kotlinc), ToolHelper.getKotlinReflectJar(kotlinc), jar)
+        .setMinApi(parameters.getApiLevel())
+        .setProgramConsumer(programConsumer)
+        .addOptionsModification(
+            options -> {
+              // Needed for passing kotlin_builtin files to output.
+              options.testing.enableD8ResourcesPassThrough = true;
+              options.dataResourceConsumer = options.programConsumer.getDataResourceConsumer();
+            })
+        .run(parameters.getRuntime(), PKG_NESTED_REFLECT + ".MainKt")
+        .assertSuccessWithOutput(expected);
+  }
+
+  private void inspectPruned(CodeInspector inspector, boolean outerRenamed) {
+    assertThat(
+        inspector.clazz(PKG_NESTED_REFLECT + ".Outer"),
+        outerRenamed ? isPresentAndRenamed() : isPresent());
+    assertThat(inspector.clazz(PKG_NESTED_REFLECT + ".Outer$Nested"), isPresent());
+    assertThat(inspector.clazz(PKG_NESTED_REFLECT + ".Outer$Inner"), isPresent());
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/nested_reflect/main.kt b/src/test/java/com/android/tools/r8/kotlin/metadata/nested_reflect/main.kt
new file mode 100644
index 0000000..f73e134
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/nested_reflect/main.kt
@@ -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.kotlin.metadata.nested_reflect
+
+import kotlin.reflect.full.primaryConstructor
+
+class Outer {
+  data class Nested(val data: Int)
+  inner class Inner(val data: Int)
+}
+
+fun main() {
+  val nestedPrimaryCtr = Outer.Nested::class.primaryConstructor
+  println(nestedPrimaryCtr?.toString() ?: "Cannot find primary constructor")
+  val innerPrimaryCtr = Outer.Inner::class.primaryConstructor
+  println(innerPrimaryCtr?.toString() ?: "Cannot find primary constructor")
+}
diff --git a/src/test/java/com/android/tools/r8/naming/methodparameters/MethodParametersTest.java b/src/test/java/com/android/tools/r8/naming/methodparameters/MethodParametersTest.java
index a8e337e..04ec6b4 100644
--- a/src/test/java/com/android/tools/r8/naming/methodparameters/MethodParametersTest.java
+++ b/src/test/java/com/android/tools/r8/naming/methodparameters/MethodParametersTest.java
@@ -16,6 +16,7 @@
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.ToolHelper.DexVm.Version;
+import com.android.tools.r8.shaking.ProguardKeepAttributes;
 import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.BooleanUtils;
 import java.io.IOException;
@@ -51,14 +52,16 @@
   }
 
   @Test
-  public void testKeepingMethodParametersR8()
-      throws ExecutionException, CompilationFailedException, IOException {
+  public void testKeepingMethodParametersR8() throws Exception {
     R8TestRunResult runResult =
         testForR8(parameters.getBackend())
             .addProgramClassFileData(MethodParametersTestDump.dump())
             .addKeepClassAndMembersRulesWithAllowObfuscation(MethodParametersTest.class)
             .addKeepMainRule(MethodParametersTest.class)
-            .addKeepRules(keepMethodParameters ? "-keepattributes MethodParameters" : "")
+            .addKeepAttributeSourceFile()
+            .applyIf(
+                keepMethodParameters,
+                builder -> builder.addKeepAttributes(ProguardKeepAttributes.METHOD_PARAMETERS))
             .setMinApi(keepMethodParameters ? AndroidApiLevel.O : AndroidApiLevel.L)
             // java.lang.reflect.Parameter was introduced in API level 26 (O).
             .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.O))
diff --git a/src/test/java/com/android/tools/r8/shaking/KeepAnnotatedMemberTest.java b/src/test/java/com/android/tools/r8/shaking/KeepAnnotatedMemberTest.java
index 9947888..33dfbaf 100644
--- a/src/test/java/com/android/tools/r8/shaking/KeepAnnotatedMemberTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/KeepAnnotatedMemberTest.java
@@ -101,9 +101,7 @@
         .addDontWarnGoogle()
         .addDontWarnJavax()
         .addDontWarn("org.codehaus.mojo.animal_sniffer.IgnoreJRERequirement")
-        .allowDiagnosticInfoMessages()
-        .compile()
-        .apply(TestBase::verifyAllInfoFromGenericSignatureTypeParameterValidation);
+        .compile();
   }
 
   @Test
@@ -128,9 +126,7 @@
         .addDontWarnGoogle()
         .addDontWarnJavax()
         .addDontWarn("org.codehaus.mojo.animal_sniffer.IgnoreJRERequirement")
-        .allowDiagnosticInfoMessages()
         .compile()
-        .apply(TestBase::verifyAllInfoFromGenericSignatureTypeParameterValidation)
         .inspect(
             inspector -> {
               ClassSubject clazz = inspector.clazz(CLASS_WITH_ANNOTATED_METHOD);
@@ -146,9 +142,7 @@
         // TODO(b/159971974): Technically this rule does not hit anything and should fail due to
         //  missing allowUnusedProguardConfigurationRules()
         .addKeepRules("-keepclassmembers class * { @" + PRESENT_ANNOTATION + " *** *(...); }")
-        .allowDiagnosticInfoMessages()
         .compile()
-        .apply(TestBase::verifyAllInfoFromGenericSignatureTypeParameterValidation)
         .inspect(inspector -> assertEquals(0, inspector.allClasses().size()));
   }
 
@@ -158,9 +152,7 @@
         .addProgramFiles(R8_JAR)
         .addKeepClassRules(CLASS_WITH_ANNOTATED_METHOD)
         .addKeepRules("-keepclassmembers class * { @" + PRESENT_ANNOTATION + " *** *(...); }")
-        .allowDiagnosticInfoMessages()
         .compile()
-        .apply(TestBase::verifyAllInfoFromGenericSignatureTypeParameterValidation)
         .inspect(
             inspector -> {
               assertEquals(1, inspector.allClasses().size());
@@ -197,9 +189,7 @@
         .addProgramFiles(R8_JAR)
         .addKeepClassRules(CLASS_WITH_ANNOTATED_METHOD)
         .addKeepRules("-if class * -keep class <1> { @" + PRESENT_ANNOTATION + " *** *(...); }")
-        .allowDiagnosticInfoMessages()
         .compile()
-        .apply(TestBase::verifyAllInfoFromGenericSignatureTypeParameterValidation)
         .inspect(
             inspector -> {
               assertEquals(1, inspector.allClasses().size());
@@ -231,10 +221,8 @@
             .addKeepRules("-keepclassmembers class * { @" + PRESENT_ANNOTATION + " *** *(...); }")
             .addDontWarnGoogle()
             .addDontWarnJavaxNullableAnnotation()
-            .allowDiagnosticInfoMessages()
             .apply(this::configureHorizontalClassMerging)
             .compile()
-            .apply(TestBase::verifyAllInfoFromGenericSignatureTypeParameterValidation)
             .graphInspector();
 
     GraphInspector ifThenKeepClassMembersInspector =
@@ -253,9 +241,7 @@
             .addDontWarnGoogle()
             .addDontWarnJavaxNullableAnnotation()
             .apply(this::configureHorizontalClassMerging)
-            .allowDiagnosticInfoMessages()
             .compile()
-            .apply(TestBase::verifyAllInfoFromGenericSignatureTypeParameterValidation)
             .graphInspector();
     assertRetainedClassesEqual(referenceInspector, ifThenKeepClassMembersInspector);
 
@@ -275,9 +261,7 @@
             .addDontWarnGoogle()
             .addDontWarnJavaxNullableAnnotation()
             .apply(this::configureHorizontalClassMerging)
-            .allowDiagnosticInfoMessages()
             .compile()
-            .apply(TestBase::verifyAllInfoFromGenericSignatureTypeParameterValidation)
             .graphInspector();
     assertRetainedClassesEqual(referenceInspector, ifThenKeepClassesWithMembersInspector);
 
@@ -299,9 +283,7 @@
             .addDontWarnGoogle()
             .addDontWarnJavaxNullableAnnotation()
             .apply(this::configureHorizontalClassMerging)
-            .allowDiagnosticInfoMessages()
             .compile()
-            .apply(TestBase::verifyAllInfoFromGenericSignatureTypeParameterValidation)
             .graphInspector();
     assertRetainedClassesEqual(referenceInspector, ifHasMemberThenKeepClassInspector);
   }
diff --git a/src/test/java/com/android/tools/r8/shaking/keptgraph/WhyAreYouKeepingAllTest.java b/src/test/java/com/android/tools/r8/shaking/keptgraph/WhyAreYouKeepingAllTest.java
index ac22ab7..94bdbf1 100644
--- a/src/test/java/com/android/tools/r8/shaking/keptgraph/WhyAreYouKeepingAllTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/keptgraph/WhyAreYouKeepingAllTest.java
@@ -48,9 +48,7 @@
         .addKeepRuleFiles(MAIN_KEEP)
         .addKeepRules(WHY_ARE_YOU_KEEPING_ALL)
         .collectStdout()
-        .allowDiagnosticInfoMessages()
         .compile()
-        .apply(TestBase::verifyAllInfoFromGenericSignatureTypeParameterValidation)
         .assertStdoutThatMatches(containsString("referenced in keep rule"))
         // TODO(b/124655065): We should always know the reason for keeping.
         // It is OK if this starts failing while the kept-graph API is incomplete, in which case
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/Matchers.java b/src/test/java/com/android/tools/r8/utils/codeinspector/Matchers.java
index 4f4b656..cbbbb14 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/Matchers.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/Matchers.java
@@ -227,6 +227,12 @@
     return DiagnosticsMatcher.diagnosticMessage(containsString("A type variable is not in scope"));
   }
 
+  public static Matcher<Diagnostic> invalidGenericArgumentApplicationCount() {
+    return DiagnosticsMatcher.diagnosticMessage(
+        containsString(
+            "The applied generic arguments have different count than the expected formals"));
+  }
+
   public static Matcher<Diagnostic> proguardConfigurationRuleDoesNotMatch() {
     return DiagnosticsMatcher.diagnosticMessage(
         containsString("Proguard configuration rule does not match anything"));