Merge commit '9ffdfb9a9dd243e5af5eb377ec7ff1efdc413707' into dev-release
diff --git a/.gitignore b/.gitignore
index 07f90a7..885f76d 100644
--- a/.gitignore
+++ b/.gitignore
@@ -231,6 +231,8 @@
 third_party/r8mappings.tar.gz
 third_party/remapper
 third_party/remapper.tar.gz
+third_party/retrace
+third_party/retrace.tar.gz
 third_party/retrace_benchmark
 third_party/retrace_benchmark.tar.gz
 third_party/retrace_internal
diff --git a/build.gradle b/build.gradle
index 8b0525f..08c9141 100644
--- a/build.gradle
+++ b/build.gradle
@@ -346,6 +346,7 @@
                 "proguard/proguard5.2.1",
                 "proguard/proguard6.0.1",
                 "proguard/proguard-7.0.0",
+                "retrace/binary_compatibility",
                 "r8",
                 "r8-releases/2.0.74",
                 "r8mappings",
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java
index 91f6172..2ab471a 100644
--- a/src/main/java/com/android/tools/r8/R8.java
+++ b/src/main/java/com/android/tools/r8/R8.java
@@ -473,7 +473,7 @@
           // We can now remove visibility bridges. Note that we do not need to update the
           // invoke-targets here, as the existing invokes will simply dispatch to the now
           // visible super-method. MemberRebinding, if run, will then dispatch it correctly.
-          new VisibilityBridgeRemover(appView.withLiveness()).run();
+          new VisibilityBridgeRemover(appView.withLiveness()).run(executorService);
         }
       }
 
@@ -720,7 +720,7 @@
       // Remove unneeded visibility bridges that have been inserted for member rebinding.
       // This can only be done if we have AppInfoWithLiveness.
       if (appView.appInfo().hasLiveness()) {
-        new VisibilityBridgeRemover(appView.withLiveness()).run();
+        new VisibilityBridgeRemover(appView.withLiveness()).run(executorService);
       } else {
         // If we don't have AppInfoWithLiveness here, it must be because we are not shrinking. When
         // we are not shrinking, we can't move visibility bridges. In principle, though, it would be
diff --git a/src/main/java/com/android/tools/r8/cf/CfCodePrinter.java b/src/main/java/com/android/tools/r8/cf/CfCodePrinter.java
index fdec52c..ab9165b 100644
--- a/src/main/java/com/android/tools/r8/cf/CfCodePrinter.java
+++ b/src/main/java/com/android/tools/r8/cf/CfCodePrinter.java
@@ -12,6 +12,7 @@
 import com.android.tools.r8.cf.code.CfCheckCast;
 import com.android.tools.r8.cf.code.CfCmp;
 import com.android.tools.r8.cf.code.CfConstClass;
+import com.android.tools.r8.cf.code.CfConstDynamic;
 import com.android.tools.r8.cf.code.CfConstMethodHandle;
 import com.android.tools.r8.cf.code.CfConstMethodType;
 import com.android.tools.r8.cf.code.CfConstNull;
@@ -388,6 +389,12 @@
   }
 
   @Override
+  public void print(CfConstDynamic constDynamic) {
+    // TODO(b/198143561): Support CfConstDynamic.
+    throw new Unimplemented(constDynamic.getClass().getSimpleName());
+  }
+
+  @Override
   public void print(CfReturnVoid ret) {
     printNewInstruction("CfReturnVoid");
   }
diff --git a/src/main/java/com/android/tools/r8/cf/CfPrinter.java b/src/main/java/com/android/tools/r8/cf/CfPrinter.java
index c6ba520..480c9cb 100644
--- a/src/main/java/com/android/tools/r8/cf/CfPrinter.java
+++ b/src/main/java/com/android/tools/r8/cf/CfPrinter.java
@@ -10,6 +10,7 @@
 import com.android.tools.r8.cf.code.CfCheckCast;
 import com.android.tools.r8.cf.code.CfCmp;
 import com.android.tools.r8.cf.code.CfConstClass;
+import com.android.tools.r8.cf.code.CfConstDynamic;
 import com.android.tools.r8.cf.code.CfConstMethodHandle;
 import com.android.tools.r8.cf.code.CfConstMethodType;
 import com.android.tools.r8.cf.code.CfConstNull;
@@ -326,6 +327,12 @@
     appendType(constClass.getType());
   }
 
+  public void print(CfConstDynamic constDynamic) {
+    indent();
+    builder.append("ldc <dynamic> ");
+    appendType(constDynamic.getType());
+  }
+
   public void print(CfInitClass initClass) {
     indent();
     builder.append("initclass ");
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfConstDynamic.java b/src/main/java/com/android/tools/r8/cf/code/CfConstDynamic.java
new file mode 100644
index 0000000..e6e1f0d
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/cf/code/CfConstDynamic.java
@@ -0,0 +1,232 @@
+// 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.cf.code;
+
+import static com.android.tools.r8.graph.UseRegistry.MethodHandleUse.NOT_ARGUMENT_TO_LAMBDA_METAFACTORY;
+
+import com.android.tools.r8.cf.CfPrinter;
+import com.android.tools.r8.errors.CompilationError;
+import com.android.tools.r8.errors.Unimplemented;
+import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.CfCode;
+import com.android.tools.r8.graph.CfCompareHelper;
+import com.android.tools.r8.graph.DexClassAndMethod;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexMethodHandle;
+import com.android.tools.r8.graph.DexString;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.GraphLens;
+import com.android.tools.r8.graph.InitClassLens;
+import com.android.tools.r8.graph.JarApplicationReader;
+import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.graph.UseRegistry;
+import com.android.tools.r8.ir.conversion.CfSourceCode;
+import com.android.tools.r8.ir.conversion.CfState;
+import com.android.tools.r8.ir.conversion.IRBuilder;
+import com.android.tools.r8.ir.conversion.LensCodeRewriterUtils;
+import com.android.tools.r8.ir.desugar.constantdynamic.ConstantDynamicReference;
+import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
+import com.android.tools.r8.ir.optimize.InliningConstraints;
+import com.android.tools.r8.naming.NamingLens;
+import com.android.tools.r8.utils.structural.CompareToVisitor;
+import java.util.ListIterator;
+import org.objectweb.asm.ConstantDynamic;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+
+public class CfConstDynamic extends CfInstruction implements CfTypeInstruction {
+
+  private final ConstantDynamicReference reference;
+
+  public CfConstDynamic(
+      DexString name,
+      DexType type,
+      DexMethodHandle bootstrapMethod,
+      Object[] bootstrapMethodArguments) {
+    assert name != null;
+    assert type != null;
+    assert bootstrapMethod != null;
+    assert bootstrapMethodArguments != null;
+    assert bootstrapMethodArguments.length == 0;
+
+    reference = new ConstantDynamicReference(name, type, bootstrapMethod, bootstrapMethodArguments);
+  }
+
+  @Override
+  public CfConstDynamic asConstDynamic() {
+    return this;
+  }
+
+  @Override
+  public boolean isConstDynamic() {
+    return true;
+  }
+
+  public ConstantDynamicReference getReference() {
+    return reference;
+  }
+
+  public DexString getName() {
+    return reference.getName();
+  }
+
+  public DexMethodHandle getBootstrapMethod() {
+    return reference.getBootstrapMethod();
+  }
+
+  public Object[] getBootstrapMethodArguments() {
+    return reference.getBootstrapMethodArguments();
+  }
+
+  public static CfConstDynamic fromAsmConstantDynamic(
+      ConstantDynamic insn, JarApplicationReader application, DexType clazz) {
+    String constantName = insn.getName();
+    String constantDescriptor = insn.getDescriptor();
+    // TODO(b/178172809): Handle bootstrap arguments.
+    if (insn.getBootstrapMethodArgumentCount() > 0) {
+      throw new CompilationError(
+          "Unsupported dynamic constant (has arguments to bootstrap method)");
+    }
+    if (insn.getBootstrapMethod().getTag() != Opcodes.H_INVOKESTATIC) {
+      throw new CompilationError("Unsupported dynamic constant (not invoke static)");
+    }
+    if (insn.getBootstrapMethod().getOwner().equals("java/lang/invoke/ConstantBootstraps")) {
+      throw new CompilationError(
+          "Unsupported dynamic constant (runtime provided bootstrap method)");
+    }
+    if (application.getTypeFromName(insn.getBootstrapMethod().getOwner()) != clazz) {
+      throw new CompilationError("Unsupported dynamic constant (different owner)");
+    }
+    // Resolve the bootstrap method.
+    DexMethodHandle bootstrapMethodHandle =
+        DexMethodHandle.fromAsmHandle(insn.getBootstrapMethod(), application, clazz);
+    if (!bootstrapMethodHandle.member.isDexMethod()) {
+      throw new CompilationError("Unsupported dynamic constant (invalid method handle)");
+    }
+    DexMethod bootstrapMethod = bootstrapMethodHandle.asMethod();
+    if (bootstrapMethod.getProto().returnType != application.getTypeFromDescriptor("[Z")
+        && bootstrapMethod.getProto().returnType
+            != application.getTypeFromDescriptor("Ljava/lang/Object;")) {
+      throw new CompilationError("Unsupported dynamic constant (unsupported constant type)");
+    }
+    if (bootstrapMethod.getProto().getParameters().size() != 3) {
+      throw new CompilationError("Unsupported dynamic constant (unsupported signature)");
+    }
+    if (bootstrapMethod.getProto().getParameters().get(0) != application.getFactory().lookupType) {
+      throw new CompilationError(
+          "Unsupported dynamic constant (unexpected type of first argument to bootstrap method");
+    }
+    if (bootstrapMethod.getProto().getParameters().get(1) != application.getFactory().stringType) {
+      throw new CompilationError(
+          "Unsupported dynamic constant (unexpected type of second argument to bootstrap method");
+    }
+    if (bootstrapMethod.getProto().getParameters().get(2) != application.getFactory().classType) {
+      throw new CompilationError(
+          "Unsupported dynamic constant (unexpected type of third argument to bootstrap method");
+    }
+    return new CfConstDynamic(
+        application.getString(constantName),
+        application.getTypeFromDescriptor(constantDescriptor),
+        bootstrapMethodHandle,
+        new Object[] {});
+  }
+
+  @Override
+  public int getCompareToId() {
+    return CfCompareHelper.CONST_DYNAMIC_COMPARE_ID;
+  }
+
+  @Override
+  public int internalAcceptCompareTo(
+      CfInstruction other, CompareToVisitor visitor, CfCompareHelper helper) {
+    int diff = getName().acceptCompareTo(((CfConstDynamic) other).getName(), visitor);
+    if (diff != 0) {
+      return diff;
+    }
+    diff = getType().acceptCompareTo(((CfConstDynamic) other).getType(), visitor);
+    if (diff != 0) {
+      return diff;
+    }
+    return getBootstrapMethod()
+        .acceptCompareTo(((CfConstDynamic) other).getBootstrapMethod(), visitor);
+  }
+
+  @Override
+  public CfTypeInstruction asTypeInstruction() {
+    return this;
+  }
+
+  @Override
+  public boolean isTypeInstruction() {
+    return true;
+  }
+
+  @Override
+  public DexType getType() {
+    return reference.getType();
+  }
+
+  @Override
+  public CfInstruction withType(DexType newType) {
+    throw new Unimplemented();
+  }
+
+  @Override
+  public void write(
+      AppView<?> appView,
+      ProgramMethod context,
+      DexItemFactory dexItemFactory,
+      GraphLens graphLens,
+      InitClassLens initClassLens,
+      NamingLens namingLens,
+      LensCodeRewriterUtils rewriter,
+      MethodVisitor visitor) {
+    // TODO(b/198142625): Support CONSTANT_Dynamic for R8 cf to cf.
+    throw new CompilationError("Unsupported dynamic constant (not desugaring)");
+  }
+
+  @Override
+  public void print(CfPrinter printer) {
+    printer.print(this);
+  }
+
+  @Override
+  public boolean canThrow() {
+    return true;
+  }
+
+  @Override
+  void internalRegisterUse(
+      UseRegistry registry, DexClassAndMethod context, ListIterator<CfInstruction> iterator) {
+    registry.registerTypeReference(reference.getType());
+    registry.registerMethodHandle(
+        reference.getBootstrapMethod(), NOT_ARGUMENT_TO_LAMBDA_METAFACTORY);
+    assert reference.getBootstrapMethodArguments().length == 0;
+  }
+
+  @Override
+  public void buildIR(IRBuilder builder, CfState state, CfSourceCode code) {
+    throw new CompilationError("Unsupported dynamic constant (not desugaring)");
+  }
+
+  @Override
+  public ConstraintWithTarget inliningConstraint(
+      InliningConstraints inliningConstraints, CfCode code, ProgramMethod context) {
+    throw new Unreachable();
+  }
+
+  @Override
+  public void evaluate(
+      CfFrameVerificationHelper frameBuilder,
+      DexType context,
+      DexType returnType,
+      DexItemFactory factory,
+      InitClassLens initClassLens) {
+    // ... →
+    // ..., value
+    frameBuilder.push(factory.classType);
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfInstruction.java b/src/main/java/com/android/tools/r8/cf/code/CfInstruction.java
index 6067cc3..188c575 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfInstruction.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfInstruction.java
@@ -118,6 +118,14 @@
     return false;
   }
 
+  public CfConstDynamic asConstDynamic() {
+    return null;
+  }
+
+  public boolean isConstDynamic() {
+    return false;
+  }
+
   public CfFieldInstruction asFieldInstruction() {
     return null;
   }
diff --git a/src/main/java/com/android/tools/r8/graph/AppInfoWithClassHierarchy.java b/src/main/java/com/android/tools/r8/graph/AppInfoWithClassHierarchy.java
index c8b99a4..dc8e0bb 100644
--- a/src/main/java/com/android/tools/r8/graph/AppInfoWithClassHierarchy.java
+++ b/src/main/java/com/android/tools/r8/graph/AppInfoWithClassHierarchy.java
@@ -465,7 +465,7 @@
     }
     assert potentialHolder.isInterface();
     for (DexEncodedMethod virtualMethod : potentialHolder.virtualMethods()) {
-      if (virtualMethod.getReference().hasSameProtoAndName(method.getReference())
+      if (virtualMethod.getReference().match(method.getReference())
           && virtualMethod.accessFlags.isSameVisibility(method.accessFlags)) {
         return true;
       }
diff --git a/src/main/java/com/android/tools/r8/graph/CfCompareHelper.java b/src/main/java/com/android/tools/r8/graph/CfCompareHelper.java
index 1108c72..d3c3351 100644
--- a/src/main/java/com/android/tools/r8/graph/CfCompareHelper.java
+++ b/src/main/java/com/android/tools/r8/graph/CfCompareHelper.java
@@ -25,6 +25,7 @@
   public static final int CONST_NUMBER_COMPARE_ID;
   public static final int CONST_METHOD_TYPE_COMPARE_ID;
   public static final int CONST_METHOD_HANDLE_COMPARE_ID;
+  public static final int CONST_DYNAMIC_COMPARE_ID;
   public static final int FRAME_COMPARE_ID;
   public static final int INIT_CLASS_COMPARE_ID;
   public static final int LABEL_COMPARE_ID;
@@ -38,6 +39,7 @@
     CONST_NUMBER_COMPARE_ID = ++lastId;
     CONST_METHOD_TYPE_COMPARE_ID = ++lastId;
     CONST_METHOD_HANDLE_COMPARE_ID = ++lastId;
+    CONST_DYNAMIC_COMPARE_ID = ++lastId;
     FRAME_COMPARE_ID = ++lastId;
     INIT_CLASS_COMPARE_ID = ++lastId;
     LABEL_COMPARE_ID = ++lastId;
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 2b8a186..5efaba3 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedField.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedField.java
@@ -374,6 +374,10 @@
     this.genericSignature = FieldTypeSignature.noSignature();
   }
 
+  public static Builder builder() {
+    return new Builder();
+  }
+
   private static Builder builder(DexEncodedField from) {
     return new Builder(from);
   }
@@ -381,16 +385,18 @@
   public static class Builder {
 
     private DexField field;
-    private DexAnnotationSet annotations;
+    private DexAnnotationSet annotations = DexAnnotationSet.empty();
     private FieldAccessFlags accessFlags;
-    private FieldTypeSignature genericSignature;
+    private FieldTypeSignature genericSignature = FieldTypeSignature.noSignature();
     private DexValue staticValue;
     private AndroidApiLevel apiLevel;
-    private FieldOptimizationInfo optimizationInfo;
+    private FieldOptimizationInfo optimizationInfo = DefaultFieldOptimizationInfo.getInstance();
     private boolean deprecated;
     private boolean d8R8Synthesized;
     private Consumer<DexEncodedField> buildConsumer = ConsumerUtils.emptyConsumer();
 
+    Builder() {}
+
     Builder(DexEncodedField from) {
       // Copy all the mutable state of a DexEncodedField here.
       field = from.getReference();
@@ -445,7 +451,27 @@
       return this;
     }
 
-    DexEncodedField build() {
+    public Builder setAccessFlags(FieldAccessFlags accessFlags) {
+      this.accessFlags = accessFlags;
+      return this;
+    }
+
+    public Builder setD8R8Synthesized() {
+      this.d8R8Synthesized = true;
+      return this;
+    }
+
+    public Builder setApiLevel(AndroidApiLevel apiLevel) {
+      this.apiLevel = apiLevel;
+      return this;
+    }
+
+    public DexEncodedField build() {
+      assert field != null;
+      assert accessFlags != null;
+      assert genericSignature != null;
+      assert annotations != null;
+      assert apiLevel != null;
       DexEncodedField dexEncodedField =
           new DexEncodedField(
               field,
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 0243703..6cbb223 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
@@ -166,8 +166,6 @@
   /** Generic signature information if the attribute is present in the input */
   private MethodTypeSignature genericSignature;
 
-  private DexEncodedMethod defaultInterfaceMethodImplementation = null;
-
   private OptionalBool isLibraryMethodOverride = OptionalBool.unknown();
 
   private Int2ReferenceMap<DebugLocalInfo> parameterInfo = NO_PARAMETER_INFO;
@@ -211,21 +209,6 @@
     return compilationState;
   }
 
-  public DexEncodedMethod getDefaultInterfaceMethodImplementation() {
-    return defaultInterfaceMethodImplementation;
-  }
-
-  public void setDefaultInterfaceMethodImplementation(DexEncodedMethod implementation) {
-    assert defaultInterfaceMethodImplementation == null;
-    assert implementation != null;
-    assert code != null;
-    // TODO(b/183998768): Once R8 desugars in the enqueuer this should always be invalid code.
-    assert InvalidCode.isInvalidCode(code) || code == implementation.getCode();
-    accessFlags.setAbstract();
-    removeCode();
-    defaultInterfaceMethodImplementation = implementation;
-  }
-
   /**
    * Flags this method as no longer being obsolete.
    *
diff --git a/src/main/java/com/android/tools/r8/graph/DexMethod.java b/src/main/java/com/android/tools/r8/graph/DexMethod.java
index 5eaf805..c2eeffa 100644
--- a/src/main/java/com/android/tools/r8/graph/DexMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/DexMethod.java
@@ -218,14 +218,6 @@
     return false;
   }
 
-  /**
-   * Returns true if the other method has the same name and prototype (including signature and
-   * return type), false otherwise.
-   */
-  public boolean hasSameProtoAndName(DexMethod other) {
-    return name == other.name && proto == other.proto;
-  }
-
   @Override
   public boolean match(DexMethod method) {
     return match(method.getProto(), method.getName());
diff --git a/src/main/java/com/android/tools/r8/graph/FieldAccessFlags.java b/src/main/java/com/android/tools/r8/graph/FieldAccessFlags.java
index 850c3cb..29e68bf 100644
--- a/src/main/java/com/android/tools/r8/graph/FieldAccessFlags.java
+++ b/src/main/java/com/android/tools/r8/graph/FieldAccessFlags.java
@@ -71,6 +71,11 @@
         Constants.ACC_PUBLIC | Constants.ACC_STATIC | Constants.ACC_SYNTHETIC);
   }
 
+  public static FieldAccessFlags createPrivateStaticSynthetic() {
+    return fromSharedAccessFlags(
+        Constants.ACC_PRIVATE | Constants.ACC_STATIC | Constants.ACC_SYNTHETIC);
+  }
+
   public static FieldAccessFlags createPublicFinalSynthetic() {
     return fromSharedAccessFlags(
         Constants.ACC_PUBLIC | Constants.ACC_FINAL | Constants.ACC_SYNTHETIC);
diff --git a/src/main/java/com/android/tools/r8/graph/GraphLens.java b/src/main/java/com/android/tools/r8/graph/GraphLens.java
index 37dea58..a81d742 100644
--- a/src/main/java/com/android/tools/r8/graph/GraphLens.java
+++ b/src/main/java/com/android/tools/r8/graph/GraphLens.java
@@ -8,7 +8,6 @@
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.ir.code.Invoke.Type;
 import com.android.tools.r8.ir.conversion.LensCodeRewriterUtils;
-import com.android.tools.r8.ir.desugar.itf.InterfaceProcessor.InterfaceProcessorNestedGraphLens;
 import com.android.tools.r8.optimize.MemberRebindingIdentityLens;
 import com.android.tools.r8.optimize.MemberRebindingLens;
 import com.android.tools.r8.shaking.KeepInfoCollection;
@@ -480,14 +479,6 @@
     return null;
   }
 
-  public boolean isInterfaceProcessorLens() {
-    return false;
-  }
-
-  public InterfaceProcessorNestedGraphLens asInterfaceProcessorLens() {
-    return null;
-  }
-
   public GraphLens withCodeRewritingsApplied(DexItemFactory dexItemFactory) {
     if (hasCodeRewritings()) {
       return new ClearCodeRewritingGraphLens(dexItemFactory, this);
diff --git a/src/main/java/com/android/tools/r8/graph/JarClassFileReader.java b/src/main/java/com/android/tools/r8/graph/JarClassFileReader.java
index 55482fc..2f052aa 100644
--- a/src/main/java/com/android/tools/r8/graph/JarClassFileReader.java
+++ b/src/main/java/com/android/tools/r8/graph/JarClassFileReader.java
@@ -516,10 +516,6 @@
       if (!accessFlags.isRecord()) {
         return;
       }
-      // TODO(b/169645628): Support records in all compilation.
-      if (!application.options.enableExperimentalRecordDesugaring()) {
-        throw new CompilationError("Records are not supported", origin);
-      }
       // TODO(b/169645628): Change this logic if we start stripping the record components.
       // Another approach would be to mark a bit in fields that are record components instead.
       String message = "Records are expected to have one record component per instance field.";
diff --git a/src/main/java/com/android/tools/r8/graph/LazyCfCode.java b/src/main/java/com/android/tools/r8/graph/LazyCfCode.java
index 174dca5..c554ca5 100644
--- a/src/main/java/com/android/tools/r8/graph/LazyCfCode.java
+++ b/src/main/java/com/android/tools/r8/graph/LazyCfCode.java
@@ -10,6 +10,7 @@
 import com.android.tools.r8.cf.code.CfCheckCast;
 import com.android.tools.r8.cf.code.CfCmp;
 import com.android.tools.r8.cf.code.CfConstClass;
+import com.android.tools.r8.cf.code.CfConstDynamic;
 import com.android.tools.r8.cf.code.CfConstMethodHandle;
 import com.android.tools.r8.cf.code.CfConstMethodType;
 import com.android.tools.r8.cf.code.CfConstNull;
@@ -887,7 +888,9 @@
             new CfConstMethodHandle(
                 DexMethodHandle.fromAsmHandle((Handle) cst, application, method.holder)));
       } else if (cst instanceof ConstantDynamic) {
-        throw new CompilationError("Unsupported dynamic constant: " + cst.toString());
+        instructions.add(
+            CfConstDynamic.fromAsmConstantDynamic(
+                (ConstantDynamic) cst, application, method.holder));
       } else {
         throw new CompilationError("Unsupported constant: " + cst.toString());
       }
diff --git a/src/main/java/com/android/tools/r8/graph/ObjectAllocationInfoCollectionImpl.java b/src/main/java/com/android/tools/r8/graph/ObjectAllocationInfoCollectionImpl.java
index 7f09192..459c008 100644
--- a/src/main/java/com/android/tools/r8/graph/ObjectAllocationInfoCollectionImpl.java
+++ b/src/main/java/com/android/tools/r8/graph/ObjectAllocationInfoCollectionImpl.java
@@ -534,7 +534,8 @@
     private boolean verifyAllSuperTypesAreInHierarchy(
         DexDefinitionSupplier definitions, Iterable<DexType> dexTypes) {
       for (DexType supertype : dexTypes) {
-        assert typeIsInHierarchy(definitions, supertype);
+        assert typeIsInHierarchy(definitions, supertype)
+            : "Type not found in hierarchy: " + supertype;
       }
       return true;
     }
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
index c00f43c..8f6f293 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
@@ -4,7 +4,6 @@
 package com.android.tools.r8.ir.conversion;
 
 import static com.android.tools.r8.ir.desugar.itf.InterfaceMethodRewriter.Flavor.ExcludeDexResources;
-import static com.android.tools.r8.ir.desugar.itf.InterfaceMethodRewriter.Flavor.IncludeAllResources;
 import static com.android.tools.r8.ir.desugar.lambda.D8LambdaDesugaring.rewriteEnclosingLambdaMethodAttributes;
 
 import com.android.tools.r8.contexts.CompilationContext.MethodProcessingContext;
@@ -58,7 +57,6 @@
 import com.android.tools.r8.ir.desugar.itf.EmulatedInterfaceApplicationRewriter;
 import com.android.tools.r8.ir.desugar.itf.InterfaceMethodProcessorFacade;
 import com.android.tools.r8.ir.desugar.itf.InterfaceMethodRewriter;
-import com.android.tools.r8.ir.desugar.itf.InterfaceMethodRewriter.Flavor;
 import com.android.tools.r8.ir.desugar.lambda.LambdaDeserializationMethodRemover;
 import com.android.tools.r8.ir.desugar.nest.D8NestBasedAccessDesugaring;
 import com.android.tools.r8.ir.optimize.AssertionsRewriter;
@@ -362,23 +360,6 @@
     }
   }
 
-  private void finalizeInterfaceMethodRewritingThroughIR(ExecutorService executorService)
-      throws ExecutionException {
-    assert !appView.getSyntheticItems().hasPendingSyntheticClasses();
-    if (interfaceMethodRewriter != null) {
-      interfaceMethodRewriter.finalizeInterfaceMethodRewritingThroughIR(this, executorService);
-    }
-  }
-
-  private void runInterfaceDesugaringProcessorsForR8(
-      Flavor includeAllResources, ExecutorService executorService) throws ExecutionException {
-    assert !appView.getSyntheticItems().hasPendingSyntheticClasses();
-    if (interfaceMethodRewriter != null) {
-      interfaceMethodRewriter.runInterfaceDesugaringProcessorsForR8(
-          this, includeAllResources, executorService);
-    }
-  }
-
   private void processCovariantReturnTypeAnnotations(Builder<?> builder) {
     if (covariantReturnTypeAnnotationTransformer != null) {
       covariantReturnTypeAnnotationTransformer.process(builder);
@@ -460,10 +441,11 @@
         CfPostProcessingDesugaringEventConsumer.createForD8(methodProcessor, instructionDesugaring);
     methodProcessor.newWave();
     InterfaceMethodProcessorFacade interfaceDesugaring =
-        instructionDesugaring.getInterfaceMethodPostProcessingDesugaring(ExcludeDexResources);
+        instructionDesugaring.getInterfaceMethodPostProcessingDesugaringD8(ExcludeDexResources);
     CfPostProcessingDesugaringCollection.create(
             appView, interfaceDesugaring, instructionDesugaring.getRetargetingInfo())
-        .postProcessingDesugaring(appView.appInfo().classes(), eventConsumer, executorService);
+        .postProcessingDesugaring(
+            appView.appInfo().classes(), m -> true, eventConsumer, executorService);
     methodProcessor.awaitMethodProcessing();
     eventConsumer.finalizeDesugaring();
   }
@@ -789,11 +771,6 @@
     Builder<?> builder = appView.appInfo().app().builder();
     builder.setHighestSortingString(highestSortingString);
 
-    printPhase("Interface method desugaring");
-    finalizeInterfaceMethodRewritingThroughIR(executorService);
-    runInterfaceDesugaringProcessorsForR8(IncludeAllResources, executorService);
-    feedback.updateVisibleOptimizationInfo();
-
     if (serviceLoaderRewriter != null) {
       processSynthesizedServiceLoaderMethods(
           serviceLoaderRewriter.getServiceLoadMethods(), executorService);
@@ -1466,14 +1443,6 @@
 
     previous = printMethod(code, "IR after class inlining (SSA)", previous);
 
-    // TODO(b/183998768): Enable interface method rewriter cf to cf also in R8.
-    if (interfaceMethodRewriter != null && appView.enableWholeProgramOptimizations()) {
-      timing.begin("Rewrite interface methods");
-      interfaceMethodRewriter.rewriteMethodReferences(
-          code, methodProcessor, methodProcessingContext);
-      timing.end();
-    }
-
     assert code.verifyTypes(appView);
 
     previous = printMethod(code, "IR after interface method rewriting (SSA)", previous);
@@ -1504,12 +1473,15 @@
       timing.begin("Canonicalize constants");
       constantCanonicalizer.canonicalize(appView, code);
       timing.end();
+      previous = printMethod(code, "IR after constant canonicalization (SSA)", previous);
       timing.begin("Create constants for literal instructions");
       codeRewriter.useDedicatedConstantForLitInstruction(code);
       timing.end();
+      previous = printMethod(code, "IR after constant literals (SSA)", previous);
       timing.begin("Shorten live ranges");
       codeRewriter.shortenLiveRanges(code);
       timing.end();
+      previous = printMethod(code, "IR after shorten live ranges (SSA)", previous);
     }
 
     timing.begin("Canonicalize idempotent calls");
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java b/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java
index 837d5fc..73f0fe5 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java
@@ -707,9 +707,8 @@
       DexType oldType,
       DexType newType,
       Value initialValue) {
-    if (initialValue.isConstNumber()
-        && initialValue.definition.asConstNumber().isZero()
-        && defaultValueHasChanged(oldType, newType)) {
+    if (initialValue.getType().isNullType() && defaultValueHasChanged(oldType, newType)) {
+      assert newType.isIntType();
       iterator.previous();
       Value rewrittenDefaultValue =
           iterator.insertConstNumberInstruction(
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/CfInstructionDesugaringCollection.java b/src/main/java/com/android/tools/r8/ir/desugar/CfInstructionDesugaringCollection.java
index 10032bc..5d4f7b6 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/CfInstructionDesugaringCollection.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/CfInstructionDesugaringCollection.java
@@ -11,9 +11,11 @@
 import com.android.tools.r8.ir.desugar.desugaredlibrary.RetargetingInfo;
 import com.android.tools.r8.ir.desugar.itf.InterfaceMethodProcessorFacade;
 import com.android.tools.r8.ir.desugar.itf.InterfaceMethodRewriter.Flavor;
+import com.android.tools.r8.ir.desugar.itf.InterfaceProcessor;
 import com.android.tools.r8.ir.desugar.nest.D8NestBasedAccessDesugaring;
 import com.android.tools.r8.utils.ThrowingConsumer;
 import java.util.function.Consumer;
+import java.util.function.Predicate;
 
 /**
  * Abstracts a collection of low-level desugarings (i.e., mappings from class-file instructions to
@@ -60,9 +62,12 @@
   public abstract <T extends Throwable> void withD8NestBasedAccessDesugaring(
       ThrowingConsumer<D8NestBasedAccessDesugaring, T> consumer) throws T;
 
-  public abstract InterfaceMethodProcessorFacade getInterfaceMethodPostProcessingDesugaring(
+  public abstract InterfaceMethodProcessorFacade getInterfaceMethodPostProcessingDesugaringD8(
       Flavor flavor);
 
+  public abstract InterfaceMethodProcessorFacade getInterfaceMethodPostProcessingDesugaringR8(
+      Flavor flavor, Predicate<ProgramMethod> isLiveMethod, InterfaceProcessor processor);
+
   public abstract RetargetingInfo getRetargetingInfo();
 
   public abstract void withDesugaredLibraryAPIConverter(
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/CfInstructionDesugaringEventConsumer.java b/src/main/java/com/android/tools/r8/ir/desugar/CfInstructionDesugaringEventConsumer.java
index 4f6dd6c..2493dd3 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/CfInstructionDesugaringEventConsumer.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/CfInstructionDesugaringEventConsumer.java
@@ -14,6 +14,8 @@
 import com.android.tools.r8.ir.conversion.ClassConverterResult;
 import com.android.tools.r8.ir.conversion.D8MethodProcessor;
 import com.android.tools.r8.ir.desugar.backports.BackportedMethodDesugaringEventConsumer;
+import com.android.tools.r8.ir.desugar.constantdynamic.ConstantDynamicClass;
+import com.android.tools.r8.ir.desugar.constantdynamic.ConstantDynamicDesugaringEventConsumer;
 import com.android.tools.r8.ir.desugar.desugaredlibrary.DesugaredLibraryRetargeterSynthesizerEventConsumer.DesugaredLibraryRetargeterInstructionEventConsumer;
 import com.android.tools.r8.ir.desugar.desugaredlibrary.DesugaredLibraryWrapperSynthesizerEventConsumer.DesugaredLibraryAPIConverterEventConsumer;
 import com.android.tools.r8.ir.desugar.invokespecial.InvokeSpecialBridgeInfo;
@@ -45,6 +47,7 @@
     implements BackportedMethodDesugaringEventConsumer,
         InvokeSpecialToSelfDesugaringEventConsumer,
         LambdaDesugaringEventConsumer,
+        ConstantDynamicDesugaringEventConsumer,
         NestBasedAccessDesugaringEventConsumer,
         RecordInstructionDesugaringEventConsumer,
         TwrCloseResourceDesugaringEventConsumer,
@@ -61,16 +64,32 @@
   public static R8CfInstructionDesugaringEventConsumer createForR8(
       AppView<? extends AppInfoWithClassHierarchy> appView,
       BiConsumer<LambdaClass, ProgramMethod> lambdaClassConsumer,
+      BiConsumer<ConstantDynamicClass, ProgramMethod> constantDynamicClassConsumer,
       BiConsumer<ProgramMethod, ProgramMethod> twrCloseResourceMethodConsumer,
-      SyntheticAdditions additions) {
+      SyntheticAdditions additions,
+      BiConsumer<ProgramMethod, ProgramMethod> companionMethodConsumer) {
     return new R8CfInstructionDesugaringEventConsumer(
-        appView, lambdaClassConsumer, twrCloseResourceMethodConsumer, additions);
+        appView,
+        lambdaClassConsumer,
+        constantDynamicClassConsumer,
+        twrCloseResourceMethodConsumer,
+        additions,
+        companionMethodConsumer);
   }
 
+  // TODO(b/183998768): Remove this event consumer. It should be unneeded for R8 and for D8 the
+  //  desugaring of interface methods should be able to happen up front too avoiding the companion
+  //  callback on nest accessors.
   public static CfInstructionDesugaringEventConsumer createForDesugaredCode() {
     return new CfInstructionDesugaringEventConsumer() {
 
       @Override
+      public void acceptCompanionMethod(ProgramMethod method, ProgramMethod companionMethod) {
+        // A synthesized nest based accessor may itself be defined on an interface, in which case
+        // desugaring the accessor will result in a rewrite to the companion method.
+      }
+
+      @Override
       public void acceptClasspathEmulatedInterface(DexClasspathClass clazz) {
         assert false;
       }
@@ -127,6 +146,12 @@
       }
 
       @Override
+      public void acceptConstantDynamicClass(
+          ConstantDynamicClass constantDynamicClass, ProgramMethod context) {
+        assert false;
+      }
+
+      @Override
       public void acceptNestFieldGetBridge(ProgramField target, ProgramMethod bridge) {
         assert false;
       }
@@ -161,12 +186,18 @@
     private final Map<DexReference, InvokeSpecialBridgeInfo> pendingInvokeSpecialBridges =
         new LinkedHashMap<>();
     private final List<LambdaClass> synthesizedLambdaClasses = new ArrayList<>();
+    private final List<ConstantDynamicClass> synthesizedConstantDynamicClasses = new ArrayList<>();
 
     private D8CfInstructionDesugaringEventConsumer(D8MethodProcessor methodProcessor) {
       this.methodProcessor = methodProcessor;
     }
 
     @Override
+    public void acceptCompanionMethod(ProgramMethod method, ProgramMethod companionMethod) {
+      // Intentionally empty. Methods are moved when processing the interface definition.
+    }
+
+    @Override
     public void acceptDesugaredLibraryRetargeterDispatchClasspathClass(DexClasspathClass clazz) {
       // Intentionally empty.
     }
@@ -207,6 +238,14 @@
     }
 
     @Override
+    public void acceptConstantDynamicClass(
+        ConstantDynamicClass constantDynamicClass, ProgramMethod context) {
+      synchronized (synthesizedConstantDynamicClasses) {
+        synthesizedConstantDynamicClasses.add(constantDynamicClass);
+      }
+    }
+
+    @Override
     public void acceptNestFieldGetBridge(ProgramField target, ProgramMethod bridge) {
       assert false;
     }
@@ -257,6 +296,7 @@
       List<ProgramMethod> needsProcessing = new ArrayList<>();
       finalizeInvokeSpecialDesugaring(appView, needsProcessing::add);
       finalizeLambdaDesugaring(classConverterResultBuilder, needsProcessing::add);
+      finalizeConstantDynamicDesugaring(needsProcessing::add);
       return needsProcessing;
     }
 
@@ -299,6 +339,13 @@
       synthesizedLambdaClasses.clear();
     }
 
+    private void finalizeConstantDynamicDesugaring(Consumer<ProgramMethod> needsProcessing) {
+      for (ConstantDynamicClass constantDynamicClass : synthesizedConstantDynamicClasses) {
+        constantDynamicClass.getConstantDynamicProgramClass().forEachProgramMethod(needsProcessing);
+      }
+      synthesizedLambdaClasses.clear();
+    }
+
     public boolean verifyNothingToFinalize() {
       assert pendingInvokeSpecialBridges.isEmpty();
       assert synthesizedLambdaClasses.isEmpty();
@@ -311,25 +358,39 @@
 
     private final AppView<? extends AppInfoWithClassHierarchy> appView;
 
-    // TODO(b/180091213): Remove these two consumers when synthesizing contexts are accessible from
+    // TODO(b/180091213): Remove these three consumers when synthesizing contexts are accessible
+    // from
     //  synthetic items.
     private final BiConsumer<LambdaClass, ProgramMethod> lambdaClassConsumer;
+    private final BiConsumer<ConstantDynamicClass, ProgramMethod> constantDynamicClassConsumer;
     private final BiConsumer<ProgramMethod, ProgramMethod> twrCloseResourceMethodConsumer;
     private final SyntheticAdditions additions;
 
     private final Map<LambdaClass, ProgramMethod> synthesizedLambdaClasses =
         new IdentityHashMap<>();
     private final List<InvokeSpecialBridgeInfo> pendingInvokeSpecialBridges = new ArrayList<>();
+    private final List<ConstantDynamicClass> synthesizedConstantDynamicClasses = new ArrayList<>();
+
+    private final BiConsumer<ProgramMethod, ProgramMethod> onCompanionMethodCallback;
 
     public R8CfInstructionDesugaringEventConsumer(
         AppView<? extends AppInfoWithClassHierarchy> appView,
         BiConsumer<LambdaClass, ProgramMethod> lambdaClassConsumer,
+        BiConsumer<ConstantDynamicClass, ProgramMethod> constantDynamicClassConsumer,
         BiConsumer<ProgramMethod, ProgramMethod> twrCloseResourceMethodConsumer,
-        SyntheticAdditions additions) {
+        SyntheticAdditions additions,
+        BiConsumer<ProgramMethod, ProgramMethod> onCompanionMethodCallback) {
       this.appView = appView;
       this.lambdaClassConsumer = lambdaClassConsumer;
+      this.constantDynamicClassConsumer = constantDynamicClassConsumer;
       this.twrCloseResourceMethodConsumer = twrCloseResourceMethodConsumer;
       this.additions = additions;
+      this.onCompanionMethodCallback = onCompanionMethodCallback;
+    }
+
+    @Override
+    public void acceptCompanionMethod(ProgramMethod method, ProgramMethod companionMethod) {
+      onCompanionMethodCallback.accept(method, companionMethod);
     }
 
     @Override
@@ -344,7 +405,7 @@
 
     @Override
     public void acceptCompanionClassClinit(ProgramMethod method) {
-      // TODO(b/183998768): Update this once desugaring is moved to the enqueuer.
+      // Intentionally empty. The method will be hit by tracing if required.
     }
 
     @Override
@@ -354,19 +415,19 @@
 
     @Override
     public void acceptRecordMethod(ProgramMethod method) {
-      // Intentionally empty. The method will be hit by the tracing in R8 as if it was
-      // present in the input code, and thus nothing needs to be done.
+      // Intentionally empty. The method will be hit by tracing if required.
     }
 
     @Override
     public void acceptThrowMethod(ProgramMethod method, ProgramMethod context) {
-      assert false : "TODO(b/183998768): To be implemented";
+      // Intentionally empty. The method will be hit by tracing if required.
     }
 
     @Override
     public void acceptInvokeStaticInterfaceOutliningMethod(
         ProgramMethod method, ProgramMethod context) {
-      assert false : "TODO(b/183998768): To be implemented";
+      // Intentionally empty. The method will be hit by tracing if required.
+      additions.addNeverInlineMethod(method);
     }
 
     @Override
@@ -376,14 +437,12 @@
 
     @Override
     public void acceptAPIConversion(ProgramMethod method) {
-      // Intentionally empty. The method will be hit by the tracing in R8 as if it was
-      // present in the input code, and thus nothing needs to be done.
+      // Intentionally empty. The method will be hit by tracing if required.
     }
 
     @Override
     public void acceptBackportedMethod(ProgramMethod backportedMethod, ProgramMethod context) {
-      // Intentionally empty. The backported method will be hit by the tracing in R8 as if it was
-      // present in the input code, and thus nothing needs to be done.
+      // Intentionally empty. The method will be hit by tracing if required.
     }
 
     @Override
@@ -404,6 +463,17 @@
     }
 
     @Override
+    public void acceptConstantDynamicClass(
+        ConstantDynamicClass constantDynamicClass, ProgramMethod context) {
+      synchronized (synthesizedConstantDynamicClasses) {
+        synthesizedConstantDynamicClasses.add(constantDynamicClass);
+      }
+      // TODO(b/180091213): Remove the recording of the synthesizing context when this is accessible
+      //  from synthetic items.
+      constantDynamicClassConsumer.accept(constantDynamicClass, context);
+    }
+
+    @Override
     public void acceptNestFieldGetBridge(ProgramField target, ProgramMethod bridge) {
       assert false;
     }
@@ -420,8 +490,6 @@
 
     @Override
     public void acceptTwrCloseResourceMethod(ProgramMethod closeMethod, ProgramMethod context) {
-      // Intentionally empty. The close method will be hit by the tracing in R8 as if they were
-      // present in the input code, and thus nothing needs to be done.
       // TODO(b/180091213): Remove the recording of the synthesizing context when this is accessible
       //  from synthetic items.
       twrCloseResourceMethodConsumer.accept(closeMethod, context);
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/CfPostProcessingDesugaringCollection.java b/src/main/java/com/android/tools/r8/ir/desugar/CfPostProcessingDesugaringCollection.java
index dbba296..a0d130d 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/CfPostProcessingDesugaringCollection.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/CfPostProcessingDesugaringCollection.java
@@ -5,6 +5,7 @@
 
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.desugar.desugaredlibrary.DesugaredLibraryAPICallbackSynthesizor;
 import com.android.tools.r8.ir.desugar.desugaredlibrary.DesugaredLibraryRetargeterPostProcessor;
 import com.android.tools.r8.ir.desugar.desugaredlibrary.RetargetingInfo;
@@ -15,6 +16,7 @@
 import java.util.List;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.ExecutorService;
+import java.util.function.Predicate;
 
 public abstract class CfPostProcessingDesugaringCollection {
 
@@ -35,6 +37,7 @@
 
   public abstract void postProcessingDesugaring(
       Collection<DexProgramClass> programClasses,
+      Predicate<ProgramMethod> isLiveMethod,
       CfPostProcessingDesugaringEventConsumer eventConsumer,
       ExecutorService executorService)
       throws ExecutionException;
@@ -83,6 +86,7 @@
     @Override
     public void postProcessingDesugaring(
         Collection<DexProgramClass> programClasses,
+        Predicate<ProgramMethod> isLiveMethod,
         CfPostProcessingDesugaringEventConsumer eventConsumer,
         ExecutorService executorService)
         throws ExecutionException {
@@ -107,6 +111,7 @@
     @Override
     public void postProcessingDesugaring(
         Collection<DexProgramClass> programClasses,
+        Predicate<ProgramMethod> isLiveMethod,
         CfPostProcessingDesugaringEventConsumer eventConsumer,
         ExecutorService executorService)
         throws ExecutionException {
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/CfPostProcessingDesugaringEventConsumer.java b/src/main/java/com/android/tools/r8/ir/desugar/CfPostProcessingDesugaringEventConsumer.java
index 66042a0..e4a34c4 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/CfPostProcessingDesugaringEventConsumer.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/CfPostProcessingDesugaringEventConsumer.java
@@ -30,8 +30,9 @@
     return new D8CfPostProcessingDesugaringEventConsumer(methodProcessor, instructionDesugaring);
   }
 
-  public static R8PostProcessingDesugaringEventConsumer createForR8(SyntheticAdditions additions) {
-    return new R8PostProcessingDesugaringEventConsumer(additions);
+  public static R8PostProcessingDesugaringEventConsumer createForR8(
+      SyntheticAdditions additions, CfInstructionDesugaringCollection desugaring) {
+    return new R8PostProcessingDesugaringEventConsumer(additions, desugaring);
   }
 
   public abstract void finalizeDesugaring() throws ExecutionException;
@@ -53,10 +54,8 @@
     }
 
     private void addMethodToReprocess(ProgramMethod method) {
-      if (instructionDesugaring.needsDesugaring(method)) {
-        instructionDesugaring.needsDesugaring(method);
-      }
       assert !instructionDesugaring.needsDesugaring(method);
+      assert method.getDefinition().getCode().isCfCode();
       methodsToReprocess.add(method);
     }
 
@@ -71,13 +70,24 @@
     }
 
     @Override
+    public void acceptEmulatedInterfaceMarkerInterface(
+        DexProgramClass clazz, DexClasspathClass newInterface) {
+      // Intentionally empty.
+    }
+
+    @Override
     public void acceptForwardingMethod(ProgramMethod method) {
       addMethodToReprocess(method);
     }
 
     @Override
     public void acceptCompanionClassClinit(ProgramMethod method) {
-      methodsToReprocess.add(method);
+      addMethodToReprocess(method);
+    }
+
+    @Override
+    public void acceptCompanionMethod(ProgramMethod method, ProgramMethod companion) {
+      // Intentionally empty. The method must be processed on the interface definition.
     }
 
     @Override
@@ -103,17 +113,27 @@
       extends CfPostProcessingDesugaringEventConsumer {
 
     private final SyntheticAdditions additions;
+    private final CfInstructionDesugaringCollection desugaring;
 
-    R8PostProcessingDesugaringEventConsumer(SyntheticAdditions additions) {
+    R8PostProcessingDesugaringEventConsumer(
+        SyntheticAdditions additions, CfInstructionDesugaringCollection desugaring) {
       this.additions = additions;
+      this.desugaring = desugaring;
     }
 
     @Override
-    public void finalizeDesugaring() throws ExecutionException {
+    public void finalizeDesugaring() {
       // Intentionally empty.
     }
 
     @Override
+    public void acceptEmulatedInterfaceMarkerInterface(
+        DexProgramClass clazz, DexClasspathClass newInterface) {
+      additions.injectInterface(clazz, newInterface);
+      additions.addLiveClasspathClass(newInterface);
+    }
+
+    @Override
     public void acceptInterfaceInjection(DexProgramClass clazz, DexClass newInterface) {
       additions.injectInterface(clazz, newInterface);
     }
@@ -130,11 +150,19 @@
 
     @Override
     public void acceptCompanionClassClinit(ProgramMethod method) {
-      assert false : "TODO(b/183998768): Support Interface processing in R8";
+      // Generation of this method must have been done during enqueuing.
+      assert false;
+    }
+
+    @Override
+    public void acceptCompanionMethod(ProgramMethod method, ProgramMethod companion) {
+      // Generation of this method must have been done during enqueuing.
+      assert false;
     }
 
     @Override
     public void acceptAPIConversionCallback(ProgramMethod method) {
+      assert !desugaring.needsDesugaring(method);
       additions.addLiveMethod(method);
     }
 
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/EmptyCfInstructionDesugaringCollection.java b/src/main/java/com/android/tools/r8/ir/desugar/EmptyCfInstructionDesugaringCollection.java
index b23f31e..027b27c 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/EmptyCfInstructionDesugaringCollection.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/EmptyCfInstructionDesugaringCollection.java
@@ -10,9 +10,11 @@
 import com.android.tools.r8.ir.desugar.desugaredlibrary.RetargetingInfo;
 import com.android.tools.r8.ir.desugar.itf.InterfaceMethodProcessorFacade;
 import com.android.tools.r8.ir.desugar.itf.InterfaceMethodRewriter.Flavor;
+import com.android.tools.r8.ir.desugar.itf.InterfaceProcessor;
 import com.android.tools.r8.ir.desugar.nest.D8NestBasedAccessDesugaring;
 import com.android.tools.r8.utils.ThrowingConsumer;
 import java.util.function.Consumer;
+import java.util.function.Predicate;
 
 public class EmptyCfInstructionDesugaringCollection extends CfInstructionDesugaringCollection {
 
@@ -61,7 +63,14 @@
   }
 
   @Override
-  public InterfaceMethodProcessorFacade getInterfaceMethodPostProcessingDesugaring(Flavor flavor) {
+  public InterfaceMethodProcessorFacade getInterfaceMethodPostProcessingDesugaringD8(
+      Flavor flavor) {
+    return null;
+  }
+
+  @Override
+  public InterfaceMethodProcessorFacade getInterfaceMethodPostProcessingDesugaringR8(
+      Flavor flavor, Predicate<ProgramMethod> isLiveMethod, InterfaceProcessor processor) {
     return null;
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/LambdaClass.java b/src/main/java/com/android/tools/r8/ir/desugar/LambdaClass.java
index 61e6828..dd815a3 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/LambdaClass.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/LambdaClass.java
@@ -6,6 +6,7 @@
 
 import static com.android.tools.r8.ir.desugar.lambda.ForcefullyMovedLambdaMethodConsumer.emptyForcefullyMovedLambdaMethodConsumer;
 import static com.android.tools.r8.utils.ConsumerUtils.emptyConsumer;
+import static com.android.tools.r8.utils.DesugarUtils.appendFullyQualifiedHolderToMethodName;
 
 import com.android.tools.r8.dex.Constants;
 import com.android.tools.r8.errors.Unimplemented;
@@ -352,17 +353,14 @@
     } else {
       // Otherwise we need to ensure the method can be reached publicly by virtual dispatch.
       // To avoid potential conflicts on the name of the lambda method once dispatch becomes virtual
-      // we add the method-holder name as suffix to the lambda-method name.
+      // we add the fully qualified method-holder name as suffix to the lambda-method name.
       return new InstanceLambdaImplTarget(
           appView
               .dexItemFactory()
               .createMethod(
                   implMethod.holder,
                   implMethod.proto,
-                  appView
-                      .dexItemFactory()
-                      .createString(
-                          implMethod.name.toString() + "$" + implMethod.holder.getName())));
+                  appendFullyQualifiedHolderToMethodName(implMethod, appView.dexItemFactory())));
     }
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/NonEmptyCfInstructionDesugaringCollection.java b/src/main/java/com/android/tools/r8/ir/desugar/NonEmptyCfInstructionDesugaringCollection.java
index b117828..bd9805d 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/NonEmptyCfInstructionDesugaringCollection.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/NonEmptyCfInstructionDesugaringCollection.java
@@ -12,6 +12,7 @@
 import com.android.tools.r8.graph.CfCode;
 import com.android.tools.r8.graph.Code;
 import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.ir.desugar.constantdynamic.ConstantDynamicInstructionDesugaring;
 import com.android.tools.r8.ir.desugar.desugaredlibrary.DesugaredLibraryAPIConverter;
 import com.android.tools.r8.ir.desugar.desugaredlibrary.DesugaredLibraryRetargeter;
 import com.android.tools.r8.ir.desugar.desugaredlibrary.RetargetingInfo;
@@ -19,6 +20,7 @@
 import com.android.tools.r8.ir.desugar.itf.InterfaceMethodProcessorFacade;
 import com.android.tools.r8.ir.desugar.itf.InterfaceMethodRewriter;
 import com.android.tools.r8.ir.desugar.itf.InterfaceMethodRewriter.Flavor;
+import com.android.tools.r8.ir.desugar.itf.InterfaceProcessor;
 import com.android.tools.r8.ir.desugar.lambda.LambdaInstructionDesugaring;
 import com.android.tools.r8.ir.desugar.nest.D8NestBasedAccessDesugaring;
 import com.android.tools.r8.ir.desugar.nest.NestBasedAccessDesugaring;
@@ -35,6 +37,7 @@
 import java.util.Iterator;
 import java.util.List;
 import java.util.function.Consumer;
+import java.util.function.Predicate;
 
 public class NonEmptyCfInstructionDesugaringCollection extends CfInstructionDesugaringCollection {
 
@@ -73,9 +76,7 @@
     if (appView.options().enableTryWithResourcesDesugaring()) {
       desugarings.add(new TwrInstructionDesugaring(appView));
     }
-    // TODO(b/183998768): Enable interface method rewriter cf to cf also in R8.
-    if (appView.options().isInterfaceMethodDesugaringEnabled()
-        && !appView.enableWholeProgramOptimizations()) {
+    if (appView.options().isInterfaceMethodDesugaringEnabled()) {
       interfaceMethodRewriter =
           new InterfaceMethodRewriter(
               appView, backportedMethodRewriter, desugaredLibraryRetargeter);
@@ -83,18 +84,11 @@
     } else {
       interfaceMethodRewriter = null;
     }
-    // In R8 interface method rewriting is performed in IR, we still need to filter
-    // out from API conversion methods desugared by the interface method rewriter.
-    InterfaceMethodRewriter enforcedInterfaceMethodRewriter =
-        interfaceMethodRewriter == null && appView.options().isInterfaceMethodDesugaringEnabled()
-            ? new InterfaceMethodRewriter(
-                appView, backportedMethodRewriter, desugaredLibraryRetargeter)
-            : interfaceMethodRewriter;
     desugaredLibraryAPIConverter =
         appView.rewritePrefix.isRewriting()
             ? new DesugaredLibraryAPIConverter(
                 appView,
-                enforcedInterfaceMethodRewriter,
+                interfaceMethodRewriter,
                 desugaredLibraryRetargeter,
                 backportedMethodRewriter)
             : null;
@@ -102,6 +96,7 @@
       desugarings.add(desugaredLibraryAPIConverter);
     }
     desugarings.add(new LambdaInstructionDesugaring(appView));
+    desugarings.add(new ConstantDynamicInstructionDesugaring(appView));
     desugarings.add(new InvokeSpecialToSelfDesugaring(appView));
     desugarings.add(new InvokeToPrivateRewriter());
     desugarings.add(new StringConcatInstructionDesugaring(appView));
@@ -182,7 +177,7 @@
     IntBox maxStackForInstruction = new IntBox(cfCode.getMaxStack());
 
     List<CfInstruction> desugaredInstructions =
-        ListUtils.flatMap(
+        ListUtils.flatMapSameType(
             cfCode.getInstructions(),
             instruction -> {
               Collection<CfInstruction> replacement =
@@ -305,7 +300,7 @@
           assert !alsoApplicable
                   || (appliedDesugaring instanceof InterfaceMethodRewriter
                       && (desugaring instanceof InvokeToPrivateRewriter
-                          || desugaring instanceof D8NestBasedAccessDesugaring))
+                          || desugaring instanceof NestBasedAccessDesugaring))
                   || (appliedDesugaring instanceof TwrInstructionDesugaring
                       && desugaring instanceof InterfaceMethodRewriter)
               : "Desugaring of "
@@ -330,9 +325,18 @@
   }
 
   @Override
-  public InterfaceMethodProcessorFacade getInterfaceMethodPostProcessingDesugaring(Flavor flavor) {
+  public InterfaceMethodProcessorFacade getInterfaceMethodPostProcessingDesugaringD8(
+      Flavor flavor) {
     return interfaceMethodRewriter != null
-        ? interfaceMethodRewriter.getPostProcessingDesugaring(flavor)
+        ? interfaceMethodRewriter.getPostProcessingDesugaringD8(flavor)
+        : null;
+  }
+
+  @Override
+  public InterfaceMethodProcessorFacade getInterfaceMethodPostProcessingDesugaringR8(
+      Flavor flavor, Predicate<ProgramMethod> isLiveMethod, InterfaceProcessor processor) {
+    return interfaceMethodRewriter != null
+        ? interfaceMethodRewriter.getPostProcessingDesugaringR8(flavor, isLiveMethod, processor)
         : null;
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/constantdynamic/ConstantDynamicClass.java b/src/main/java/com/android/tools/r8/ir/desugar/constantdynamic/ConstantDynamicClass.java
new file mode 100644
index 0000000..bd2e603
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/desugar/constantdynamic/ConstantDynamicClass.java
@@ -0,0 +1,275 @@
+// Copyright (c) 2021, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.ir.desugar.constantdynamic;
+
+import static org.objectweb.asm.Opcodes.GETSTATIC;
+import static org.objectweb.asm.Opcodes.INVOKESTATIC;
+import static org.objectweb.asm.Opcodes.PUTSTATIC;
+
+import com.android.tools.r8.cf.code.CfCheckCast;
+import com.android.tools.r8.cf.code.CfConstClass;
+import com.android.tools.r8.cf.code.CfConstDynamic;
+import com.android.tools.r8.cf.code.CfConstNull;
+import com.android.tools.r8.cf.code.CfConstNumber;
+import com.android.tools.r8.cf.code.CfConstString;
+import com.android.tools.r8.cf.code.CfFieldInstruction;
+import com.android.tools.r8.cf.code.CfFrame;
+import com.android.tools.r8.cf.code.CfFrame.FrameType;
+import com.android.tools.r8.cf.code.CfGoto;
+import com.android.tools.r8.cf.code.CfIf;
+import com.android.tools.r8.cf.code.CfInstruction;
+import com.android.tools.r8.cf.code.CfInvoke;
+import com.android.tools.r8.cf.code.CfLabel;
+import com.android.tools.r8.cf.code.CfLoad;
+import com.android.tools.r8.cf.code.CfMonitor;
+import com.android.tools.r8.cf.code.CfReturn;
+import com.android.tools.r8.cf.code.CfStackInstruction;
+import com.android.tools.r8.cf.code.CfStackInstruction.Opcode;
+import com.android.tools.r8.cf.code.CfStore;
+import com.android.tools.r8.cf.code.CfThrow;
+import com.android.tools.r8.cf.code.CfTryCatch;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.CfCode;
+import com.android.tools.r8.graph.DexEncodedField;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexField;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexMethodHandle;
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.FieldAccessFlags;
+import com.android.tools.r8.graph.MethodAccessFlags;
+import com.android.tools.r8.graph.MethodResolutionResult;
+import com.android.tools.r8.graph.MethodResolutionResult.SingleResolutionResult;
+import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.ir.code.If;
+import com.android.tools.r8.ir.code.Monitor;
+import com.android.tools.r8.ir.code.ValueType;
+import com.android.tools.r8.synthesis.SyntheticProgramClassBuilder;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.collections.ImmutableDeque;
+import com.android.tools.r8.utils.collections.ImmutableInt2ReferenceSortedMap;
+import com.google.common.collect.ImmutableList;
+import java.util.List;
+
+public class ConstantDynamicClass {
+  public static final String INITIALIZED_FIELD_NAME = "INITIALIZED";
+  public static final String CONST_FIELD_NAME = "CONST";
+
+  final AppView<?> appView;
+  final ConstantDynamicInstructionDesugaring desugaring;
+  private final DexType accessedFrom;
+  public ConstantDynamicReference reference;
+  public final DexField initializedValueField;
+  public final DexField constantValueField;
+  final DexMethod getConstMethod;
+
+  // Considered final but is set after due to circularity in allocation.
+  private DexProgramClass clazz = null;
+
+  public ConstantDynamicClass(
+      SyntheticProgramClassBuilder builder,
+      AppView<?> appView,
+      ConstantDynamicInstructionDesugaring desugaring,
+      ProgramMethod accessedFrom,
+      CfConstDynamic constantDynamic) {
+    DexItemFactory factory = appView.dexItemFactory();
+    this.appView = appView;
+    this.desugaring = desugaring;
+    this.accessedFrom = accessedFrom.getHolderType();
+    this.reference = constantDynamic.getReference();
+    this.constantValueField =
+        factory.createField(
+            builder.getType(), constantDynamic.getType(), factory.createString(CONST_FIELD_NAME));
+    this.initializedValueField =
+        factory.createField(
+            builder.getType(), factory.booleanType, factory.createString(INITIALIZED_FIELD_NAME));
+    this.getConstMethod =
+        factory.createMethod(
+            builder.getType(),
+            factory.createProto(constantDynamic.getType()),
+            factory.createString("get"));
+
+    synthesizeConstantDynamicClass(builder);
+  }
+
+  /*
+    Generate code following this pattern:
+
+    class CondySyntheticXXX {
+      private static boolean INITIALIZED;
+      private static <constant type> CONST;
+
+     public static get() {
+        if (!INITIALIZED) {
+          synchronized (CondySyntheticXXX.class) {
+            if (!INITIALIZED) {
+              CONST = bsm(null, "constant name", <constant type>);
+              INITIALIZED = true;
+            }
+          }
+        }
+        return value;
+      }
+    }
+
+  */
+  private void synthesizeConstantDynamicClass(SyntheticProgramClassBuilder builder) {
+    synthesizeStaticFields(builder);
+    synthesizeDirectMethods(builder);
+  }
+
+  private void synthesizeStaticFields(SyntheticProgramClassBuilder builder) {
+    builder.setStaticFields(
+        ImmutableList.of(
+            DexEncodedField.builder()
+                .setField(this.initializedValueField)
+                .setAccessFlags(FieldAccessFlags.createPrivateStaticSynthetic())
+                .setApiLevel(AndroidApiLevel.minApiLevelIfEnabledOrUnknown(appView))
+                .setD8R8Synthesized()
+                .build(),
+            DexEncodedField.builder()
+                .setField(this.constantValueField)
+                .setAccessFlags(FieldAccessFlags.createPrivateStaticSynthetic())
+                .setApiLevel(AndroidApiLevel.minApiLevelIfEnabledOrUnknown(appView))
+                .setD8R8Synthesized()
+                .build()));
+  }
+
+  private void synthesizeDirectMethods(SyntheticProgramClassBuilder builder) {
+    builder.setDirectMethods(
+        ImmutableList.of(
+            DexEncodedMethod.builder()
+                .setMethod(getConstMethod)
+                .setAccessFlags(MethodAccessFlags.createPublicStaticSynthetic())
+                .setCode(generateGetterCode(builder))
+                .setD8R8Synthesized()
+                .setApiLevelForDefinition(AndroidApiLevel.S)
+                .setApiLevelForCode(AndroidApiLevel.S)
+                .build()));
+  }
+
+  private void invokeBootstrapMethod(
+      ConstantDynamicReference reference, ImmutableList.Builder<CfInstruction> instructions) {
+    assert reference.getBootstrapMethod().type.isInvokeStatic();
+
+    // TODO(b/178172809): Use MethodHandle.invokeWithArguments if supported.
+    DexMethodHandle bootstrapMethodHandle = reference.getBootstrapMethod();
+    DexMethod bootstrapMethodReference = bootstrapMethodHandle.asMethod();
+    MethodResolutionResult resolution =
+        appView
+            .appInfoForDesugaring()
+            .resolveMethod(bootstrapMethodReference, bootstrapMethodHandle.isInterface);
+    if (resolution.isFailedResolution()) {
+      // TODO(b/178172809): Generate code which throws ICCE.
+    }
+    SingleResolutionResult result = resolution.asSingleResolution();
+    assert result.getResolvedMethod().isStatic();
+    assert result.getResolvedHolder().isProgramClass();
+    instructions.add(new CfConstNull());
+    instructions.add(new CfConstString(reference.getName()));
+    instructions.add(new CfConstClass(reference.getType()));
+    instructions.add(new CfInvoke(INVOKESTATIC, bootstrapMethodReference, false));
+    instructions.add(new CfCheckCast(reference.getType()));
+
+    // Ensure that the bootstrap method is accessible from the generated class.
+    MethodAccessFlags flags = result.getResolvedMethod().getAccessFlags();
+    flags.unsetPrivate();
+    flags.setPublic();
+  }
+
+  private CfCode generateGetterCode(SyntheticProgramClassBuilder builder) {
+    int maxStack = 3;
+    int maxLocals = 2;
+    ImmutableList<CfCode.LocalVariableInfo> localVariables = ImmutableList.of();
+    ImmutableList.Builder<CfInstruction> instructions = ImmutableList.builder();
+
+    CfLabel initializedTrue = new CfLabel();
+    CfLabel initializedTrueSecond = new CfLabel();
+    CfLabel tryCatchStart = new CfLabel();
+    CfLabel tryCatchEnd = new CfLabel();
+    CfLabel tryCatchTarget = new CfLabel();
+    CfLabel tryCatchEndFinally = new CfLabel();
+
+    instructions.add(new CfFieldInstruction(GETSTATIC, initializedValueField));
+    instructions.add(new CfIf(If.Type.NE, ValueType.INT, initializedTrue));
+
+    instructions.add(new CfConstClass(builder.getType()));
+    instructions.add(new CfStackInstruction(Opcode.Dup));
+    instructions.add(new CfStore(ValueType.OBJECT, 0));
+    instructions.add(new CfMonitor(Monitor.Type.ENTER));
+    instructions.add(tryCatchStart);
+
+    instructions.add(new CfFieldInstruction(GETSTATIC, initializedValueField));
+    instructions.add(new CfIf(If.Type.NE, ValueType.INT, initializedTrueSecond));
+
+    invokeBootstrapMethod(reference, instructions);
+    instructions.add(new CfFieldInstruction(PUTSTATIC, constantValueField));
+    instructions.add(new CfConstNumber(1, ValueType.INT));
+    instructions.add(new CfFieldInstruction(PUTSTATIC, initializedValueField));
+
+    instructions.add(initializedTrueSecond);
+    instructions.add(
+        new CfFrame(
+            ImmutableInt2ReferenceSortedMap.of(
+                new int[] {0},
+                new FrameType[] {FrameType.initialized(builder.getFactory().objectType)}),
+            ImmutableDeque.of()));
+    instructions.add(new CfLoad(ValueType.OBJECT, 0));
+    instructions.add(new CfMonitor(Monitor.Type.EXIT));
+    instructions.add(tryCatchEnd);
+    instructions.add(new CfGoto(initializedTrue));
+
+    instructions.add(tryCatchTarget);
+    instructions.add(
+        new CfFrame(
+            ImmutableInt2ReferenceSortedMap.of(
+                new int[] {0},
+                new FrameType[] {FrameType.initialized(builder.getFactory().objectType)}),
+            ImmutableDeque.of(FrameType.initialized(builder.getFactory().throwableType))));
+    instructions.add(new CfStore(ValueType.OBJECT, 1));
+    instructions.add(new CfLoad(ValueType.OBJECT, 0));
+    instructions.add(new CfMonitor(Monitor.Type.EXIT));
+    instructions.add(tryCatchEndFinally);
+    instructions.add(new CfLoad(ValueType.OBJECT, 1));
+    instructions.add(new CfThrow());
+
+    instructions.add(initializedTrue);
+    instructions.add(new CfFrame(ImmutableInt2ReferenceSortedMap.empty(), ImmutableDeque.of()));
+    instructions.add(new CfFieldInstruction(GETSTATIC, constantValueField));
+    instructions.add(new CfReturn(ValueType.OBJECT));
+
+    List<CfTryCatch> tryCatchRanges =
+        ImmutableList.of(
+            new CfTryCatch(
+                tryCatchStart,
+                tryCatchEnd,
+                ImmutableList.of(builder.getFactory().throwableType),
+                ImmutableList.of(tryCatchTarget)),
+            new CfTryCatch(
+                tryCatchTarget,
+                tryCatchEndFinally,
+                ImmutableList.of(builder.getFactory().throwableType),
+                ImmutableList.of(tryCatchTarget)));
+    return new CfCode(
+        builder.getType(),
+        maxStack,
+        maxLocals,
+        instructions.build(),
+        tryCatchRanges,
+        localVariables);
+  }
+
+  public final DexProgramClass getConstantDynamicProgramClass() {
+    assert clazz != null;
+    return clazz;
+  }
+
+  public void setClass(DexProgramClass clazz) {
+    assert this.clazz == null;
+    assert clazz != null;
+    this.clazz = clazz;
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/constantdynamic/ConstantDynamicDesugaringEventConsumer.java b/src/main/java/com/android/tools/r8/ir/desugar/constantdynamic/ConstantDynamicDesugaringEventConsumer.java
new file mode 100644
index 0000000..3c0efd7
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/desugar/constantdynamic/ConstantDynamicDesugaringEventConsumer.java
@@ -0,0 +1,11 @@
+// Copyright (c) 2021, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.ir.desugar.constantdynamic;
+
+import com.android.tools.r8.graph.ProgramMethod;
+
+public interface ConstantDynamicDesugaringEventConsumer {
+
+  void acceptConstantDynamicClass(ConstantDynamicClass lambdaClass, ProgramMethod context);
+}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/constantdynamic/ConstantDynamicInstructionDesugaring.java b/src/main/java/com/android/tools/r8/ir/desugar/constantdynamic/ConstantDynamicInstructionDesugaring.java
new file mode 100644
index 0000000..a01fdd2
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/desugar/constantdynamic/ConstantDynamicInstructionDesugaring.java
@@ -0,0 +1,129 @@
+// Copyright (c) 2021, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.desugar.constantdynamic;
+
+import com.android.tools.r8.cf.code.CfConstDynamic;
+import com.android.tools.r8.cf.code.CfInstruction;
+import com.android.tools.r8.cf.code.CfInvoke;
+import com.android.tools.r8.contexts.CompilationContext.MethodProcessingContext;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.ir.desugar.CfInstructionDesugaring;
+import com.android.tools.r8.ir.desugar.CfInstructionDesugaringEventConsumer;
+import com.android.tools.r8.ir.desugar.FreshLocalProvider;
+import com.android.tools.r8.ir.desugar.LocalStackAllocator;
+import com.android.tools.r8.synthesis.SyntheticNaming.SyntheticKind;
+import com.android.tools.r8.utils.Box;
+import com.google.common.collect.ImmutableList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import org.objectweb.asm.Opcodes;
+
+public class ConstantDynamicInstructionDesugaring implements CfInstructionDesugaring {
+
+  private final AppView<?> appView;
+  private final Map<DexType, Map<ConstantDynamicReference, ConstantDynamicClass>>
+      dynamicConstantSyntheticsPerClass = new ConcurrentHashMap<>();
+
+  public ConstantDynamicInstructionDesugaring(AppView<?> appView) {
+    this.appView = appView;
+  }
+
+  @Override
+  public boolean needsDesugaring(CfInstruction instruction, ProgramMethod context) {
+    return instruction.isConstDynamic();
+  }
+
+  @Override
+  public Collection<CfInstruction> desugarInstruction(
+      CfInstruction instruction,
+      FreshLocalProvider freshLocalProvider,
+      LocalStackAllocator localStackAllocator,
+      CfInstructionDesugaringEventConsumer eventConsumer,
+      ProgramMethod context,
+      MethodProcessingContext methodProcessingContext,
+      DexItemFactory dexItemFactory) {
+    if (instruction.isConstDynamic()) {
+      return desugarConstDynamicInstruction(
+          instruction.asConstDynamic(),
+          freshLocalProvider,
+          localStackAllocator,
+          eventConsumer,
+          context,
+          methodProcessingContext);
+    }
+    return null;
+  }
+
+  private Collection<CfInstruction> desugarConstDynamicInstruction(
+      CfConstDynamic invoke,
+      FreshLocalProvider freshLocalProvider,
+      LocalStackAllocator localStackAllocator,
+      ConstantDynamicDesugaringEventConsumer eventConsumer,
+      ProgramMethod context,
+      MethodProcessingContext methodProcessingContext) {
+    ConstantDynamicClass constantDynamicClass =
+        ensureConstantDynamicClass(invoke, context, methodProcessingContext, eventConsumer);
+    return ImmutableList.of(
+        new CfInvoke(Opcodes.INVOKESTATIC, constantDynamicClass.getConstMethod, false));
+  }
+
+  // Creates a class corresponding to the constant dynamic symbolic reference and context.
+  // TODO(b/178172809): Move this ensure handling to the synthetic items handling and move to
+  //  one class for dynamic constants for each class using dynamic constants.
+  private ConstantDynamicClass ensureConstantDynamicClass(
+      CfConstDynamic constantDynamic,
+      ProgramMethod context,
+      MethodProcessingContext methodProcessingContext,
+      ConstantDynamicDesugaringEventConsumer eventConsumer) {
+    Map<ConstantDynamicReference, ConstantDynamicClass> dynamicConstantSyntheticsForClass =
+        dynamicConstantSyntheticsPerClass.computeIfAbsent(
+            context.getHolderType(), (ignore) -> new HashMap<>());
+    ConstantDynamicClass result =
+        dynamicConstantSyntheticsForClass.get(constantDynamic.getReference());
+    if (result == null) {
+      synchronized (dynamicConstantSyntheticsForClass) {
+        result = dynamicConstantSyntheticsForClass.get(constantDynamic.getReference());
+        if (result == null) {
+          result =
+              createConstantDynamicClass(
+                  constantDynamic, context, methodProcessingContext, eventConsumer);
+          dynamicConstantSyntheticsForClass.put(constantDynamic.getReference(), result);
+        }
+      }
+    }
+    return result;
+  }
+
+  // TODO(b/178172809): Change to use ensureFixedClass.
+  private ConstantDynamicClass createConstantDynamicClass(
+      CfConstDynamic constantDynamic,
+      ProgramMethod context,
+      MethodProcessingContext methodProcessingContext,
+      ConstantDynamicDesugaringEventConsumer eventConsumer) {
+    Box<ConstantDynamicClass> box = new Box<>();
+    DexProgramClass clazz =
+        appView
+            .getSyntheticItems()
+            .createClass(
+                SyntheticKind.CONST_DYNAMIC,
+                methodProcessingContext.createUniqueContext(),
+                appView,
+                builder ->
+                    box.set(
+                        new ConstantDynamicClass(
+                            builder, appView, this, context, constantDynamic)));
+    // Immediately set the actual program class on the constant dynamic.
+    ConstantDynamicClass constantDynamicClass = box.get();
+    constantDynamicClass.setClass(clazz);
+    eventConsumer.acceptConstantDynamicClass(constantDynamicClass, context);
+    return constantDynamicClass;
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/constantdynamic/ConstantDynamicReference.java b/src/main/java/com/android/tools/r8/ir/desugar/constantdynamic/ConstantDynamicReference.java
new file mode 100644
index 0000000..da8257c
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/desugar/constantdynamic/ConstantDynamicReference.java
@@ -0,0 +1,61 @@
+// Copyright (c) 2021, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.ir.desugar.constantdynamic;
+
+import com.android.tools.r8.graph.DexMethodHandle;
+import com.android.tools.r8.graph.DexString;
+import com.android.tools.r8.graph.DexType;
+import java.util.Objects;
+
+public class ConstantDynamicReference {
+  private final DexString name;
+  private final DexType type;
+  private final DexMethodHandle bootstrapMethod;
+  private final Object[] bootstrapMethodArguments;
+
+  public ConstantDynamicReference(
+      DexString name,
+      DexType type,
+      DexMethodHandle bootstrapMethod,
+      Object[] bootstrapMethodArguments) {
+    assert bootstrapMethodArguments.length == 0;
+    this.name = name;
+    this.type = type;
+    this.bootstrapMethod = bootstrapMethod;
+    this.bootstrapMethodArguments = bootstrapMethodArguments;
+  }
+
+  public DexString getName() {
+    return name;
+  }
+
+  public DexType getType() {
+    return type;
+  }
+
+  public DexMethodHandle getBootstrapMethod() {
+    return bootstrapMethod;
+  }
+
+  public Object[] getBootstrapMethodArguments() {
+    return bootstrapMethodArguments;
+  }
+
+  @Override
+  public boolean equals(Object obj) {
+    if (this == obj) return true;
+    if (!(obj instanceof ConstantDynamicReference)) {
+      return false;
+    }
+    ConstantDynamicReference other = (ConstantDynamicReference) obj;
+    return Objects.equals(name, other.name)
+        && Objects.equals(type, other.type)
+        && Objects.equals(bootstrapMethod, other.bootstrapMethod);
+  }
+
+  @Override
+  public int hashCode() {
+    return Objects.hash(name, type, bootstrapMethod);
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/itf/ClassProcessor.java b/src/main/java/com/android/tools/r8/ir/desugar/itf/ClassProcessor.java
index 90a7b35..8661587 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/itf/ClassProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/itf/ClassProcessor.java
@@ -24,16 +24,19 @@
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.GenericSignature;
+import com.android.tools.r8.graph.GenericSignature.ClassTypeSignature;
 import com.android.tools.r8.graph.GenericSignature.MethodTypeSignature;
 import com.android.tools.r8.graph.LibraryMethod;
 import com.android.tools.r8.graph.MethodAccessFlags;
 import com.android.tools.r8.graph.MethodResolutionResult;
 import com.android.tools.r8.graph.ParameterAnnotationsList;
+import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.position.MethodPosition;
 import com.android.tools.r8.utils.BooleanBox;
 import com.android.tools.r8.utils.BooleanUtils;
 import com.android.tools.r8.utils.IterableUtils;
 import com.android.tools.r8.utils.MethodSignatureEquivalence;
+import com.android.tools.r8.utils.OptionalBool;
 import com.android.tools.r8.utils.WorkList;
 import com.android.tools.r8.utils.collections.ProgramMethodSet;
 import com.google.common.base.Equivalence.Wrapper;
@@ -50,6 +53,7 @@
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.function.Consumer;
 import java.util.function.Function;
+import java.util.function.Predicate;
 import org.objectweb.asm.Opcodes;
 
 /**
@@ -344,6 +348,7 @@
   private final InterfaceDesugaringSyntheticHelper helper;
   private final MethodSignatureEquivalence equivalence = MethodSignatureEquivalence.get();
   private final boolean needsLibraryInfo;
+  private final Predicate<ProgramMethod> isLiveMethod;
 
   // Mapping from program and classpath classes to their information summary.
   private final Map<DexClass, ClassInfo> classInfo = new ConcurrentHashMap<>();
@@ -358,7 +363,11 @@
   private final Map<DexProgramClass, ProgramMethodSet> newSyntheticMethods =
       new ConcurrentHashMap<>();
 
-  ClassProcessor(AppView<?> appView) {
+  // Mapping from actual program classes to the extra interfaces needed for emulated dispatch.
+  private final Map<DexProgramClass, List<ClassTypeSignature>> newExtraInterfaceSignatures =
+      new ConcurrentHashMap<>();
+
+  ClassProcessor(AppView<?> appView, Predicate<ProgramMethod> isLiveMethod) {
     this.appView = appView;
     this.dexItemFactory = appView.dexItemFactory();
     this.helper = new InterfaceDesugaringSyntheticHelper(appView);
@@ -369,6 +378,14 @@
                 .desugaredLibraryConfiguration
                 .getRetargetCoreLibMember()
                 .isEmpty();
+    this.isLiveMethod = isLiveMethod;
+  }
+
+  private boolean isLiveMethod(DexClassAndMethod method) {
+    if (method.isProgramMethod()) {
+      return isLiveMethod.test(method.asProgramMethod());
+    }
+    return true;
   }
 
   private boolean needsLibraryInfo() {
@@ -396,6 +413,16 @@
           clazz.addVirtualMethods(newForwardingMethods.toDefinitionSet());
           newForwardingMethods.forEach(eventConsumer::acceptForwardingMethod);
         });
+    newExtraInterfaceSignatures.forEach(
+        (clazz, extraInterfaceSignatures) -> {
+          if (!extraInterfaceSignatures.isEmpty()) {
+            for (ClassTypeSignature signature : extraInterfaceSignatures) {
+              eventConsumer.acceptEmulatedInterfaceMarkerInterface(
+                  clazz, helper.ensureEmulatedInterfaceMarkerInterface(signature.type()));
+            }
+            clazz.addExtraInterfaces(extraInterfaceSignatures);
+          }
+        });
   }
 
   // Computes the set of method signatures that may need forwarding methods on derived classes.
@@ -533,7 +560,7 @@
                 }
               }
             });
-    clazz.asProgramClass().addExtraInterfaces(extraInterfaceSignatures);
+    newExtraInterfaceSignatures.put(clazz.asProgramClass(), extraInterfaceSignatures);
   }
 
   private void collectEmulatedInterfaces(
@@ -637,7 +664,7 @@
           clazz,
           wrapper.get(),
           target -> {
-            if (!superInfo.isTargetedByForwards(target)) {
+            if (isLiveMethod(target) && !superInfo.isTargetedByForwards(target)) {
               additionalForwards.add(target);
               addForwardingMethod(target, clazz);
             }
@@ -821,6 +848,10 @@
     DexEncodedMethod desugaringForwardingMethod =
         DexEncodedMethod.createDesugaringForwardingMethod(
             target, clazz, forwardMethod, dexItemFactory);
+    if (!target.isProgramDefinition()
+        || target.getDefinition().isLibraryMethodOverride().isTrue()) {
+      desugaringForwardingMethod.setLibraryMethodOverride(OptionalBool.TRUE);
+    }
     addSyntheticMethod(clazz.asProgramClass(), desugaringForwardingMethod);
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceDesugaringSyntheticHelper.java b/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceDesugaringSyntheticHelper.java
index 92a64fe..9ee3606 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceDesugaringSyntheticHelper.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceDesugaringSyntheticHelper.java
@@ -17,6 +17,7 @@
 import com.android.tools.r8.graph.DexAnnotationSet;
 import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexClassAndMethod;
+import com.android.tools.r8.graph.DexClasspathClass;
 import com.android.tools.r8.graph.DexEncodedField;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexField;
@@ -32,7 +33,9 @@
 import com.android.tools.r8.graph.InvalidCode;
 import com.android.tools.r8.graph.MethodAccessFlags;
 import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.ir.desugar.CfInstructionDesugaringEventConsumer;
 import com.android.tools.r8.ir.desugar.itf.EmulatedInterfaceSynthesizerEventConsumer.ClasspathEmulatedInterfaceSynthesizerEventConsumer;
+import com.android.tools.r8.synthesis.SyntheticClassBuilder;
 import com.android.tools.r8.synthesis.SyntheticMethodBuilder;
 import com.android.tools.r8.synthesis.SyntheticNaming.SyntheticKind;
 import com.android.tools.r8.utils.InternalOptions;
@@ -41,7 +44,6 @@
 import com.google.common.collect.ImmutableList;
 import java.util.Map;
 import java.util.Set;
-import java.util.function.Consumer;
 import java.util.function.Predicate;
 import org.objectweb.asm.Opcodes;
 
@@ -185,6 +187,17 @@
     return factory.createType(interfaceTypeDescriptor);
   }
 
+  // TODO(b/198273164): This should take the context class and not just a type.
+  DexClasspathClass ensureEmulatedInterfaceMarkerInterface(DexType type) {
+    return appView
+        .getSyntheticItems()
+        .ensureFixedClasspathClassFromType(
+            SyntheticKind.EMULATED_INTERFACE_MARKER_CLASS,
+            type,
+            appView,
+            SyntheticClassBuilder::setInterface);
+  }
+
   DexClassAndMethod ensureEmulatedInterfaceMethod(
       DexClassAndMethod method, ClasspathEmulatedInterfaceSynthesizerEventConsumer eventConsumer) {
     DexMethod emulatedInterfaceMethod = emulateInterfaceLibraryMethod(method);
@@ -232,10 +245,10 @@
   }
 
   DexClassAndMethod ensureStaticAsMethodOfCompanionClassStub(
-      DexClassAndMethod method, Consumer<ProgramMethod> companionClinitConsumer) {
+      DexClassAndMethod method, CfInstructionDesugaringEventConsumer eventConsumer) {
     if (method.isProgramMethod()) {
       return ensureStaticAsMethodOfProgramCompanionClassStub(
-          method.asProgramMethod(), companionClinitConsumer);
+          method.asProgramMethod(), eventConsumer);
     } else {
       ClasspathOrLibraryClass context = method.getHolder().asClasspathOrLibraryClass();
       DexMethod companionMethodReference = staticAsMethodOfCompanionClass(method);
@@ -264,15 +277,7 @@
                       .methodParametersWithFakeThisArguments(appView.dexItemFactory()))
               .setParameterAnnotationsList(
                   virtual.getParameterAnnotations().withFakeThisParameter())
-              // TODO(b/183998768): Once R8 desugars in the enqueuer this should set an invalid
-              //  code to ensure it is never used before desugared and installed.
-              .setCode(
-                  syntheticMethod ->
-                      appView.enableWholeProgramOptimizations()
-                          ? virtual
-                              .getCode()
-                              .getCodeAsInlining(syntheticMethod, method.getReference())
-                          : InvalidCode.getInstance());
+              .setCode(ignored -> InvalidCode.getInstance());
         },
         ignored -> {});
   }
@@ -297,15 +302,7 @@
               .setAnnotations(definition.annotations())
               // TODO(b/183998768): Should this not also be updating with a fake 'this'
               .setParameterAnnotationsList(definition.getParameterAnnotations())
-              // TODO(b/183998768): Once R8 desugars in the enqueuer this should set an invalid
-              //  code to ensure it is never used before desugared and installed.
-              .setCode(
-                  syntheticMethod ->
-                      appView.enableWholeProgramOptimizations()
-                          ? definition
-                              .getCode()
-                              .getCodeAsInlining(syntheticMethod, method.getReference())
-                          : InvalidCode.getInstance());
+              .setCode(ignored -> InvalidCode.getInstance());
         },
         ignored -> {});
   }
@@ -368,9 +365,10 @@
   }
 
   ProgramMethod ensureStaticAsMethodOfProgramCompanionClassStub(
-      ProgramMethod method, Consumer<ProgramMethod> companionClinitConsumer) {
-    if (!method.getDefinition().isClassInitializer() && method.getHolder().hasClassInitializer()) {
-      ensureCompanionClassInitializesInterface(method.getHolder(), companionClinitConsumer);
+      ProgramMethod method, InterfaceMethodDesugaringBaseEventConsumer eventConsumer) {
+    assert !method.getDefinition().isClassInitializer();
+    if (method.getHolder().hasClassInitializer()) {
+      ensureCompanionClassInitializesInterface(method.getHolder(), eventConsumer);
     }
     DexMethod companionMethodReference = staticAsMethodOfCompanionClass(method);
     DexEncodedMethod definition = method.getDefinition();
@@ -387,21 +385,29 @@
               .setGenericSignature(definition.getGenericSignature())
               .setAnnotations(definition.annotations())
               .setParameterAnnotationsList(definition.getParameterAnnotations())
-              // TODO(b/183998768): Once R8 desugars in the enqueuer this should set an invalid
-              //  code to ensure it is never used before desugared and installed.
-              .setCode(
-                  syntheticMethod ->
-                      appView.enableWholeProgramOptimizations()
-                          ? definition
-                              .getCode()
-                              .getCodeAsInlining(syntheticMethod, method.getReference())
-                          : InvalidCode.getInstance());
+              .setCode(ignored -> InvalidCode.getInstance());
         },
-        ignored -> {});
+        companion -> eventConsumer.acceptCompanionMethod(method, companion));
+  }
+
+  public ProgramMethod ensureMethodOfProgramCompanionClassStub(
+      ProgramMethod method, InterfaceMethodDesugaringBaseEventConsumer eventConsumer) {
+    DexEncodedMethod definition = method.getDefinition();
+    assert method.getHolder().isInterface();
+    assert definition.isNonAbstractNonNativeMethod();
+    assert definition.getCode() != null;
+    assert !InvalidCode.isInvalidCode(definition.getCode());
+    if (definition.isStatic()) {
+      return ensureStaticAsMethodOfProgramCompanionClassStub(method, eventConsumer);
+    }
+    if (definition.isPrivate()) {
+      return ensurePrivateAsMethodOfProgramCompanionClassStub(method);
+    }
+    return ensureDefaultAsMethodOfProgramCompanionClassStub(method);
   }
 
   private void ensureCompanionClassInitializesInterface(
-      DexProgramClass iface, Consumer<ProgramMethod> companionClinitConsumer) {
+      DexProgramClass iface, InterfaceMethodDesugaringBaseEventConsumer eventConsumer) {
     assert hasStaticMethodThatTriggersNonTrivialClassInitializer(iface);
     InterfaceProcessor.ensureCompanionMethod(
         iface,
@@ -409,7 +415,7 @@
         appView.dexItemFactory().createProto(appView.dexItemFactory().voidType),
         appView,
         methodBuilder -> createCompanionClassInitializer(iface, methodBuilder),
-        companionClinitConsumer);
+        eventConsumer::acceptCompanionClassClinit);
   }
 
   private DexEncodedField ensureStaticClinitFieldToTriggerInterfaceInitialization(
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceMethodDesugaringBaseEventConsumer.java b/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceMethodDesugaringBaseEventConsumer.java
new file mode 100644
index 0000000..4e1a30f
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceMethodDesugaringBaseEventConsumer.java
@@ -0,0 +1,13 @@
+// Copyright (c) 2021, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.ir.desugar.itf;
+
+import com.android.tools.r8.graph.ProgramMethod;
+
+public interface InterfaceMethodDesugaringBaseEventConsumer {
+
+  void acceptCompanionClassClinit(ProgramMethod method);
+
+  void acceptCompanionMethod(ProgramMethod method, ProgramMethod companionMethod);
+}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceMethodDesugaringEventConsumer.java b/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceMethodDesugaringEventConsumer.java
index bdc8816..9a65214 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceMethodDesugaringEventConsumer.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceMethodDesugaringEventConsumer.java
@@ -6,13 +6,10 @@
 
 import com.android.tools.r8.graph.ProgramMethod;
 
-public interface InterfaceMethodDesugaringEventConsumer {
+public interface InterfaceMethodDesugaringEventConsumer
+    extends InterfaceMethodDesugaringBaseEventConsumer {
 
   void acceptThrowMethod(ProgramMethod method, ProgramMethod context);
 
   void acceptInvokeStaticInterfaceOutliningMethod(ProgramMethod method, ProgramMethod context);
-
-  void acceptCompanionClassClinit(ProgramMethod method);
-
-  // TODO(b/183998768): Add acceptCompanionClass and acceptEmulatedInterface.
 }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceMethodProcessorFacade.java b/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceMethodProcessorFacade.java
index f989858..94a4e60 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceMethodProcessorFacade.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceMethodProcessorFacade.java
@@ -7,18 +7,17 @@
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.ProgramMethod;
-import com.android.tools.r8.ir.conversion.IRConverter;
 import com.android.tools.r8.ir.desugar.CfPostProcessingDesugaring;
 import com.android.tools.r8.ir.desugar.CfPostProcessingDesugaringEventConsumer;
 import com.android.tools.r8.ir.desugar.itf.InterfaceMethodRewriter.Flavor;
 import com.android.tools.r8.utils.ThreadUtils;
-import com.android.tools.r8.utils.collections.SortedProgramMethodSet;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.Iterables;
 import java.util.Collection;
 import java.util.List;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.ExecutorService;
+import java.util.function.Predicate;
 
 public class InterfaceMethodProcessorFacade implements CfPostProcessingDesugaring {
 
@@ -26,18 +25,30 @@
   private final Flavor flavour;
   private final List<InterfaceDesugaringProcessor> interfaceDesugaringProcessors;
 
-  InterfaceMethodProcessorFacade(AppView<?> appView, Flavor flavour) {
+  InterfaceMethodProcessorFacade(
+      AppView<?> appView, Flavor flavour, Predicate<ProgramMethod> isLiveMethod) {
     this.appView = appView;
     this.flavour = flavour;
-    interfaceDesugaringProcessors = instantiateInterfaceDesugaringProcessors(appView);
+    interfaceDesugaringProcessors = instantiateInterfaceDesugaringProcessors(appView, isLiveMethod);
+  }
+
+  InterfaceMethodProcessorFacade(
+      AppView<?> appView,
+      Flavor flavour,
+      Predicate<ProgramMethod> isLiveMethod,
+      InterfaceProcessor interfaceProcessor) {
+    this.appView = appView;
+    this.flavour = flavour;
+    interfaceDesugaringProcessors =
+        ImmutableList.of(new ClassProcessor(appView, isLiveMethod), interfaceProcessor);
   }
 
   private List<InterfaceDesugaringProcessor> instantiateInterfaceDesugaringProcessors(
-      AppView<?> appView) {
+      AppView<?> appView, Predicate<ProgramMethod> isLiveMethod) {
 
     // Process all classes first. Add missing forwarding methods to
     // replace desugared default interface methods.
-    ClassProcessor classProcessor = new ClassProcessor(appView);
+    ClassProcessor classProcessor = new ClassProcessor(appView, isLiveMethod);
 
     // Process interfaces, create companion or dispatch class if needed, move static
     // methods to companion class, copy default interface methods to companion classes,
@@ -49,39 +60,6 @@
     return ImmutableList.of(classProcessor, interfaceProcessor);
   }
 
-  /** Runs the interfaceProcessor, the class processor and the emulated interface processor. */
-  void runInterfaceDesugaringProcessorsForR8(IRConverter converter, ExecutorService executorService)
-      throws ExecutionException {
-
-    CollectingInterfaceDesugaringEventConsumer eventConsumer =
-        new CollectingInterfaceDesugaringEventConsumer();
-    processClassesConcurrently(appView.appInfo().classes(), eventConsumer, executorService);
-    converter.processMethodsConcurrently(
-        eventConsumer.getSortedSynthesizedMethods(), executorService);
-  }
-
-  // This temporary class avoids the duality between collecting with IR processing and
-  // having events with the Cf desugaring.
-  private static class CollectingInterfaceDesugaringEventConsumer
-      implements InterfaceProcessingDesugaringEventConsumer {
-
-    SortedProgramMethodSet sortedSynthesizedMethods = SortedProgramMethodSet.createConcurrent();
-
-    @Override
-    public void acceptForwardingMethod(ProgramMethod method) {
-      sortedSynthesizedMethods.add(method);
-    }
-
-    @Override
-    public void acceptCompanionClassClinit(ProgramMethod method) {
-      sortedSynthesizedMethods.add(method);
-    }
-
-    public SortedProgramMethodSet getSortedSynthesizedMethods() {
-      return sortedSynthesizedMethods;
-    }
-  }
-
   private boolean shouldProcess(DexProgramClass clazz, Flavor flavour) {
     if (appView.isAlreadyLibraryDesugared(clazz)) {
       return false;
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 8b3f70e..77f2c98 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
@@ -38,10 +38,8 @@
 import com.android.tools.r8.graph.MethodAccessFlags;
 import com.android.tools.r8.graph.MethodResolutionResult.SingleResolutionResult;
 import com.android.tools.r8.graph.ProgramMethod;
-import com.android.tools.r8.ir.analysis.type.TypeAnalysis;
 import com.android.tools.r8.ir.code.BasicBlock;
 import com.android.tools.r8.ir.code.IRCode;
-import com.android.tools.r8.ir.code.Instruction;
 import com.android.tools.r8.ir.code.InstructionListIterator;
 import com.android.tools.r8.ir.code.Invoke.Type;
 import com.android.tools.r8.ir.code.InvokeMethod;
@@ -70,7 +68,6 @@
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.IteratorUtils;
 import com.android.tools.r8.utils.collections.ProgramMethodSet;
-import com.android.tools.r8.utils.collections.SortedProgramMethodSet;
 import com.android.tools.r8.utils.structural.Ordered;
 import com.google.common.collect.Sets;
 import java.util.ArrayList;
@@ -80,11 +77,10 @@
 import java.util.Map;
 import java.util.Set;
 import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.ExecutionException;
-import java.util.concurrent.ExecutorService;
 import java.util.function.BiConsumer;
 import java.util.function.Consumer;
 import java.util.function.Function;
+import java.util.function.Predicate;
 
 //
 // Default and static interface method desugaring rewriter (note that lambda
@@ -267,7 +263,9 @@
       AppInfoWithClassHierarchy appInfoForDesugaring = appView.appInfoForDesugaring();
       SingleResolutionResult resolution =
           appInfoForDesugaring.resolveMethod(method, invokeType == INTERFACE).asSingleResolution();
-      if (resolution != null && resolution.getResolvedMethod().isPrivateMethod()) {
+      if (resolution != null
+          && (resolution.getResolvedMethod().isPrivate()
+              || resolution.getResolvedMethod().isStatic())) {
         return true;
       }
       return defaultMethodForEmulatedDispatchOrNull(method, invokeType == INTERFACE) != null;
@@ -442,10 +440,15 @@
               .resolveMethod(invoke.getMethod(), invoke.isInterface())
               .asSingleResolution();
       if (resolution != null
-          && resolution.getResolvedMethod().isPrivateMethod()
+          && resolution.getResolvedMethod().isPrivate()
           && resolution.isAccessibleFrom(context, appInfoForDesugaring).isTrue()) {
-        return rewriteInvokeDirect(invoke.getMethod(), context, rewriteInvoke);
+        // TODO(b/198267586): What about the private in-accessible case?
+        return rewriteInvokeDirect(invoke.getMethod(), context, rewriteInvoke, eventConsumer);
       }
+      if (resolution != null && resolution.getResolvedMethod().isStatic()) {
+        return rewriteToThrow.apply(resolution);
+      }
+      // TODO(b/198267586): What about an invoke <init>?
       return rewriteInvokeInterfaceOrInvokeVirtual(
           invoke.getMethod(), invoke.isInterface(), rewriteInvoke, eventConsumer);
     }
@@ -465,13 +468,14 @@
           staticOutliningMethodConsumer,
           rewriteInvoke,
           rewriteToThrow,
-          eventConsumer::acceptCompanionClassClinit);
+          eventConsumer);
     }
     assert invoke.isInvokeSpecial();
     if (invoke.isInvokeSuper(context.getHolderType())) {
-      return rewriteInvokeSuper(invoke.getMethod(), context, rewriteInvoke, rewriteToThrow);
+      return rewriteInvokeSuper(
+          invoke.getMethod(), context, rewriteInvoke, rewriteToThrow, eventConsumer);
     }
-    return rewriteInvokeDirect(invoke.getMethod(), context, rewriteInvoke);
+    return rewriteInvokeDirect(invoke.getMethod(), context, rewriteInvoke, eventConsumer);
   }
 
   private Collection<CfInstruction> rewriteInvokeToThrowCf(
@@ -568,113 +572,6 @@
     }
   }
 
-  // Rewrites the references to static and default interface methods.
-  // NOTE: can be called for different methods concurrently.
-  public void rewriteMethodReferences(
-      IRCode code,
-      MethodProcessor methodProcessor,
-      MethodProcessingContext methodProcessingContext) {
-    ProgramMethod context = code.context();
-    if (isSyntheticMethodThatShouldNotBeDoubleProcessed(code.context())) {
-      // As the synthetics for dispatching to static interface methods are not desugared again
-      // this can leave a static invoke to a static method on an interface.
-      leavingStaticInvokeToInterface(context.asProgramMethod());
-      return;
-    }
-
-    Set<Value> affectedValues = Sets.newIdentityHashSet();
-    Set<BasicBlock> blocksToRemove = Sets.newIdentityHashSet();
-    ListIterator<BasicBlock> blocks = code.listIterator();
-    while (blocks.hasNext()) {
-      BasicBlock block = blocks.next();
-      if (blocksToRemove.contains(block)) {
-        continue;
-      }
-      InstructionListIterator instructions = block.listIterator(code);
-      while (instructions.hasNext()) {
-        Instruction instruction = instructions.next();
-        rewriteMethodReferences(
-            code,
-            methodProcessor,
-            methodProcessingContext,
-            context,
-            affectedValues,
-            blocksToRemove,
-            blocks,
-            instructions,
-            instruction);
-      }
-    }
-
-    code.removeBlocks(blocksToRemove);
-
-    if (!affectedValues.isEmpty()) {
-      new TypeAnalysis(appView).narrowing(affectedValues);
-    }
-
-    assert code.isConsistentSSA();
-  }
-
-  private void rewriteMethodReferences(
-      IRCode code,
-      MethodProcessor methodProcessor,
-      MethodProcessingContext methodProcessingContext,
-      ProgramMethod context,
-      Set<Value> affectedValues,
-      Set<BasicBlock> blocksToRemove,
-      ListIterator<BasicBlock> blocks,
-      InstructionListIterator instructions,
-      Instruction instruction) {
-    if (instruction.isInvokeCustom()) {
-      reportInterfaceMethodHandleCallSite(instruction.asInvokeCustom().getCallSite(), context);
-      return;
-    }
-    if (!instruction.isInvokeMethod()) {
-      return;
-    }
-    InvokeMethod invoke = instruction.asInvokeMethod();
-    Function<DexMethod, Collection<CfInstruction>> rewriteInvoke =
-        (newTarget) -> {
-          instructions.replaceCurrentInstruction(
-              new InvokeStatic(newTarget, invoke.outValue(), invoke.arguments()));
-          return null;
-        };
-    if (instruction.isInvokeDirect()) {
-      rewriteInvokeDirect(invoke.getInvokedMethod(), context, rewriteInvoke);
-    } else if (instruction.isInvokeVirtual() || instruction.isInvokeInterface()) {
-      rewriteInvokeInterfaceOrInvokeVirtual(
-          invoke.getInvokedMethod(), invoke.getInterfaceBit(), rewriteInvoke, null);
-    } else {
-      Function<SingleResolutionResult, Collection<CfInstruction>> rewriteToThrow =
-          (resolutionResult) ->
-              rewriteInvokeToThrowIR(
-                  invoke,
-                  resolutionResult,
-                  code,
-                  blocks,
-                  instructions,
-                  affectedValues,
-                  blocksToRemove,
-                  methodProcessor,
-                  methodProcessingContext);
-      if (instruction.isInvokeStatic()) {
-        // TODO(b/192439456): Make a test to prove resolution is needed here and fix it.
-        rewriteInvokeStatic(
-            invoke.getInvokedMethod(),
-            invoke.getInterfaceBit(),
-            methodProcessingContext,
-            context,
-            synthesizedMethods::add,
-            rewriteInvoke,
-            rewriteToThrow,
-            synthesizedMethods::add);
-      } else {
-        assert instruction.isInvokeSuper();
-        rewriteInvokeSuper(invoke.getInvokedMethod(), context, rewriteInvoke, rewriteToThrow);
-      }
-    }
-  }
-
   private boolean isSyntheticMethodThatShouldNotBeDoubleProcessed(ProgramMethod method) {
     return appView.getSyntheticItems().isSyntheticMethodThatShouldNotBeDoubleProcessed(method);
   }
@@ -693,7 +590,8 @@
   private Collection<CfInstruction> rewriteInvokeDirect(
       DexMethod invokedMethod,
       ProgramMethod context,
-      Function<DexMethod, Collection<CfInstruction>> rewriteInvoke) {
+      Function<DexMethod, Collection<CfInstruction>> rewriteInvoke,
+      InterfaceMethodDesugaringEventConsumer eventConsumer) {
     if (factory.isConstructor(invokedMethod)) {
       return null;
     }
@@ -723,19 +621,24 @@
       // method is expected to be in the current class since it is private, but desugaring
       // may move some methods or their code into other classes.
       assert invokeNeedsRewriting(invokedMethod, DIRECT);
+      DexClassAndMethod companionMethodDefinition = null;
       DexMethod companionMethod;
       if (directTarget.getDefinition().isPrivateMethod()) {
-        companionMethod =
-            directTarget.isProgramMethod()
-                ? helper
-                    .ensurePrivateAsMethodOfProgramCompanionClassStub(
-                        directTarget.asProgramMethod())
-                    .getReference()
-                // TODO(b/183998768): Why does this not create a stub on the class path?
-                : helper.privateAsMethodOfCompanionClass(directTarget);
+        if (directTarget.isProgramMethod()) {
+          companionMethodDefinition =
+              helper.ensurePrivateAsMethodOfProgramCompanionClassStub(
+                  directTarget.asProgramMethod());
+          companionMethod = companionMethodDefinition.getReference();
+        } else {
+          // TODO(b/183998768): Why does this not create a stub on the class path?
+          companionMethod = helper.privateAsMethodOfCompanionClass(directTarget);
+        }
       } else {
-        companionMethod =
-            helper.ensureDefaultAsMethodOfCompanionClassStub(directTarget).getReference();
+        companionMethodDefinition = helper.ensureDefaultAsMethodOfCompanionClassStub(directTarget);
+        companionMethod = companionMethodDefinition.getReference();
+      }
+      if (companionMethodDefinition != null) {
+        acceptCompanionMethod(directTarget, companionMethodDefinition, eventConsumer);
       }
       return rewriteInvoke.apply(companionMethod);
     } else {
@@ -745,8 +648,10 @@
       if (virtualTarget != null) {
         // This is a invoke-direct call to a virtual method.
         assert invokeNeedsRewriting(invokedMethod, DIRECT);
-        return rewriteInvoke.apply(
-            helper.ensureDefaultAsMethodOfCompanionClassStub(virtualTarget).getReference());
+        DexClassAndMethod companionMethod =
+            helper.ensureDefaultAsMethodOfCompanionClassStub(virtualTarget);
+        acceptCompanionMethod(virtualTarget, companionMethod, eventConsumer);
+        return rewriteInvoke.apply(companionMethod.getReference());
       } else {
         // The below assert is here because a well-type program should have a target, but we
         // cannot throw a compilation error, since we have no knowledge about the input.
@@ -756,6 +661,16 @@
     return null;
   }
 
+  private void acceptCompanionMethod(
+      DexClassAndMethod method,
+      DexClassAndMethod companion,
+      InterfaceMethodDesugaringEventConsumer eventConsumer) {
+    assert method.isProgramMethod() == companion.isProgramMethod();
+    if (method.isProgramMethod()) {
+      eventConsumer.acceptCompanionMethod(method.asProgramMethod(), companion.asProgramMethod());
+    }
+  }
+
   private Collection<CfInstruction> rewriteInvokeStatic(
       DexMethod invokedMethod,
       boolean interfaceBit,
@@ -764,7 +679,7 @@
       Consumer<ProgramMethod> staticOutliningMethodConsumer,
       Function<DexMethod, Collection<CfInstruction>> rewriteInvoke,
       Function<SingleResolutionResult, Collection<CfInstruction>> rewriteToThrow,
-      Consumer<ProgramMethod> companionClinitConsumer) {
+      CfInstructionDesugaringEventConsumer eventConsumer) {
     if (appView.getSyntheticItems().isPendingSynthetic(invokedMethod.holder)) {
       // We did not create this code yet, but it will not require rewriting.
       return null;
@@ -858,9 +773,9 @@
     assert resolutionResult != null;
     assert resolutionResult.getResolvedMethod().isStatic();
     assert invokeNeedsRewriting(invokedMethod, STATIC);
+    DexClassAndMethod method = resolutionResult.getResolutionPair();
     DexClassAndMethod companionMethod =
-        helper.ensureStaticAsMethodOfCompanionClassStub(
-            resolutionResult.getResolutionPair(), companionClinitConsumer);
+        helper.ensureStaticAsMethodOfCompanionClassStub(method, eventConsumer);
     return rewriteInvoke.apply(companionMethod.getReference());
   }
 
@@ -868,7 +783,8 @@
       DexMethod invokedMethod,
       ProgramMethod context,
       Function<DexMethod, Collection<CfInstruction>> rewriteInvoke,
-      Function<SingleResolutionResult, Collection<CfInstruction>> rewriteToThrow) {
+      Function<SingleResolutionResult, Collection<CfInstruction>> rewriteToThrow,
+      InterfaceMethodDesugaringEventConsumer eventConsumer) {
     DexClass clazz = appView.definitionFor(invokedMethod.holder, context);
     if (clazz == null) {
       // NOTE: leave unchanged those calls to undefined targets. This may lead to runtime
@@ -901,10 +817,27 @@
           // TODO(b/145775365): This should throw IAE.
           return rewriteToThrow.apply(null);
         }
-        return rewriteInvoke.apply(
-            helper.privateAsMethodOfCompanionClass(resolutionResult.getResolutionPair()));
+        DexClassAndMethod method = resolutionResult.getResolutionPair();
+        DexMethod companionMethod;
+        if (method.isProgramMethod()) {
+          ProgramMethod companionMethodDefinition =
+              helper.ensurePrivateAsMethodOfProgramCompanionClassStub(method.asProgramMethod());
+          companionMethod = companionMethodDefinition.getReference();
+          eventConsumer.acceptCompanionMethod(method.asProgramMethod(), companionMethodDefinition);
+        } else {
+          companionMethod = helper.privateAsMethodOfCompanionClass(method);
+        }
+        return rewriteInvoke.apply(companionMethod);
       } else {
+        DexClassAndMethod method = resolutionResult.getResolutionPair();
+        // TODO(b/183998768): Why do this amend routine. We have done resolution, so would that
+        //  not be the correct target!? I think this is just legacy from before resolution was
+        //  implemented in full.
         DexMethod amendedMethod = amendDefaultMethod(context.getHolder(), invokedMethod);
+        assert method.getReference() == amendedMethod;
+        DexClassAndMethod companionMethod =
+            helper.ensureDefaultAsMethodOfCompanionClassStub(method);
+        acceptCompanionMethod(method, companionMethod, eventConsumer);
         return rewriteInvoke.apply(
             InterfaceDesugaringSyntheticHelper.defaultAsMethodOfCompanionClass(
                 amendedMethod, appView.dexItemFactory()));
@@ -920,8 +853,10 @@
           DexClass holder = target.getHolder();
           if (holder.isLibraryClass() && holder.isInterface()) {
             assert invokeNeedsRewriting(invokedMethod, SUPER);
-            return rewriteInvoke.apply(
-                helper.ensureDefaultAsMethodOfCompanionClassStub(target).getReference());
+            DexClassAndMethod companionTarget =
+                helper.ensureDefaultAsMethodOfCompanionClassStub(target);
+            acceptCompanionMethod(target, companionTarget, eventConsumer);
+            return rewriteInvoke.apply(companionTarget.getReference());
           }
         }
       }
@@ -1121,26 +1056,15 @@
     return singleCandidate != null ? singleCandidate : method;
   }
 
-  public void finalizeInterfaceMethodRewritingThroughIR(
-      IRConverter converter, ExecutorService executorService) throws ExecutionException {
-    SortedProgramMethodSet sortedSynthesizedMethods = SortedProgramMethodSet.create();
-    sortedSynthesizedMethods.addAll(synthesizedMethods);
-    converter.processMethodsConcurrently(sortedSynthesizedMethods, executorService);
-
-    // Cached data is not needed any more.
-    this.cache.clear();
-    this.synthesizedMethods.clear();
+  public InterfaceMethodProcessorFacade getPostProcessingDesugaringD8(Flavor flavour) {
+    return new InterfaceMethodProcessorFacade(appView, flavour, m -> true);
   }
 
-  public void runInterfaceDesugaringProcessorsForR8(
-      IRConverter converter, Flavor flavour, ExecutorService executorService)
-      throws ExecutionException {
-    getPostProcessingDesugaring(flavour)
-        .runInterfaceDesugaringProcessorsForR8(converter, executorService);
-  }
-
-  public InterfaceMethodProcessorFacade getPostProcessingDesugaring(Flavor flavour) {
-    return new InterfaceMethodProcessorFacade(appView, flavour);
+  public InterfaceMethodProcessorFacade getPostProcessingDesugaringR8(
+      Flavor flavour,
+      Predicate<ProgramMethod> isLiveMethod,
+      InterfaceProcessor interfaceProcessor) {
+    return new InterfaceMethodProcessorFacade(appView, flavour, isLiveMethod, interfaceProcessor);
   }
 
   private Origin getMethodOrigin(DexMethod method) {
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceProcessingDesugaringEventConsumer.java b/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceProcessingDesugaringEventConsumer.java
index 8fc50d1..e16f254 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceProcessingDesugaringEventConsumer.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceProcessingDesugaringEventConsumer.java
@@ -4,12 +4,18 @@
 
 package com.android.tools.r8.ir.desugar.itf;
 
+import com.android.tools.r8.graph.DexClasspathClass;
+import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.ProgramMethod;
 
-public interface InterfaceProcessingDesugaringEventConsumer {
+// TODO(b/183998768): Consider forcing the processing of interface methods in D8 akin to R8.
+//  That would avoid the need to reiterate the interface methods to collect info and this
+//  could avoid the "base" methods.
+public interface InterfaceProcessingDesugaringEventConsumer
+    extends InterfaceMethodDesugaringBaseEventConsumer {
 
   void acceptForwardingMethod(ProgramMethod method);
 
-  // TODO(b/183998768): Remove this once interface desugaring is moved to the R8 enqueuer.
-  void acceptCompanionClassClinit(ProgramMethod method);
+  void acceptEmulatedInterfaceMarkerInterface(
+      DexProgramClass clazz, DexClasspathClass newInterface);
 }
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 ac12741..dd4a7a9 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
@@ -9,7 +9,6 @@
 import com.android.tools.r8.code.Instruction;
 import com.android.tools.r8.code.InvokeSuper;
 import com.android.tools.r8.errors.CompilationError;
-import com.android.tools.r8.errors.Unimplemented;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.Code;
 import com.android.tools.r8.graph.DexClass;
@@ -33,7 +32,6 @@
 import com.android.tools.r8.utils.collections.BidirectionalManyToOneRepresentativeMap;
 import com.android.tools.r8.utils.collections.BidirectionalOneToOneHashMap;
 import com.android.tools.r8.utils.collections.BidirectionalOneToOneMap;
-import com.android.tools.r8.utils.collections.EmptyBidirectionalOneToOneMap;
 import com.android.tools.r8.utils.collections.MutableBidirectionalOneToOneMap;
 import java.util.ArrayDeque;
 import java.util.ArrayList;
@@ -44,6 +42,7 @@
 import java.util.Map;
 import java.util.Set;
 import java.util.concurrent.ConcurrentHashMap;
+import java.util.function.BiConsumer;
 import java.util.function.Consumer;
 
 // Default and static method interface desugaring processor for interfaces.
@@ -59,14 +58,22 @@
   private final Map<DexProgramClass, PostProcessingInterfaceInfo> postProcessingInterfaceInfos =
       new ConcurrentHashMap<>();
 
-  InterfaceProcessor(AppView<?> appView) {
+  public InterfaceProcessor(AppView<?> appView) {
     this.appView = appView;
     helper = new InterfaceDesugaringSyntheticHelper(appView);
   }
 
+  public InterfaceDesugaringSyntheticHelper getHelper() {
+    return helper;
+  }
+
   @Override
   public void process(
       DexProgramClass iface, InterfaceProcessingDesugaringEventConsumer eventConsumer) {
+    if (appView.enableWholeProgramOptimizations()) {
+      // R8 populates all info as part of enqueuing.
+      return;
+    }
     if (!iface.isInterface()) {
       return;
     }
@@ -75,9 +82,9 @@
   }
 
   private void analyzeBridges(DexProgramClass iface) {
+    assert !appView.enableWholeProgramOptimizations();
     for (ProgramMethod method : iface.virtualProgramMethods()) {
-      DexEncodedMethod virtual = method.getDefinition();
-      if (!interfaceMethodRemovalChangesApi(virtual, iface)) {
+      if (!interfaceMethodRemovalChangesApi(method, iface)) {
         getPostProcessingInterfaceInfo(iface).setHasBridgesToRemove();
         return;
       }
@@ -86,9 +93,6 @@
 
   private void ensureCompanionClassMethods(
       DexProgramClass iface, InterfaceProcessingDesugaringEventConsumer eventConsumer) {
-    // TODO(b/183998768): Once fixed, the methods should be added for processing.
-    // D8 and R8 don't need to optimize the methods since they are just moved from interfaces and
-    // don't need to be re-processed.
     processVirtualInterfaceMethods(iface);
     processDirectInterfaceMethods(iface, eventConsumer);
   }
@@ -121,96 +125,76 @@
   }
 
   private void processVirtualInterfaceMethods(DexProgramClass iface) {
+    assert !appView.enableWholeProgramOptimizations();
     for (ProgramMethod method : iface.virtualProgramMethods()) {
       DexEncodedMethod virtual = method.getDefinition();
       if (helper.isCompatibleDefaultMethod(virtual)) {
-        if (!canMoveToCompanionClass(virtual)) {
-          throw new CompilationError(
-              "One or more instruction is preventing default interface "
-                  + "method from being desugared: "
-                  + method.toSourceString(),
-              iface.origin);
-        }
-        Code code = virtual.getCode();
-        if (code == null) {
-          throw new CompilationError(
-              "Code is missing for default " + "interface method: " + method.toSourceString(),
-              iface.origin);
-        }
         // Create a new method in a companion class to represent default method implementation.
         ProgramMethod companion = helper.ensureDefaultAsMethodOfProgramCompanionClassStub(method);
-        DexEncodedMethod.setDebugInfoWithFakeThisParameter(
-            code, companion.getReference().getArity(), appView);
         finalizeMoveToCompanionMethod(method, companion);
-        getPostProcessingInterfaceInfo(iface)
-            .mapDefaultMethodToCompanionMethod(virtual, companion.getDefinition());
       }
     }
   }
 
   private void processDirectInterfaceMethods(
       DexProgramClass iface, InterfaceProcessingDesugaringEventConsumer eventConsumer) {
+    assert !appView.enableWholeProgramOptimizations();
     for (ProgramMethod method : iface.directProgramMethods()) {
       DexEncodedMethod definition = method.getDefinition();
-      if (definition.isClassInitializer()) {
-        continue;
+      if (!definition.isClassInitializer()) {
+        getPostProcessingInterfaceInfo(iface).setHasNonClinitDirectMethods();
+        ProgramMethod companion =
+            helper.ensureMethodOfProgramCompanionClassStub(method, eventConsumer);
+        finalizeMoveToCompanionMethod(method, companion);
       }
-      if (definition.isInstanceInitializer()) {
-        assert false
-            : "Unexpected interface instance initializer: "
-                + method.getReference().toSourceString();
-        continue;
-      }
-
-      getPostProcessingInterfaceInfo(iface).setHasNonClinitDirectMethods();
-
-      ProgramMethod companion;
-      if (isStaticMethod(definition)) {
-        assert definition.isPrivate() || definition.isPublic()
-            : "Static interface method "
-                + method.toSourceString()
-                + " is expected to "
-                + "either be public or private in "
-                + iface.origin;
-        companion =
-            helper.ensureStaticAsMethodOfProgramCompanionClassStub(
-                method, eventConsumer::acceptCompanionClassClinit);
-      } else {
-        assert definition.isPrivate();
-        Code code = definition.getCode();
-        if (code == null) {
-          throw new CompilationError(
-              "Code is missing for private instance "
-                  + "interface method: "
-                  + method.getReference().toSourceString(),
-              iface.origin);
-        }
-        companion = helper.ensurePrivateAsMethodOfProgramCompanionClassStub(method);
-        DexEncodedMethod.setDebugInfoWithFakeThisParameter(
-            code, companion.getReference().getArity(), appView);
-      }
-
-      finalizeMoveToCompanionMethod(method, companion);
-      getPostProcessingInterfaceInfo(iface)
-          .moveMethod(method.getReference(), companion.getReference());
     }
   }
 
-  private void finalizeMoveToCompanionMethod(ProgramMethod method, ProgramMethod companion) {
-    // TODO(b/183998768): R8 should also install an "invalid code" object until the actual code
-    //  moves.
-    assert appView.enableWholeProgramOptimizations()
-        || InvalidCode.isInvalidCode(companion.getDefinition().getCode());
+  public void finalizeMoveToCompanionMethod(ProgramMethod method, ProgramMethod companion) {
+    assert InvalidCode.isInvalidCode(companion.getDefinition().getCode());
+    if (method.getDefinition().getCode() == null) {
+      throw new CompilationError(
+          "Code is missing for private instance "
+              + "interface method: "
+              + method.getReference().toSourceString(),
+          method.getOrigin());
+    }
+    if (!canMoveToCompanionClass(method)) {
+      throw new CompilationError(
+          "One or more instruction is preventing default interface "
+              + "method from being desugared: "
+              + method.toSourceString(),
+          method.getOrigin());
+    }
     DexProgramClass iface = method.getHolder();
     DexEncodedMethod definition = method.getDefinition();
+    assert !definition.isInitializer();
+    assert !definition.isStatic() || definition.isPrivate() || definition.isPublic()
+        : "Static interface method "
+            + method.toSourceString()
+            + " is expected to "
+            + "either be public or private in "
+            + method.getOrigin();
+
+    if (definition.isStatic() || definition.isPrivate()) {
+      getPostProcessingInterfaceInfo(iface).setHasNonClinitDirectMethods();
+      getPostProcessingInterfaceInfo(iface)
+          .moveMethod(method.getReference(), companion.getReference());
+    } else {
+      assert helper.isCompatibleDefaultMethod(definition);
+      getPostProcessingInterfaceInfo(iface)
+          .mapDefaultMethodToCompanionMethod(method.getDefinition(), companion.getDefinition());
+    }
     if (definition.hasClassFileVersion()) {
       companion.getDefinition().downgradeClassFileVersion(definition.getClassFileVersion());
     }
-    companion
-        .getDefinition()
-        .setCode(
-            definition.getCode().getCodeAsInlining(companion.getReference(), method.getReference()),
-            appView);
+    Code code =
+        definition.getCode().getCodeAsInlining(companion.getReference(), method.getReference());
+    if (!definition.isStatic()) {
+      DexEncodedMethod.setDebugInfoWithFakeThisParameter(
+          code, companion.getReference().getArity(), appView);
+    }
+    companion.getDefinition().setCode(code, appView);
     definition.setCode(InvalidCode.getInstance(), appView);
   }
 
@@ -224,8 +208,8 @@
     }
   }
 
-  private boolean canMoveToCompanionClass(DexEncodedMethod method) {
-    Code code = method.getCode();
+  private static boolean canMoveToCompanionClass(ProgramMethod method) {
+    Code code = method.getDefinition().getCode();
     assert code != null;
     if (code.isDexCode()) {
       for (Instruction insn : code.asDexCode().instructions) {
@@ -254,13 +238,9 @@
   // implementation to the companion class of [iface]. This is always the case for non-bridge
   // methods. Bridge methods that does not override an implementation in a super-interface must
   // also be kept (such a situation can happen if the vertical class merger merges two interfaces).
-  private boolean interfaceMethodRemovalChangesApi(DexEncodedMethod method, DexClass iface) {
-    if (appView.enableWholeProgramOptimizations()) {
-      if (appView.appInfo().withLiveness().isPinned(method.getReference())) {
-        return true;
-      }
-    }
-    if (method.accessFlags.isBridge()) {
+  private boolean interfaceMethodRemovalChangesApi(ProgramMethod method, DexClass iface) {
+    assert !appView.enableWholeProgramOptimizations();
+    if (method.getAccessFlags().isBridge()) {
       if (appView.options().cfToCfDesugar) {
         // TODO(b/187176895): Find the compilation causing this to not be removed.
         return false;
@@ -292,27 +272,22 @@
     }
   }
 
-  private boolean isStaticMethod(DexEncodedMethod method) {
-    if (method.accessFlags.isNative()) {
-      throw new Unimplemented("Native interface methods are not yet supported.");
-    }
-    return method.accessFlags.isStatic()
-        && !appView.dexItemFactory().isClassConstructor(method.getReference());
-  }
-
   private InterfaceProcessorNestedGraphLens postProcessInterfaces() {
     InterfaceProcessorNestedGraphLens.Builder graphLensBuilder =
         InterfaceProcessorNestedGraphLens.builder();
     postProcessingInterfaceInfos.forEach(
         (iface, info) -> {
-          if (info.hasNonClinitDirectMethods()) {
+          if (info.hasNonClinitDirectMethods() || appView.enableWholeProgramOptimizations()) {
             clearDirectMethods(iface);
           }
           if (info.hasDefaultMethodsToImplementationMap()) {
             info.getDefaultMethodsToImplementation()
                 .forEach(
                     (defaultMethod, companionMethod) -> {
-                      defaultMethod.setDefaultInterfaceMethodImplementation(companionMethod);
+                      assert InvalidCode.isInvalidCode(defaultMethod.getCode());
+                      assert !InvalidCode.isInvalidCode(companionMethod.getCode());
+                      defaultMethod.accessFlags.setAbstract();
+                      defaultMethod.removeCode();
                       graphLensBuilder.recordCodeMovedToCompanionClass(
                           defaultMethod.getReference(), companionMethod.getReference());
                     });
@@ -321,6 +296,8 @@
             info.getMethodsToMove().forEach(graphLensBuilder::move);
           }
           if (info.hasBridgesToRemove()) {
+            // D8 can remove bridges at this point.
+            assert !appView.enableWholeProgramOptimizations();
             removeBridges(iface);
           }
         });
@@ -328,12 +305,12 @@
   }
 
   private void removeBridges(DexProgramClass iface) {
+    assert !appView.enableWholeProgramOptimizations();
     List<DexEncodedMethod> newVirtualMethods = new ArrayList<>();
     for (ProgramMethod method : iface.virtualProgramMethods()) {
-      DexEncodedMethod virtual = method.getDefinition();
       // Remove bridge methods.
-      if (interfaceMethodRemovalChangesApi(virtual, iface)) {
-        newVirtualMethods.add(virtual);
+      if (interfaceMethodRemovalChangesApi(method, iface)) {
+        newVirtualMethods.add(method.getDefinition());
       }
     }
 
@@ -350,14 +327,11 @@
 
   @Override
   public void finalizeProcessing(InterfaceProcessingDesugaringEventConsumer eventConsumer) {
+    // TODO(b/196337368): Simplify this fix-up to be specific for the move of companion methods
+    //  rather than be based on a graph lens.
     InterfaceProcessorNestedGraphLens graphLens = postProcessInterfaces();
     if (graphLens != null) {
-      if (appView.enableWholeProgramOptimizations()) {
-        appView.setGraphLens(graphLens);
-      }
       new InterfaceMethodRewriterFixup(appView, graphLens).run();
-
-      graphLens.moveToPending();
     }
   }
 
@@ -366,6 +340,15 @@
         iface, ignored -> new PostProcessingInterfaceInfo());
   }
 
+  public void forEachMethodToMove(BiConsumer<DexMethod, DexMethod> fn) {
+    postProcessingInterfaceInfos.forEach(
+        (iface, info) -> {
+          if (info.methodsToMove != null) {
+            info.methodsToMove.forEach(fn);
+          }
+        });
+  }
+
   static class PostProcessingInterfaceInfo {
     private Map<DexEncodedMethod, DexEncodedMethod> defaultMethodsToImplementation;
     private Map<DexMethod, DexMethod> methodsToMove;
@@ -422,14 +405,11 @@
 
   // Specific lens which remaps invocation types to static since all rewrites performed here
   // are to static companion methods.
-  // TODO(b/167345026): Remove the use of this lens.
+  // TODO(b/196337368): Replace this by a desugaring lens shared for D8 and R8.
   public static class InterfaceProcessorNestedGraphLens extends NestedGraphLens {
 
-    private BidirectionalManyToManyRepresentativeMap<DexMethod, DexMethod> extraNewMethodSignatures;
-    private BidirectionalManyToManyRepresentativeMap<DexMethod, DexMethod>
-        pendingNewMethodSignatures;
-    private BidirectionalManyToManyRepresentativeMap<DexMethod, DexMethod>
-        pendingExtraNewMethodSignatures;
+    private final BidirectionalManyToManyRepresentativeMap<DexMethod, DexMethod>
+        extraNewMethodSignatures;
 
     public InterfaceProcessorNestedGraphLens(
         AppView<?> appView,
@@ -441,55 +421,12 @@
       this.extraNewMethodSignatures = extraNewMethodSignatures;
     }
 
-    public void moveToPending() {
-      // These are "pending" and installed in the "toggled" state only.
-      pendingNewMethodSignatures = newMethodSignatures;
-      pendingExtraNewMethodSignatures = extraNewMethodSignatures;
-      // The interface methods do not contribute to renaming lens info anymore, so they are cleared.
-      newMethodSignatures = new EmptyBidirectionalOneToOneMap<>();
-      this.extraNewMethodSignatures = new EmptyBidirectionalOneToOneMap<>();
-    }
-
-    public static InterfaceProcessorNestedGraphLens find(GraphLens lens) {
-      if (lens.isInterfaceProcessorLens()) {
-        return lens.asInterfaceProcessorLens();
-      }
-      if (lens.isIdentityLens()) {
-        return null;
-      }
-      if (lens.isNonIdentityLens()) {
-        return find(lens.asNonIdentityLens().getPrevious());
-      }
-      assert false;
-      return null;
-    }
-
-    public void enableMapping() {
-      this.newMethodSignatures = pendingExtraNewMethodSignatures;
-      this.extraNewMethodSignatures = pendingNewMethodSignatures;
-    }
-
-    public void disableMapping() {
-      this.newMethodSignatures = new EmptyBidirectionalOneToOneMap<>();
-      this.extraNewMethodSignatures = new EmptyBidirectionalOneToOneMap<>();
-    }
-
     public BidirectionalManyToManyRepresentativeMap<DexMethod, DexMethod>
         getExtraNewMethodSignatures() {
       return extraNewMethodSignatures;
     }
 
     @Override
-    public boolean isInterfaceProcessorLens() {
-      return true;
-    }
-
-    @Override
-    public InterfaceProcessorNestedGraphLens asInterfaceProcessorLens() {
-      return this;
-    }
-
-    @Override
     public boolean isLegitimateToHaveEmptyMappings() {
       return true;
     }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/records/RecordRewriter.java b/src/main/java/com/android/tools/r8/ir/desugar/records/RecordRewriter.java
index 39a6ae1..7c26d30 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/records/RecordRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/records/RecordRewriter.java
@@ -413,7 +413,6 @@
 
   @Override
   public boolean needsDesugaring(CfInstruction instruction, ProgramMethod context) {
-    assert !instruction.isInitClass();
     if (instruction.isInvokeDynamic()) {
       return needsDesugaring(instruction.asInvokeDynamic(), context);
     }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/CallSiteOptimizationInfoPropagator.java b/src/main/java/com/android/tools/r8/ir/optimize/CallSiteOptimizationInfoPropagator.java
index e58024e..ce0646d 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/CallSiteOptimizationInfoPropagator.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/CallSiteOptimizationInfoPropagator.java
@@ -399,9 +399,7 @@
           },
           method -> {
             targetsToRevisit.add(method);
-            if (appView.options().testing.callSiteOptimizationInfoInspector != null) {
-              appView.options().testing.callSiteOptimizationInfoInspector.accept(method);
-            }
+            appView.options().testing.callSiteOptimizationInfoInspector.accept(method);
           });
     }
     if (revisitedMethods != null) {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/Outliner.java b/src/main/java/com/android/tools/r8/ir/optimize/Outliner.java
index c07ad6d..dba8278 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/Outliner.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/Outliner.java
@@ -54,7 +54,6 @@
 import com.android.tools.r8.ir.code.ValueTypeConstraint;
 import com.android.tools.r8.ir.conversion.IRBuilder;
 import com.android.tools.r8.ir.conversion.SourceCode;
-import com.android.tools.r8.ir.desugar.itf.InterfaceProcessor.InterfaceProcessorNestedGraphLens;
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
 import com.android.tools.r8.naming.ClassNameMapper;
 import com.android.tools.r8.origin.Origin;
@@ -1315,29 +1314,12 @@
   public ProgramMethodSet selectMethodsForOutlining() {
     ProgramMethodSet methodsSelectedForOutlining = ProgramMethodSet.create();
     assert outlineSites.isEmpty();
-
-    // TODO(b/167345026): This is needed to ensure that default interface methods are mapped to
-    //  the corresponding companion methods that contain the code objects. This should be removed
-    //  once default interface methods are desugared prior to the first optimization pass.
-    InterfaceProcessorNestedGraphLens interfaceProcessorLens =
-        InterfaceProcessorNestedGraphLens.find(appView.graphLens());
-    if (interfaceProcessorLens != null) {
-      interfaceProcessorLens.enableMapping();
-    }
-
     for (LongLivedProgramMethodMultisetBuilder outlineMethods : candidateMethodLists) {
       if (outlineMethods.size() >= appView.options().outline.threshold) {
         ProgramMethodMultiset multiset = outlineMethods.build(appView);
         multiset.forEachEntry((method, ignore) -> methodsSelectedForOutlining.add(method));
       }
     }
-
-    // TODO(b/167345026): Remove once default interface methods are desugared prior to the first
-    //  optimization pass.
-    if (interfaceProcessorLens != null) {
-      interfaceProcessorLens.disableMapping();
-    }
-
     candidateMethodLists.clear();
     return methodsSelectedForOutlining;
   }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/ConcreteCallSiteOptimizationInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/info/ConcreteCallSiteOptimizationInfo.java
index ae702f9..d0c82cb 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/ConcreteCallSiteOptimizationInfo.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/ConcreteCallSiteOptimizationInfo.java
@@ -116,7 +116,7 @@
       if (dynamicUpperBoundType == null) {
         continue;
       }
-      assert appView.options().callSiteOptimizationOptions().isTypePropagationEnabled();
+      assert appView.options().callSiteOptimizationOptions().isDynamicTypePropagationEnabled();
       // To avoid the full join of type lattices below, separately check if the nullability of
       // arguments is improved, and if so, we can eagerly conclude that we've collected useful
       // call site information for this method.
@@ -255,8 +255,6 @@
             newCallSiteInfo.dynamicUpperBoundTypes.put(
                 argumentIndex, dynamicType.getDynamicUpperBoundType());
             isTop = false;
-          } else {
-            newCallSiteInfo.dynamicUpperBoundTypes.put(argumentIndex, staticTypeElement);
           }
         }
       }
diff --git a/src/main/java/com/android/tools/r8/naming/RecordRewritingNamingLens.java b/src/main/java/com/android/tools/r8/naming/RecordRewritingNamingLens.java
index e809317..ebada3d 100644
--- a/src/main/java/com/android/tools/r8/naming/RecordRewritingNamingLens.java
+++ b/src/main/java/com/android/tools/r8/naming/RecordRewritingNamingLens.java
@@ -83,6 +83,16 @@
   }
 
   @Override
+  public boolean hasPrefixRewritingLogic() {
+    return namingLens.hasPrefixRewritingLogic();
+  }
+
+  @Override
+  public DexString prefixRewrittenType(DexType type) {
+    return namingLens.prefixRewrittenType(type);
+  }
+
+  @Override
   public String lookupPackageName(String packageName) {
     return namingLens.lookupPackageName(packageName);
   }
diff --git a/src/main/java/com/android/tools/r8/optimize/VisibilityBridgeRemover.java b/src/main/java/com/android/tools/r8/optimize/VisibilityBridgeRemover.java
index f073314..a990959 100644
--- a/src/main/java/com/android/tools/r8/optimize/VisibilityBridgeRemover.java
+++ b/src/main/java/com/android/tools/r8/optimize/VisibilityBridgeRemover.java
@@ -3,18 +3,22 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.optimize;
 
+import static com.android.tools.r8.utils.ThreadUtils.processItems;
+
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.graph.PrunedItems;
 import com.android.tools.r8.logging.Log;
 import com.android.tools.r8.optimize.InvokeSingleTargetExtractor.InvokeKind;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
-import com.android.tools.r8.utils.ForEachable;
-import com.android.tools.r8.utils.IntBox;
 import com.google.common.collect.Sets;
 import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
 
 public class VisibilityBridgeRemover {
 
@@ -24,50 +28,14 @@
     this.appView = appView;
   }
 
-  private void removeUnneededVisibilityBridgesFromClass(DexProgramClass clazz) {
-    DexEncodedMethod[] newDirectMethods =
-        removeUnneededVisibilityBridges(
-            clazz::forEachProgramDirectMethod, clazz.getMethodCollection().numberOfDirectMethods());
-    if (newDirectMethods != null) {
-      clazz.setDirectMethods(newDirectMethods);
-    }
-    DexEncodedMethod[] newVirtualMethods =
-        removeUnneededVisibilityBridges(
-            clazz::forEachProgramVirtualMethod,
-            clazz.getMethodCollection().numberOfVirtualMethods());
-    if (newVirtualMethods != null) {
-      clazz.setVirtualMethods(newVirtualMethods);
-    }
-  }
-
-  private DexEncodedMethod[] removeUnneededVisibilityBridges(
-      ForEachable<ProgramMethod> methods, int size) {
-    Set<DexEncodedMethod> methodsToBeRemoved = Sets.newIdentityHashSet();
-    methods.forEach(
-        method -> {
-          if (isUnneededVisibilityBridge(method)) {
-            methodsToBeRemoved.add(method.getDefinition());
-          }
-        });
-    if (!methodsToBeRemoved.isEmpty()) {
-      DexEncodedMethod[] newMethods = new DexEncodedMethod[size - methodsToBeRemoved.size()];
-      IntBox i = new IntBox(0);
-      methods.forEach(
-          method -> {
-            if (!methodsToBeRemoved.contains(method.getDefinition())) {
-              newMethods[i.getAndIncrement()] = method.getDefinition();
-            }
-          });
-      return newMethods;
-    }
-    return null;
-  }
-
   private boolean isUnneededVisibilityBridge(ProgramMethod method) {
+    // Clean-up the predicate check.
     if (appView.appInfo().isPinned(method.getReference())) {
       return false;
     }
     DexEncodedMethod definition = method.getDefinition();
+    // TODO(b/198133259): Extend to definitions that are not defined as bridges.
+    // TODO(b/197490164): Remove if method is abstract.
     if (!definition.isBridge() || definition.isAbstract()) {
       return false;
     }
@@ -75,33 +43,67 @@
         new InvokeSingleTargetExtractor(appView.dexItemFactory());
     method.registerCodeReferences(targetExtractor);
     DexMethod target = targetExtractor.getTarget();
-    InvokeKind kind = targetExtractor.getKind();
     // javac-generated visibility forward bridge method has same descriptor (name, signature and
     // return type).
-    if (target != null && target.hasSameProtoAndName(method.getReference())) {
-      assert !definition.isPrivate() && !definition.isInstanceInitializer();
-      if (kind == InvokeKind.SUPER) {
-        // This is a visibility forward, so check for the direct target.
-        DexEncodedMethod targetMethod =
-            appView.appInfo().unsafeResolveMethodDueToDexFormat(target).getSingleTarget();
-        if (targetMethod != null && targetMethod.accessFlags.isPublic()) {
-          if (Log.ENABLED) {
-            Log.info(
-                getClass(),
-                "Removing visibility forwarding %s -> %s",
-                method,
-                targetMethod.getReference());
-          }
-          return true;
-        }
-      }
+    if (target == null || !target.match(method.getReference())) {
+      return false;
     }
+    assert !definition.isPrivate() && !definition.isInstanceInitializer();
+    if (!isTargetingSuperMethod(method, targetExtractor.getKind(), target)) {
+      return false;
+    }
+    // This is a visibility forward, so check for the direct target.
+    DexEncodedMethod targetMethod =
+        appView.appInfo().unsafeResolveMethodDueToDexFormat(target).getSingleTarget();
+    if (targetMethod == null || !targetMethod.accessFlags.isPublic()) {
+      return false;
+    }
+    if (Log.ENABLED) {
+      Log.info(
+          getClass(),
+          "Removing visibility forwarding %s -> %s",
+          method,
+          targetMethod.getReference());
+    }
+    return true;
+  }
+
+  private boolean isTargetingSuperMethod(ProgramMethod method, InvokeKind kind, DexMethod target) {
+    if (kind == InvokeKind.SUPER) {
+      return true;
+    }
+    if (kind == InvokeKind.STATIC) {
+      return appView.appInfo().isStrictSubtypeOf(method.getHolderType(), target.holder);
+    }
+    assert false : "Unexpected invoke-kind for visibility bridge";
     return false;
   }
 
-  public void run() {
-    for (DexProgramClass clazz : appView.appInfo().classes()) {
-      removeUnneededVisibilityBridgesFromClass(clazz);
-    }
+  public void run(ExecutorService executorService) throws ExecutionException {
+    // Collect all visibility bridges to remove.
+    ConcurrentHashMap<DexProgramClass, Set<DexEncodedMethod>> visibilityBridgesToRemove =
+        new ConcurrentHashMap<>();
+    processItems(
+        appView.appInfo().classes(),
+        clazz -> {
+          Set<DexEncodedMethod> bridgesToRemoveForClass = Sets.newIdentityHashSet();
+          clazz.forEachProgramMethod(
+              method -> {
+                if (isUnneededVisibilityBridge(method)) {
+                  bridgesToRemoveForClass.add(method.getDefinition());
+                }
+              });
+          if (!bridgesToRemoveForClass.isEmpty()) {
+            visibilityBridgesToRemove.put(clazz, bridgesToRemoveForClass);
+          }
+        },
+        executorService);
+    // Remove all bridges found.
+    PrunedItems.Builder builder = PrunedItems.builder();
+    visibilityBridgesToRemove.forEach(
+        (clazz, methods) -> {
+          clazz.getMethodCollection().removeMethods(methods);
+          methods.forEach(method -> builder.addRemovedMethod(method.getReference()));
+        });
   }
 }
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagator.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagator.java
index 2173883..2761296 100644
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagator.java
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagator.java
@@ -19,9 +19,11 @@
 import com.android.tools.r8.optimize.argumentpropagation.codescanner.MethodState;
 import com.android.tools.r8.optimize.argumentpropagation.codescanner.MethodStateCollectionByReference;
 import com.android.tools.r8.optimize.argumentpropagation.codescanner.VirtualRootMethodsAnalysis;
+import com.android.tools.r8.optimize.argumentpropagation.reprocessingcriteria.ArgumentPropagatorReprocessingCriteriaCollection;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.utils.ThreadUtils;
 import com.android.tools.r8.utils.Timing;
+import java.util.Collection;
 import java.util.List;
 import java.util.Set;
 import java.util.concurrent.ExecutionException;
@@ -40,6 +42,12 @@
    */
   private ArgumentPropagatorCodeScanner codeScanner;
 
+  /**
+   * Analyzes the uses of arguments in methods to determine when reprocessing of methods will likely
+   * not lead to any additional code optimizations.
+   */
+  private ArgumentPropagatorReprocessingCriteriaCollection reprocessingCriteriaCollection;
+
   public ArgumentPropagator(AppView<AppInfoWithLiveness> appView) {
     assert appView.enableWholeProgramOptimizations();
     assert appView.options().isOptimizing();
@@ -63,6 +71,7 @@
     timing.begin("Initialize code scanner");
 
     codeScanner = new ArgumentPropagatorCodeScanner(appView);
+    reprocessingCriteriaCollection = new ArgumentPropagatorReprocessingCriteriaCollection(appView);
 
     ImmediateProgramSubtypingInfo immediateSubtypingInfo =
         ImmediateProgramSubtypingInfo.create(appView);
@@ -80,7 +89,7 @@
           // Compute the mapping from virtual methods to their root virtual method and the set of
           // monomorphic virtual methods.
           new VirtualRootMethodsAnalysis(appView, immediateSubtypingInfo)
-              .extendVirtualRootMethods(appView.appInfo().classes(), codeScanner);
+              .extendVirtualRootMethods(classes, codeScanner);
         },
         executorService);
 
@@ -92,12 +101,14 @@
   public void scan(
       ProgramMethod method, IRCode code, MethodProcessor methodProcessor, Timing timing) {
     if (codeScanner != null) {
-      // TODO(b/190154391): Do we process synthetic methods using a OneTimeMethodProcessor
-      //  during the primary optimization pass?
       assert methodProcessor.isPrimaryMethodProcessor();
       codeScanner.scan(method, code, timing);
+
+      assert reprocessingCriteriaCollection != null;
+      reprocessingCriteriaCollection.analyzeArgumentUses(method, code);
     } else {
       assert !methodProcessor.isPrimaryMethodProcessor();
+      assert reprocessingCriteriaCollection == null;
     }
   }
 
@@ -117,8 +128,14 @@
       throws ExecutionException {
     assert !appView.getSyntheticItems().hasPendingSyntheticClasses();
     timing.begin("Argument propagator");
-    populateParameterOptimizationInfo(executorService, timing);
-    optimizeMethodParameters();
+    ImmediateProgramSubtypingInfo immediateSubtypingInfo =
+        ImmediateProgramSubtypingInfo.create(appView);
+    List<Set<DexProgramClass>> stronglyConnectedProgramComponents =
+        computeStronglyConnectedProgramClasses(appView, immediateSubtypingInfo);
+    populateParameterOptimizationInfo(
+        immediateSubtypingInfo, stronglyConnectedProgramComponents, executorService, timing);
+    optimizeMethodParameters(
+        immediateSubtypingInfo, stronglyConnectedProgramComponents, executorService);
     enqueueMethodsForProcessing(postMethodProcessorBuilder);
     timing.end();
   }
@@ -127,7 +144,11 @@
    * Called by {@link IRConverter} *after* the primary optimization pass to populate the parameter
    * optimization info.
    */
-  private void populateParameterOptimizationInfo(ExecutorService executorService, Timing timing)
+  private void populateParameterOptimizationInfo(
+      ImmediateProgramSubtypingInfo immediateSubtypingInfo,
+      List<Set<DexProgramClass>> stronglyConnectedProgramComponents,
+      ExecutorService executorService,
+      Timing timing)
       throws ExecutionException {
     // Unset the scanner since all code objects have been scanned at this point.
     assert appView.isAllCodeProcessed();
@@ -136,22 +157,40 @@
     codeScanner = null;
 
     timing.begin("Compute optimization info");
-    new ArgumentPropagatorOptimizationInfoPopulator(appView, codeScannerResult)
+    new ArgumentPropagatorOptimizationInfoPopulator(
+            appView,
+            immediateSubtypingInfo,
+            codeScannerResult,
+            reprocessingCriteriaCollection,
+            stronglyConnectedProgramComponents)
         .populateOptimizationInfo(executorService, timing);
+    reprocessingCriteriaCollection = null;
     timing.end();
   }
 
   /** Called by {@link IRConverter} to optimize method definitions. */
-  private void optimizeMethodParameters() {
-    // TODO(b/190154391): Remove parameters with constant values.
-    // TODO(b/190154391): Remove unused parameters by simulating they are constant.
-    // TODO(b/190154391): Strengthen the static type of parameters.
-    // TODO(b/190154391): If we learn that a method returns a constant, then consider changing its
-    //  return type to void.
-    // TODO(b/69963623): If we optimize a method to be unconditionally throwing (because it has a
-    //  bottom parameter), then for each caller that becomes unconditionally throwing, we could
-    //  also enqueue the caller's callers for reprocessing. This would propagate the throwing
-    //  information to all call sites.
+  private void optimizeMethodParameters(
+      ImmediateProgramSubtypingInfo immediateSubtypingInfo,
+      List<Set<DexProgramClass>> stronglyConnectedProgramComponents,
+      ExecutorService executorService)
+      throws ExecutionException {
+    Collection<ArgumentPropagatorGraphLens.Builder> partialGraphLensBuilders =
+        ThreadUtils.processItemsWithResults(
+            stronglyConnectedProgramComponents,
+            classes ->
+                new ArgumentPropagatorProgramOptimizer(appView, immediateSubtypingInfo)
+                    .optimize(classes),
+            executorService);
+
+    // Merge all the partial, disjoint graph lens builders into a single graph lens.
+    ArgumentPropagatorGraphLens.Builder graphLensBuilder =
+        ArgumentPropagatorGraphLens.builder(appView);
+    partialGraphLensBuilders.forEach(graphLensBuilder::mergeDisjoint);
+
+    ArgumentPropagatorGraphLens graphLens = graphLensBuilder.build();
+    if (graphLens != null) {
+      appView.setGraphLens(graphLens);
+    }
   }
 
   /**
@@ -168,6 +207,7 @@
             if (callSiteOptimizationInfo.isConcreteCallSiteOptimizationInfo()
                 && !appView.appInfo().isNeverReprocessMethod(method.getReference())) {
               postMethodProcessorBuilder.add(method);
+              appView.testing().callSiteOptimizationInfoInspector.accept(method);
             }
           });
     }
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorGraphLens.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorGraphLens.java
new file mode 100644
index 0000000..0110197
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorGraphLens.java
@@ -0,0 +1,108 @@
+// 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.optimize.argumentpropagation;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexField;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.GraphLens;
+import com.android.tools.r8.graph.GraphLens.NonIdentityGraphLens;
+import com.android.tools.r8.graph.RewrittenPrototypeDescription;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+
+public class ArgumentPropagatorGraphLens extends NonIdentityGraphLens {
+
+  ArgumentPropagatorGraphLens(AppView<AppInfoWithLiveness> appView) {
+    super(appView);
+  }
+
+  public static Builder builder(AppView<AppInfoWithLiveness> appView) {
+    return new Builder(appView);
+  }
+
+  @Override
+  public DexType getOriginalType(DexType type) {
+    return getPrevious().getOriginalType(type);
+  }
+
+  @Override
+  public Iterable<DexType> getOriginalTypes(DexType type) {
+    return getPrevious().getOriginalTypes(type);
+  }
+
+  @Override
+  public DexField getOriginalFieldSignature(DexField field) {
+    return getPrevious().getOriginalFieldSignature(field);
+  }
+
+  @Override
+  public DexField getRenamedFieldSignature(DexField originalField) {
+    return getPrevious().getRenamedFieldSignature(originalField);
+  }
+
+  @Override
+  public DexMethod getOriginalMethodSignature(DexMethod method) {
+    return getPrevious().getOriginalMethodSignature(method);
+  }
+
+  @Override
+  public DexMethod getRenamedMethodSignature(DexMethod originalMethod, GraphLens applied) {
+    return applied != this
+        ? getPrevious().getRenamedMethodSignature(originalMethod, applied)
+        : originalMethod;
+  }
+
+  @Override
+  protected DexType internalDescribeLookupClassType(DexType previous) {
+    return previous;
+  }
+
+  @Override
+  protected FieldLookupResult internalDescribeLookupField(FieldLookupResult previous) {
+    return previous;
+  }
+
+  @Override
+  protected MethodLookupResult internalDescribeLookupMethod(
+      MethodLookupResult previous, DexMethod context) {
+    return previous;
+  }
+
+  @Override
+  protected DexMethod internalGetPreviousMethodSignature(DexMethod method) {
+    return method;
+  }
+
+  @Override
+  public boolean isContextFreeForMethods() {
+    return getPrevious().isContextFreeForMethods();
+  }
+
+  @Override
+  public RewrittenPrototypeDescription lookupPrototypeChangesForMethodDefinition(DexMethod method) {
+    return getPrevious().lookupPrototypeChangesForMethodDefinition(method);
+  }
+
+  public static class Builder {
+
+    private final AppView<AppInfoWithLiveness> appView;
+
+    Builder(AppView<AppInfoWithLiveness> appView) {
+      this.appView = appView;
+    }
+
+    public ArgumentPropagatorGraphLens.Builder mergeDisjoint(
+        ArgumentPropagatorGraphLens.Builder partialGraphLensBuilder) {
+      // TODO(b/190154391): Implement.
+      return this;
+    }
+
+    public ArgumentPropagatorGraphLens build() {
+      // TODO(b/190154391): Implement.
+      return null;
+    }
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorOptimizationInfoPopulator.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorOptimizationInfoPopulator.java
index 6d4f3b7..53876ab 100644
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorOptimizationInfoPopulator.java
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorOptimizationInfoPopulator.java
@@ -4,7 +4,6 @@
 
 package com.android.tools.r8.optimize.argumentpropagation;
 
-import static com.android.tools.r8.optimize.argumentpropagation.utils.StronglyConnectedProgramClasses.computeStronglyConnectedProgramClasses;
 
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexEncodedMethod;
@@ -23,6 +22,8 @@
 import com.android.tools.r8.optimize.argumentpropagation.propagation.InParameterFlowPropagator;
 import com.android.tools.r8.optimize.argumentpropagation.propagation.InterfaceMethodArgumentPropagator;
 import com.android.tools.r8.optimize.argumentpropagation.propagation.VirtualDispatchMethodArgumentPropagator;
+import com.android.tools.r8.optimize.argumentpropagation.reprocessingcriteria.ArgumentPropagatorReprocessingCriteriaCollection;
+import com.android.tools.r8.optimize.argumentpropagation.reprocessingcriteria.MethodReprocessingCriteria;
 import com.android.tools.r8.optimize.argumentpropagation.utils.WideningUtils;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.utils.ThreadUtils;
@@ -42,20 +43,22 @@
 
   private final AppView<AppInfoWithLiveness> appView;
   private final MethodStateCollectionByReference methodStates;
+  private final ArgumentPropagatorReprocessingCriteriaCollection reprocessingCriteriaCollection;
 
   private final ImmediateProgramSubtypingInfo immediateSubtypingInfo;
-  private final List<Set<DexProgramClass>> stronglyConnectedComponents;
+  private final List<Set<DexProgramClass>> stronglyConnectedProgramComponents;
 
   ArgumentPropagatorOptimizationInfoPopulator(
-      AppView<AppInfoWithLiveness> appView, MethodStateCollectionByReference methodStates) {
+      AppView<AppInfoWithLiveness> appView,
+      ImmediateProgramSubtypingInfo immediateSubtypingInfo,
+      MethodStateCollectionByReference methodStates,
+      ArgumentPropagatorReprocessingCriteriaCollection reprocessingCriteriaCollection,
+      List<Set<DexProgramClass>> stronglyConnectedProgramComponents) {
     this.appView = appView;
-    this.methodStates = methodStates;
-
-    ImmediateProgramSubtypingInfo immediateSubtypingInfo =
-        ImmediateProgramSubtypingInfo.create(appView);
     this.immediateSubtypingInfo = immediateSubtypingInfo;
-    this.stronglyConnectedComponents =
-        computeStronglyConnectedProgramClasses(appView, immediateSubtypingInfo);
+    this.methodStates = methodStates;
+    this.reprocessingCriteriaCollection = reprocessingCriteriaCollection;
+    this.stronglyConnectedProgramComponents = stronglyConnectedProgramComponents;
   }
 
   /**
@@ -74,7 +77,9 @@
     //  that the method returns the constant.
     timing.begin("Propagate argument information for virtual methods");
     ThreadUtils.processItems(
-        stronglyConnectedComponents, this::processStronglyConnectedComponent, executorService);
+        stronglyConnectedProgramComponents,
+        this::processStronglyConnectedComponent,
+        executorService);
     timing.end();
 
     // Solve the parameter flow constraints.
@@ -190,6 +195,14 @@
               return true;
             });
 
+    // If we have any reprocessing criteria for the given method, check that they are satisfied
+    // before reenqueing.
+    MethodReprocessingCriteria reprocessingCriteria =
+        reprocessingCriteriaCollection.getReprocessingCriteria(method);
+    if (!reprocessingCriteria.shouldReprocess(appView, method, monomorphicMethodState)) {
+      return;
+    }
+
     method
         .getDefinition()
         .joinCallSiteOptimizationInfo(
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorProgramOptimizer.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorProgramOptimizer.java
new file mode 100644
index 0000000..859830b
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorProgramOptimizer.java
@@ -0,0 +1,37 @@
+// 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.optimize.argumentpropagation;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.ImmediateProgramSubtypingInfo;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import java.util.Set;
+
+public class ArgumentPropagatorProgramOptimizer {
+
+  private final AppView<AppInfoWithLiveness> appView;
+  private final ImmediateProgramSubtypingInfo immediateSubtypingInfo;
+
+  public ArgumentPropagatorProgramOptimizer(
+      AppView<AppInfoWithLiveness> appView, ImmediateProgramSubtypingInfo immediateSubtypingInfo) {
+    this.appView = appView;
+    this.immediateSubtypingInfo = immediateSubtypingInfo;
+  }
+
+  // TODO(b/190154391): Remove parameters with constant values.
+  // TODO(b/190154391): Remove unused parameters by simulating they are constant.
+  // TODO(b/190154391): Strengthen the static type of parameters.
+  // TODO(b/190154391): If we learn that a method returns a constant, then consider changing its
+  //  return type to void.
+  // TODO(b/69963623): If we optimize a method to be unconditionally throwing (because it has a
+  //  bottom parameter), then for each caller that becomes unconditionally throwing, we could
+  //  also enqueue the caller's callers for reprocessing. This would propagate the throwing
+  //  information to all call sites.
+  public ArgumentPropagatorGraphLens.Builder optimize(
+      Set<DexProgramClass> stronglyConnectedProgramClasses) {
+    return ArgumentPropagatorGraphLens.builder(appView);
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/ConcreteClassTypeParameterState.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/ConcreteClassTypeParameterState.java
index 6dc6c5c..3244db4 100644
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/ConcreteClassTypeParameterState.java
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/ConcreteClassTypeParameterState.java
@@ -51,6 +51,10 @@
     return this;
   }
 
+  public AbstractValue getAbstractValue() {
+    return abstractValue;
+  }
+
   @Override
   public AbstractValue getAbstractValue(AppView<AppInfoWithLiveness> appView) {
     return abstractValue;
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/reprocessingcriteria/AlwaysFalseParameterReprocessingCriteria.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/reprocessingcriteria/AlwaysFalseParameterReprocessingCriteria.java
new file mode 100644
index 0000000..90d7e4a
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/reprocessingcriteria/AlwaysFalseParameterReprocessingCriteria.java
@@ -0,0 +1,56 @@
+// Copyright (c) 2021, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.optimize.argumentpropagation.reprocessingcriteria;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.optimize.argumentpropagation.codescanner.ConcreteParameterState;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+
+/**
+ * Represents that a parameter should never be reprocessed even if we have non-trivial information
+ * about it (e.g., abstract value, dynamic type, nullability).
+ *
+ * <p>Example: This is used for unused parameters.
+ */
+public class AlwaysFalseParameterReprocessingCriteria extends ParameterReprocessingCriteria {
+
+  public static final AlwaysFalseParameterReprocessingCriteria INSTANCE =
+      new AlwaysFalseParameterReprocessingCriteria();
+
+  private AlwaysFalseParameterReprocessingCriteria() {}
+
+  public static AlwaysFalseParameterReprocessingCriteria get() {
+    return INSTANCE;
+  }
+
+  @Override
+  public boolean isNeverReprocess() {
+    return true;
+  }
+
+  @Override
+  public boolean shouldReprocess(
+      AppView<AppInfoWithLiveness> appView,
+      ConcreteParameterState parameterState,
+      DexType parameterType) {
+    return false;
+  }
+
+  @Override
+  public boolean shouldReprocessDueToAbstractValue() {
+    return false;
+  }
+
+  @Override
+  public boolean shouldReprocessDueToDynamicType() {
+    return false;
+  }
+
+  @Override
+  public boolean shouldReprocessDueToNullability() {
+    return false;
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/reprocessingcriteria/AlwaysTrueParameterReprocessingCriteria.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/reprocessingcriteria/AlwaysTrueParameterReprocessingCriteria.java
new file mode 100644
index 0000000..72d382f
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/reprocessingcriteria/AlwaysTrueParameterReprocessingCriteria.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.optimize.argumentpropagation.reprocessingcriteria;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.optimize.argumentpropagation.codescanner.ConcreteParameterState;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+
+/**
+ * Represents that a parameter should always be reprocessed if we have non-trivial information about
+ * it (e.g., abstract value, dynamic type, nullability).
+ */
+public class AlwaysTrueParameterReprocessingCriteria extends ParameterReprocessingCriteria {
+
+  public static final AlwaysTrueParameterReprocessingCriteria INSTANCE =
+      new AlwaysTrueParameterReprocessingCriteria();
+
+  private AlwaysTrueParameterReprocessingCriteria() {}
+
+  public static AlwaysTrueParameterReprocessingCriteria get() {
+    return INSTANCE;
+  }
+
+  @Override
+  public boolean isAlwaysReprocess() {
+    return true;
+  }
+
+  @Override
+  public boolean shouldReprocess(
+      AppView<AppInfoWithLiveness> appView,
+      ConcreteParameterState parameterState,
+      DexType parameterType) {
+    return true;
+  }
+
+  @Override
+  public boolean shouldReprocessDueToAbstractValue() {
+    return true;
+  }
+
+  @Override
+  public boolean shouldReprocessDueToDynamicType() {
+    return true;
+  }
+
+  @Override
+  public boolean shouldReprocessDueToNullability() {
+    return true;
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/reprocessingcriteria/ArgumentPropagatorReprocessingCriteriaCollection.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/reprocessingcriteria/ArgumentPropagatorReprocessingCriteriaCollection.java
new file mode 100644
index 0000000..301738e
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/reprocessingcriteria/ArgumentPropagatorReprocessingCriteriaCollection.java
@@ -0,0 +1,142 @@
+// 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.optimize.argumentpropagation.reprocessingcriteria;
+
+import static com.android.tools.r8.ir.code.Opcodes.ASSUME;
+import static com.android.tools.r8.ir.code.Opcodes.IF;
+import static com.android.tools.r8.ir.code.Opcodes.INSTANCE_GET;
+import static com.android.tools.r8.ir.code.Opcodes.INSTANCE_PUT;
+import static com.android.tools.r8.ir.code.Opcodes.INVOKE_DIRECT;
+import static com.android.tools.r8.ir.code.Opcodes.INVOKE_INTERFACE;
+import static com.android.tools.r8.ir.code.Opcodes.INVOKE_STATIC;
+import static com.android.tools.r8.ir.code.Opcodes.INVOKE_SUPER;
+import static com.android.tools.r8.ir.code.Opcodes.INVOKE_VIRTUAL;
+import static com.android.tools.r8.ir.code.Opcodes.RETURN;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.MethodResolutionResult.SingleResolutionResult;
+import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.ir.code.Argument;
+import com.android.tools.r8.ir.code.IRCode;
+import com.android.tools.r8.ir.code.Instruction;
+import com.android.tools.r8.ir.code.InstructionIterator;
+import com.android.tools.r8.ir.code.InvokeMethodWithReceiver;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import it.unimi.dsi.fastutil.ints.Int2ReferenceMap;
+import it.unimi.dsi.fastutil.ints.Int2ReferenceOpenHashMap;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+public class ArgumentPropagatorReprocessingCriteriaCollection {
+
+  private final AppView<AppInfoWithLiveness> appView;
+
+  private final Map<DexMethod, MethodReprocessingCriteria> reproccessingCriteria =
+      new ConcurrentHashMap<>();
+
+  public ArgumentPropagatorReprocessingCriteriaCollection(AppView<AppInfoWithLiveness> appView) {
+    this.appView = appView;
+  }
+
+  public MethodReprocessingCriteria getReprocessingCriteria(ProgramMethod method) {
+    return reproccessingCriteria.getOrDefault(
+        method.getReference(), MethodReprocessingCriteria.empty());
+  }
+
+  /**
+   * Analyzes the uses of the method arguments to determine if a given piece of optimization info
+   * related to the arguments should or should not lead to reprocessing of the given method.
+   */
+  public void analyzeArgumentUses(ProgramMethod method, IRCode code) {
+    Int2ReferenceMap<ParameterReprocessingCriteria> methodReprocessingCriteria =
+        new Int2ReferenceOpenHashMap<>();
+
+    // Analyze each of the Argument instructions.
+    InstructionIterator instructionIterator = code.entryBlock().iterator();
+    for (Argument argument = instructionIterator.next().asArgument();
+        argument != null;
+        argument = instructionIterator.next().asArgument()) {
+      ParameterReprocessingCriteria reprocessingCriteria = analyzeArgumentUses(argument);
+      if (!reprocessingCriteria.isAlwaysReprocess()) {
+        methodReprocessingCriteria.put(argument.getIndex(), reprocessingCriteria);
+      }
+    }
+
+    // If there are some parameters which should not be naively reprocessed if they hold non-trivial
+    // optimization info, then record this information. If the map is empty, then the method should
+    // always be reprocessed if we find non-trivial optimization info for some of the parameters.
+    if (!methodReprocessingCriteria.isEmpty()) {
+      reproccessingCriteria.put(
+          method.getReference(), new MethodReprocessingCriteria(methodReprocessingCriteria));
+    }
+  }
+
+  private ParameterReprocessingCriteria analyzeArgumentUses(Argument argument) {
+    // For now, always reprocess if we have non-trivial information about primitive types.
+    // TODO(b/190154391): Introduce analysis for primitives.
+    if (argument.getOutType().isPrimitiveType()) {
+      return ParameterReprocessingCriteria.alwaysReprocess();
+    }
+
+    ParameterReprocessingCriteria.Builder builder = ParameterReprocessingCriteria.builder();
+    for (Instruction instruction : argument.outValue().aliasedUsers()) {
+      // TODO(b/190154391): Introduce analysis for usefulness of abstract value and nullability.
+      builder.setReprocessDueToAbstractValue().setReprocessDueToNullability();
+
+      switch (instruction.opcode()) {
+        case ASSUME:
+        case IF:
+        case INSTANCE_GET:
+        case INSTANCE_PUT:
+        case RETURN:
+          break;
+
+        case INVOKE_DIRECT:
+        case INVOKE_STATIC:
+          // Do not reprocess calls without dynamic dispatch due to dynamic type information.
+          break;
+
+        case INVOKE_INTERFACE:
+        case INVOKE_SUPER:
+        case INVOKE_VIRTUAL:
+          {
+            InvokeMethodWithReceiver invoke = instruction.asInvokeMethodWithReceiver();
+
+            // Do not reprocess calls with dynamic dispatch due to dynamic type information of a
+            // non-receiver operand.
+            if (invoke.getReceiver().getAliasedValue() != argument.outValue()) {
+              break;
+            }
+
+            // Do not reprocess the method if the invoke resolves to a library method.
+            SingleResolutionResult resolutionResult =
+                appView
+                    .appInfo()
+                    .unsafeResolveMethodDueToDexFormat(invoke.getInvokedMethod())
+                    .asSingleResolution();
+            if (resolutionResult == null
+                || !resolutionResult.getResolvedHolder().isProgramClass()) {
+              break;
+            }
+
+            builder.setReprocessDueToDynamicType();
+            break;
+          }
+
+        default:
+          // Conservatively reprocess the method if we have a non-trivial dynamic type.
+          builder.setReprocessDueToDynamicType();
+          break;
+      }
+
+      if (builder.shouldAlwaysReprocess()) {
+        break;
+      }
+    }
+
+    return builder.build();
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/reprocessingcriteria/MethodReprocessingCriteria.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/reprocessingcriteria/MethodReprocessingCriteria.java
new file mode 100644
index 0000000..527ee9e
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/reprocessingcriteria/MethodReprocessingCriteria.java
@@ -0,0 +1,62 @@
+// 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.optimize.argumentpropagation.reprocessingcriteria;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.optimize.argumentpropagation.codescanner.ConcreteMonomorphicMethodState;
+import com.android.tools.r8.optimize.argumentpropagation.codescanner.ParameterState;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import it.unimi.dsi.fastutil.ints.Int2ReferenceMap;
+import it.unimi.dsi.fastutil.ints.Int2ReferenceOpenHashMap;
+
+public class MethodReprocessingCriteria {
+
+  public static final MethodReprocessingCriteria EMPTY = new MethodReprocessingCriteria();
+
+  private final Int2ReferenceMap<ParameterReprocessingCriteria> reproccesingCriteria;
+
+  private MethodReprocessingCriteria() {
+    this.reproccesingCriteria = new Int2ReferenceOpenHashMap<>();
+  }
+
+  public MethodReprocessingCriteria(
+      Int2ReferenceMap<ParameterReprocessingCriteria> reproccesingCriteria) {
+    assert !reproccesingCriteria.isEmpty();
+    this.reproccesingCriteria = reproccesingCriteria;
+  }
+
+  public static MethodReprocessingCriteria empty() {
+    return EMPTY;
+  }
+
+  public ParameterReprocessingCriteria getParameterReprocessingCriteria(int parameterIndex) {
+    return reproccesingCriteria.getOrDefault(
+        parameterIndex, ParameterReprocessingCriteria.alwaysReprocess());
+  }
+
+  public boolean shouldReprocess(
+      AppView<AppInfoWithLiveness> appView,
+      ProgramMethod method,
+      ConcreteMonomorphicMethodState methodState) {
+    for (int parameterIndex = 0; parameterIndex < methodState.size(); parameterIndex++) {
+      ParameterState parameterState = methodState.getParameterState(parameterIndex);
+      assert !parameterState.isBottom();
+      if (parameterState.isUnknown()) {
+        continue;
+      }
+
+      ParameterReprocessingCriteria parameterReprocessingCriteria =
+          getParameterReprocessingCriteria(parameterIndex);
+      DexType parameterType = method.getArgumentType(parameterIndex);
+      if (parameterReprocessingCriteria.shouldReprocess(
+          appView, parameterState.asConcrete(), parameterType)) {
+        return true;
+      }
+    }
+    return false;
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/reprocessingcriteria/NonTrivialParameterReprocessingCriteria.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/reprocessingcriteria/NonTrivialParameterReprocessingCriteria.java
new file mode 100644
index 0000000..b55bf13
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/reprocessingcriteria/NonTrivialParameterReprocessingCriteria.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.optimize.argumentpropagation.reprocessingcriteria;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.ir.analysis.type.DynamicType;
+import com.android.tools.r8.ir.analysis.type.Nullability;
+import com.android.tools.r8.optimize.argumentpropagation.codescanner.ConcreteParameterState;
+import com.android.tools.r8.optimize.argumentpropagation.codescanner.ConcretePrimitiveTypeParameterState;
+import com.android.tools.r8.optimize.argumentpropagation.codescanner.ConcreteReferenceTypeParameterState;
+import com.android.tools.r8.optimize.argumentpropagation.utils.WideningUtils;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+
+/**
+ * Represents that a parameter should be reprocessed under certain conditions if we have non-trivial
+ * information about it (e.g., abstract value, dynamic type, nullability).
+ *
+ * <p>Example: If we determine that a parameter should not be reprocessed if we only have
+ * non-trivial information about its dynamic type, then an instance of this class is used which
+ * returns false in {@link #shouldReprocessDueToDynamicType()}.
+ */
+public class NonTrivialParameterReprocessingCriteria extends ParameterReprocessingCriteria {
+
+  public NonTrivialParameterReprocessingCriteria(boolean reprocessDueToDynamicType) {
+    assert !reprocessDueToDynamicType;
+  }
+
+  @Override
+  public boolean shouldReprocess(
+      AppView<AppInfoWithLiveness> appView,
+      ConcreteParameterState parameterState,
+      DexType parameterType) {
+    if (parameterState.isReferenceParameter()) {
+      return shouldReprocess(appView, parameterState.asReferenceParameter(), parameterType);
+    } else {
+      assert parameterState.isPrimitiveParameter();
+      return shouldReprocess(appView, parameterState.asPrimitiveParameter(), parameterType);
+    }
+  }
+
+  @Override
+  public boolean shouldReprocessDueToAbstractValue() {
+    return true;
+  }
+
+  @Override
+  public boolean shouldReprocessDueToDynamicType() {
+    return false;
+  }
+
+  @Override
+  public boolean shouldReprocessDueToNullability() {
+    return true;
+  }
+
+  private boolean shouldReprocess(
+      AppView<AppInfoWithLiveness> appView,
+      ConcretePrimitiveTypeParameterState parameterState,
+      DexType parameterType) {
+    return true;
+  }
+
+  private boolean shouldReprocess(
+      AppView<AppInfoWithLiveness> appView,
+      ConcreteReferenceTypeParameterState parameterState,
+      DexType parameterType) {
+    if (shouldReprocessDueToAbstractValue()
+        && !parameterState.getAbstractValue(appView).isUnknown()) {
+      return true;
+    }
+    if (shouldReprocessDueToDynamicType()) {
+      DynamicType widenedDynamicType =
+          WideningUtils.widenDynamicNonReceiverType(
+              appView,
+              parameterState.getDynamicType().withNullability(Nullability.maybeNull()),
+              parameterType);
+      if (!widenedDynamicType.isUnknown()) {
+        return true;
+      }
+    }
+    if (shouldReprocessDueToNullability()
+        && !parameterState.isReceiverParameter()
+        && !parameterState.getNullability().isUnknown()) {
+      return true;
+    }
+    return false;
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/reprocessingcriteria/ParameterReprocessingCriteria.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/reprocessingcriteria/ParameterReprocessingCriteria.java
new file mode 100644
index 0000000..9022f81
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/reprocessingcriteria/ParameterReprocessingCriteria.java
@@ -0,0 +1,86 @@
+// Copyright (c) 2021, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.optimize.argumentpropagation.reprocessingcriteria;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.optimize.argumentpropagation.codescanner.ConcreteParameterState;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+
+public abstract class ParameterReprocessingCriteria {
+
+  public static AlwaysTrueParameterReprocessingCriteria alwaysReprocess() {
+    return AlwaysTrueParameterReprocessingCriteria.get();
+  }
+
+  public static AlwaysFalseParameterReprocessingCriteria neverReprocess() {
+    return AlwaysFalseParameterReprocessingCriteria.get();
+  }
+
+  public static Builder builder() {
+    return new Builder();
+  }
+
+  public boolean isAlwaysReprocess() {
+    return false;
+  }
+
+  public boolean isNeverReprocess() {
+    return false;
+  }
+
+  public abstract boolean shouldReprocess(
+      AppView<AppInfoWithLiveness> appView,
+      ConcreteParameterState parameterState,
+      DexType parameterType);
+
+  public abstract boolean shouldReprocessDueToAbstractValue();
+
+  public abstract boolean shouldReprocessDueToDynamicType();
+
+  public abstract boolean shouldReprocessDueToNullability();
+
+  public static class Builder {
+
+    private boolean reprocessDueToAbstractValue;
+    private boolean reprocessDueToDynamicType;
+    private boolean reprocessDueToNullability;
+
+    Builder setReprocessDueToAbstractValue() {
+      reprocessDueToAbstractValue = true;
+      return this;
+    }
+
+    Builder setReprocessDueToDynamicType() {
+      reprocessDueToDynamicType = true;
+      return this;
+    }
+
+    Builder setReprocessDueToNullability() {
+      reprocessDueToNullability = true;
+      return this;
+    }
+
+    boolean shouldAlwaysReprocess() {
+      return reprocessDueToAbstractValue && reprocessDueToDynamicType && reprocessDueToNullability;
+    }
+
+    boolean shouldNeverReprocess() {
+      return !reprocessDueToAbstractValue
+          && !reprocessDueToDynamicType
+          && !reprocessDueToNullability;
+    }
+
+    public ParameterReprocessingCriteria build() {
+      if (shouldAlwaysReprocess()) {
+        return alwaysReprocess();
+      }
+      if (shouldNeverReprocess()) {
+        return neverReprocess();
+      }
+      return new NonTrivialParameterReprocessingCriteria(reprocessDueToDynamicType);
+    }
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java b/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
index be31b16..acc8410 100644
--- a/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
+++ b/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
@@ -1048,6 +1048,10 @@
       keepInfo.mutate(
           keepInfo -> keepInfo.removeKeepInfoForPrunedItems(prunedItems.getRemovedClasses()));
     }
+    if (!prunedItems.getRemovedMethods().isEmpty()) {
+      keepInfo.mutate(
+          keepInfo -> keepInfo.removeKeepInfoForPrunedItems(prunedItems.getRemovedMethods()));
+    }
     return new AppInfoWithLiveness(this, prunedItems);
   }
 
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 41eb11b..66e9136 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -6,6 +6,7 @@
 import static com.android.tools.r8.graph.DexProgramClass.asProgramClassOrNull;
 import static com.android.tools.r8.graph.FieldAccessInfoImpl.MISSING_FIELD_ACCESS_INFO;
 import static com.android.tools.r8.ir.desugar.LambdaDescriptor.isLambdaMetafactoryMethod;
+import static com.android.tools.r8.ir.desugar.itf.InterfaceMethodRewriter.Flavor.ExcludeDexResources;
 import static com.android.tools.r8.naming.IdentifierNameStringUtils.identifyIdentifier;
 import static com.android.tools.r8.naming.IdentifierNameStringUtils.isReflectionMethod;
 import static com.android.tools.r8.utils.FunctionUtils.ignoreArgument;
@@ -59,6 +60,7 @@
 import com.android.tools.r8.graph.FieldResolutionResult;
 import com.android.tools.r8.graph.GenericSignatureEnqueuerAnalysis;
 import com.android.tools.r8.graph.InnerClassAttribute;
+import com.android.tools.r8.graph.InvalidCode;
 import com.android.tools.r8.graph.LookupLambdaTarget;
 import com.android.tools.r8.graph.LookupTarget;
 import com.android.tools.r8.graph.MethodAccessInfoCollection;
@@ -98,7 +100,10 @@
 import com.android.tools.r8.ir.desugar.LambdaClass;
 import com.android.tools.r8.ir.desugar.LambdaDescriptor;
 import com.android.tools.r8.ir.desugar.ProgramAdditions;
+import com.android.tools.r8.ir.desugar.constantdynamic.ConstantDynamicClass;
 import com.android.tools.r8.ir.desugar.desugaredlibrary.DesugaredLibraryAPIConverter;
+import com.android.tools.r8.ir.desugar.itf.InterfaceMethodProcessorFacade;
+import com.android.tools.r8.ir.desugar.itf.InterfaceProcessor;
 import com.android.tools.r8.kotlin.KotlinMetadataEnqueuerExtension;
 import com.android.tools.r8.logging.Log;
 import com.android.tools.r8.naming.identifiernamestring.IdentifierNameStringLookupResult;
@@ -120,6 +125,7 @@
 import com.android.tools.r8.shaking.ScopedDexMethodSet.AddMethodIfMoreVisibleResult;
 import com.android.tools.r8.synthesis.SyntheticItems.SynthesizingContextOracle;
 import com.android.tools.r8.utils.Action;
+import com.android.tools.r8.utils.BooleanBox;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.IteratorUtils;
 import com.android.tools.r8.utils.OptionalBool;
@@ -131,10 +137,10 @@
 import com.android.tools.r8.utils.Visibility;
 import com.android.tools.r8.utils.WorkList;
 import com.android.tools.r8.utils.collections.ProgramFieldSet;
+import com.android.tools.r8.utils.collections.ProgramMethodMap;
 import com.android.tools.r8.utils.collections.ProgramMethodSet;
 import com.google.common.base.Equivalence.Wrapper;
 import com.google.common.collect.ImmutableSet;
-import com.google.common.collect.Iterables;
 import com.google.common.collect.Maps;
 import com.google.common.collect.Sets;
 import com.google.common.collect.Sets.SetView;
@@ -423,7 +429,20 @@
   private final GraphReporter graphReporter;
 
   private final CfInstructionDesugaringCollection desugaring;
-  private final ProgramMethodSet pendingDesugaring = ProgramMethodSet.create();
+  private final ProgramMethodSet pendingCodeDesugaring = ProgramMethodSet.create();
+
+  // Collections for tracing progress on interface method desugaring.
+
+  // The pending method move set is all the methods that need to be moved to companions.
+  // They may or may not need desugaring.
+  private final ProgramMethodSet pendingMethodMove = ProgramMethodSet.create();
+
+  // The inverse map records references to companion methods that may now be active but yet to
+  // be moved.
+  private final ProgramMethodMap<ProgramMethod> pendingMethodMoveInverse =
+      ProgramMethodMap.createConcurrent();
+
+  private final InterfaceProcessor interfaceProcessor;
 
   Enqueuer(
       AppView<? extends AppInfoWithClassHierarchy> appView,
@@ -466,10 +485,13 @@
     failedFieldResolutionTargets = SetUtils.newIdentityHashSet(0);
     liveMethods = new LiveMethodsSet(graphReporter::registerMethod);
     liveFields = new LiveFieldsSet(graphReporter::registerField);
-    desugaring =
-        mode.isInitialTreeShaking()
-            ? CfInstructionDesugaringCollection.create(appView)
-            : CfInstructionDesugaringCollection.empty();
+    if (mode.isInitialTreeShaking()) {
+      desugaring = CfInstructionDesugaringCollection.create(appView);
+      interfaceProcessor = new InterfaceProcessor(appView);
+    } else {
+      desugaring = CfInstructionDesugaringCollection.empty();
+      interfaceProcessor = null;
+    }
 
     objectAllocationInfoCollection =
         ObjectAllocationInfoCollectionImpl.builder(mode.isInitialTreeShaking(), graphReporter);
@@ -3080,6 +3102,7 @@
   public EnqueuerResult traceApplication(
       RootSet rootSet, ExecutorService executorService, Timing timing) throws ExecutionException {
     this.rootSet = rootSet;
+    rootSet.pendingMethodMoveInverse.forEach(pendingMethodMoveInverse::put);
     // Translate the result of root-set computation into enqueuer actions.
     if (mode.isTreeShaking()
         && appView.options().hasProguardConfiguration()
@@ -3324,6 +3347,8 @@
 
     private final Map<DexMethod, ProgramMethod> liveMethods = new ConcurrentHashMap<>();
 
+    private final ProgramMethodSet neverInlineMethods = ProgramMethodSet.createConcurrent();
+
     private final Map<DexType, DexClasspathClass> syntheticClasspathClasses =
         new ConcurrentHashMap<>();
 
@@ -3355,7 +3380,7 @@
 
     public void addLiveClasspathClass(DexClasspathClass clazz) {
       DexClasspathClass old = syntheticClasspathClasses.put(clazz.type, clazz);
-      assert old == null;
+      assert old == null || old == clazz;
     }
 
     public void addLiveMethods(Iterable<ProgramMethod> methods) {
@@ -3364,8 +3389,12 @@
 
     public void addLiveMethod(ProgramMethod method) {
       DexMethod signature = method.getDefinition().getReference();
-      assert !liveMethods.containsKey(signature);
-      liveMethods.put(signature, method);
+      ProgramMethod old = liveMethods.put(signature, method);
+      assert old == null;
+    }
+
+    public void addMethodWithDesugaredCodeForTracing(ProgramMethod method) {
+      desugaredMethods.add(method);
     }
 
     public void injectInterface(DexProgramClass clazz, DexClass newInterface) {
@@ -3386,6 +3415,10 @@
       return set;
     }
 
+    public void addNeverInlineMethod(ProgramMethod method) {
+      neverInlineMethods.add(method);
+    }
+
     void enqueueWorkItems(Enqueuer enqueuer) {
       assert enqueuer.mode.isInitialTreeShaking();
 
@@ -3409,6 +3442,8 @@
             enqueuer.objectAllocationInfoCollection.injectInterfaces(
                 enqueuer.appInfo(), clazz, itfs);
           });
+
+      neverInlineMethods.forEach(m -> enqueuer.rootSet.neverInline.add(m.getReference()));
     }
   }
 
@@ -3436,31 +3471,95 @@
     additions.enqueueWorkItems(this);
   }
 
+  private boolean mustMoveToInterfaceCompanionMethod(ProgramMethod method) {
+    return method.getHolder().isInterface()
+        && method.getDefinition().isNonAbstractNonNativeMethod()
+        && !method.getDefinition().isInitializer();
+  }
+
+  private boolean addToPendingDesugaring(ProgramMethod method) {
+    if (options.isInterfaceMethodDesugaringEnabled()) {
+      if (mustMoveToInterfaceCompanionMethod(method)) {
+        pendingMethodMove.add(method);
+        return true;
+      }
+      ProgramMethod nonMovedMethod = pendingMethodMoveInverse.get(method);
+      if (nonMovedMethod != null) {
+        // Any non-moved code must be a proper pending item.
+        assert InvalidCode.isInvalidCode(method.getDefinition().getCode());
+        assert !InvalidCode.isInvalidCode(nonMovedMethod.getDefinition().getCode());
+        pendingMethodMove.add(nonMovedMethod);
+        return true;
+      }
+    }
+    if (desugaring.needsDesugaring(method)) {
+      pendingCodeDesugaring.add(method);
+      return true;
+    }
+    return false;
+  }
+
   private void desugar(SyntheticAdditions additions) throws ExecutionException {
-    if (pendingDesugaring.isEmpty()) {
+    if (pendingCodeDesugaring.isEmpty() && pendingMethodMove.isEmpty()) {
       return;
     }
 
+    // All non-moving methods are ready for tracing post desugar.
+    pendingCodeDesugaring.forEach(additions::addMethodWithDesugaredCodeForTracing);
+    // Then amend the desugar set with the move methods that need desugaring.
+    for (ProgramMethod method : pendingMethodMove) {
+      if (desugaring.needsDesugaring(method)) {
+        pendingCodeDesugaring.add(method);
+      }
+    }
+
+    R8CfInstructionDesugaringEventConsumer eventConsumer =
+        CfInstructionDesugaringEventConsumer.createForR8(
+            appView,
+            this::recordLambdaSynthesizingContext,
+            this::recordConstantDynamicSynthesizingContext,
+            this::recordTwrCloseResourceMethodSynthesizingContext,
+            additions,
+            (method, companion) -> {
+              if (!isMethodLive(method)) {
+                // Record the original placement of the companion method such that we can desugar
+                // and transfer the code if and when the companion method becomes live.
+                pendingMethodMoveInverse.put(companion, method);
+              }
+            });
+
     // Prepare desugaring by collecting all the synthetic methods required on program classes.
     ProgramAdditions programAdditions = new ProgramAdditions();
     ThreadUtils.processItems(
-        pendingDesugaring, method -> desugaring.prepare(method, programAdditions), executorService);
+        pendingCodeDesugaring,
+        method -> desugaring.prepare(method, programAdditions),
+        executorService);
     programAdditions.apply(executorService);
 
-    R8CfInstructionDesugaringEventConsumer desugaringEventConsumer =
-        CfInstructionDesugaringEventConsumer.createForR8(
-            appView,
-            this::recordLambdaSynthesizingContext,
-            this::recordTwrCloseResourceMethodSynthesizingContext,
-            additions);
+    // Then do the actual desugaring.
     ThreadUtils.processItems(
-        pendingDesugaring,
-        method ->
-            desugaring.desugar(method, additions.getMethodContext(method), desugaringEventConsumer),
+        pendingCodeDesugaring,
+        method -> desugaring.desugar(method, additions.getMethodContext(method), eventConsumer),
         executorService);
-    desugaringEventConsumer.finalizeDesugaring();
-    Iterables.addAll(additions.desugaredMethods, pendingDesugaring);
-    pendingDesugaring.clear();
+
+    // Move the pending methods and mark them live and ready for tracing.
+    for (ProgramMethod method : pendingMethodMove) {
+      ProgramMethod companion =
+          interfaceProcessor
+              .getHelper()
+              .ensureMethodOfProgramCompanionClassStub(method, eventConsumer);
+      interfaceProcessor.finalizeMoveToCompanionMethod(method, companion);
+      pendingMethodMoveInverse.remove(companion);
+      if (!isMethodLive(companion)) {
+        additions.addLiveMethod(companion);
+      }
+      additions.addMethodWithDesugaredCodeForTracing(companion);
+    }
+
+    eventConsumer.finalizeDesugaring();
+
+    pendingMethodMove.clear();
+    pendingCodeDesugaring.clear();
   }
 
   private void recordLambdaSynthesizingContext(LambdaClass lambdaClass, ProgramMethod context) {
@@ -3469,6 +3568,13 @@
     }
   }
 
+  private void recordConstantDynamicSynthesizingContext(
+      ConstantDynamicClass constantDynamicClass, ProgramMethod context) {
+    synchronized (synthesizingContexts) {
+      synthesizingContexts.put(constantDynamicClass.getConstantDynamicProgramClass(), context);
+    }
+  }
+
   private void recordTwrCloseResourceMethodSynthesizingContext(
       ProgramMethod closeMethod, ProgramMethod context) {
     synchronized (synthesizingContexts) {
@@ -3481,8 +3587,6 @@
       DexProgramClass holder = bridge.getHolder();
       DexEncodedMethod method = bridge.getDefinition();
       holder.addVirtualMethod(method);
-      additions.addLiveMethodWithKeepAction(
-          bridge, joiner -> joiner.disallowOptimization().disallowShrinking());
     }
     syntheticInterfaceMethodBridges.clear();
   }
@@ -3593,15 +3697,15 @@
             rootSet.mayHaveSideEffects,
             rootSet.noSideEffects,
             rootSet.assumedValues,
-            rootSet.alwaysInline,
-            rootSet.forceInline,
-            rootSet.neverInline,
-            rootSet.neverInlineDueToSingleCaller,
-            rootSet.whyAreYouNotInlining,
-            rootSet.keepConstantArguments,
-            rootSet.keepUnusedArguments,
-            rootSet.reprocess,
-            rootSet.neverReprocess,
+            amendWithCompanionMethods(rootSet.alwaysInline),
+            amendWithCompanionMethods(rootSet.forceInline),
+            amendWithCompanionMethods(rootSet.neverInline),
+            amendWithCompanionMethods(rootSet.neverInlineDueToSingleCaller),
+            amendWithCompanionMethods(rootSet.whyAreYouNotInlining),
+            amendWithCompanionMethods(rootSet.keepConstantArguments),
+            amendWithCompanionMethods(rootSet.keepUnusedArguments),
+            amendWithCompanionMethods(rootSet.reprocess),
+            amendWithCompanionMethods(rootSet.neverReprocess),
             rootSet.alwaysClassInline,
             rootSet.neverClassInline,
             noClassMerging,
@@ -3620,6 +3724,22 @@
     return new EnqueuerResult(appInfoWithLiveness);
   }
 
+  private Set<DexMethod> amendWithCompanionMethods(Set<DexMethod> methods) {
+    if (methods.isEmpty() || interfaceProcessor == null) {
+      return methods;
+    }
+    BooleanBox changed = new BooleanBox(false);
+    ImmutableSet.Builder<DexMethod> builder = ImmutableSet.builder();
+    interfaceProcessor.forEachMethodToMove(
+        (method, companion) -> {
+          if (methods.contains(method)) {
+            changed.set(true);
+            builder.add(companion);
+          }
+        });
+    return changed.isTrue() ? builder.addAll(methods).build() : methods;
+  }
+
   private boolean verifyReferences(DexApplication app) {
     WorkList<DexClass> worklist = WorkList.newIdentityWorkList();
     for (DexProgramClass clazz : liveTypes.getItems()) {
@@ -3762,6 +3882,16 @@
           continue;
         }
 
+        for (DelayedRootSetActionItem delayedRootSetActionItem :
+            rootSet.delayedRootSetActionItems) {
+          if (delayedRootSetActionItem.isInterfaceMethodSyntheticBridgeAction()) {
+            identifySyntheticInterfaceMethodBridges(
+                delayedRootSetActionItem.asInterfaceMethodSyntheticBridgeAction());
+          }
+        }
+
+        synthesize();
+
         ConsequentRootSet consequentRootSet = computeDelayedInterfaceMethodSyntheticBridges();
         addConsequentRootSet(consequentRootSet);
         rootSet
@@ -3773,11 +3903,6 @@
           continue;
         }
 
-        synthesize();
-        if (!workList.isEmpty()) {
-          continue;
-        }
-
         // Reached the fixpoint.
         break;
       }
@@ -3808,9 +3933,14 @@
     assert workList.isEmpty();
 
     R8PostProcessingDesugaringEventConsumer eventConsumer =
-        CfPostProcessingDesugaringEventConsumer.createForR8(syntheticAdditions);
-    CfPostProcessingDesugaringCollection.create(appView, null, desugaring.getRetargetingInfo())
-        .postProcessingDesugaring(liveTypes.items, eventConsumer, executorService);
+        CfPostProcessingDesugaringEventConsumer.createForR8(syntheticAdditions, desugaring);
+    InterfaceMethodProcessorFacade interfaceDesugaring =
+        desugaring.getInterfaceMethodPostProcessingDesugaringR8(
+            ExcludeDexResources, liveMethods::contains, interfaceProcessor);
+    CfPostProcessingDesugaringCollection.create(
+            appView, interfaceDesugaring, desugaring.getRetargetingInfo())
+        .postProcessingDesugaring(
+            liveTypes.items, liveMethods::contains, eventConsumer, executorService);
 
     if (syntheticAdditions.isEmpty()) {
       return;
@@ -3829,8 +3959,6 @@
       EnqueuerAction action = workList.poll();
       action.run(this);
     }
-
-    eventConsumer.finalizeDesugaring();
   }
 
   private long getNumberOfLiveItems() {
@@ -3846,6 +3974,7 @@
     //  enqueuer, similar to Enqueuer#dependentMinimumKeepClassInfo.
     rootSet.addConsequentRootSet(consequentRootSet);
     includeMinimumKeepInfo(consequentRootSet);
+    consequentRootSet.pendingMethodMoveInverse.forEach(pendingMethodMoveInverse::put);
 
     // Check for compatibility rules indicating that the holder must be implicitly kept.
     if (forceProguardCompatibility) {
@@ -3873,11 +4002,10 @@
   private final Map<DexMethod, ProgramMethod> syntheticInterfaceMethodBridges =
       new LinkedHashMap<>();
 
-  private void handleInterfaceMethodSyntheticBridgeAction(
-      InterfaceMethodSyntheticBridgeAction action, RootSetBuilder builder) {
+  private void identifySyntheticInterfaceMethodBridges(
+      InterfaceMethodSyntheticBridgeAction action) {
     ProgramMethod methodToKeep = action.getMethodToKeep();
     ProgramMethod singleTarget = action.getSingleTarget();
-    DexEncodedMethod singleTargetMethod = singleTarget.getDefinition();
     if (rootSet.isShrinkingDisallowedUnconditionally(singleTarget, options)) {
       return;
     }
@@ -3886,20 +4014,19 @@
             methodToKeep.getDefinition().getReference())) {
       syntheticInterfaceMethodBridges.put(
           methodToKeep.getDefinition().getReference(), methodToKeep);
-      assert null
-          == methodToKeep.getHolder().lookupMethod(methodToKeep.getDefinition().getReference());
-      if (singleTargetMethod.isLibraryMethodOverride().isTrue()) {
-        methodToKeep.getDefinition().setLibraryMethodOverride(OptionalBool.TRUE);
-      }
-      DexProgramClass singleTargetHolder = singleTarget.getHolder();
-      assert singleTargetHolder.isInterface();
-      markVirtualMethodAsReachable(
-          singleTargetMethod.getReference(),
-          singleTargetHolder.isInterface(),
-          singleTarget,
-          graphReporter.fakeReportShouldNotBeUsed());
-      workList.enqueueMarkMethodLiveAction(
-          singleTarget, singleTarget, graphReporter.fakeReportShouldNotBeUsed());
+    }
+  }
+
+  private void handleInterfaceMethodSyntheticBridgeAction(
+      InterfaceMethodSyntheticBridgeAction action, RootSetBuilder builder) {
+    ProgramMethod methodToKeep = action.getMethodToKeep();
+    ProgramMethod singleTarget = action.getSingleTarget();
+    DexEncodedMethod singleTargetMethod = singleTarget.getDefinition();
+    if (rootSet.isShrinkingDisallowedUnconditionally(singleTarget, options)) {
+      return;
+    }
+    if (singleTargetMethod.isLibraryMethodOverride().isTrue()) {
+      methodToKeep.getDefinition().setLibraryMethodOverride(OptionalBool.TRUE);
     }
     action.getAction().accept(builder);
   }
@@ -3922,33 +4049,23 @@
     DexEncodedMethod definition = target.getDefinition();
     DexProgramClass holder = target.getHolder();
     DexMethod reference = target.getReference();
+    markMethodAsTargeted(target, reason);
     if (definition.isVirtualMethod()) {
       // A virtual method. Mark it as reachable so that subclasses, if instantiated, keep
       // their overrides. However, we don't mark it live, as a keep rule might not imply that
       // the corresponding class is live.
       markVirtualMethodAsReachable(reference, holder.isInterface(), target, reason);
-      if (holder.isInterface()) {
-        // Reachability for default methods is based on live subtypes in general. For keep rules,
-        // we need special handling as we essentially might have live subtypes that are outside of
-        // the current compilation unit. Keep either the default-method or its implementation
-        // method.
+      // When generating interface bridges the method may be inserted into a live hierarchy.
+      // If so we need to also mark it as live as the reachable check above will not reprocess the
+      // hierarchy.
+      // TODO(b/183998768): The check for isInterface here should be possible to remove now.
+      if (definition.isNonAbstractVirtualMethod()
+          && (objectAllocationInfoCollection.isInstantiatedDirectlyOrHasInstantiatedSubtype(holder)
+              || holder.isInterface())) {
         // TODO(b/120959039): Codify the kept-graph expectations for these cases in tests.
-        if (definition.isNonAbstractVirtualMethod()) {
-          markVirtualMethodAsLive(target, reason);
-        } else {
-          DexEncodedMethod implementation = definition.getDefaultInterfaceMethodImplementation();
-          if (implementation != null) {
-            DexProgramClass companion =
-                asProgramClassOrNull(appInfo().definitionFor(implementation.getHolderType()));
-            markTypeAsLive(companion, graphReporter.reportCompanionClass(holder, companion));
-            markVirtualMethodAsLive(
-                new ProgramMethod(companion, implementation),
-                graphReporter.reportCompanionMethod(definition, implementation));
-          }
-        }
+        markVirtualMethodAsLive(target, reason);
       }
     } else {
-      markMethodAsTargeted(target, reason);
       markDirectStaticOrConstructorMethodAsLive(target, reason);
     }
   }
@@ -4090,9 +4207,10 @@
   }
 
   private void traceNonDesugaredCode(ProgramMethod method) {
-    if (getMode().isInitialTreeShaking() && desugaring.needsDesugaring(method)) {
-      pendingDesugaring.add(method);
-      return;
+    if (getMode().isInitialTreeShaking()) {
+      if (addToPendingDesugaring(method)) {
+        return;
+      }
     }
 
     traceCode(method);
diff --git a/src/main/java/com/android/tools/r8/shaking/EnqueuerWorklist.java b/src/main/java/com/android/tools/r8/shaking/EnqueuerWorklist.java
index cd52fd7..ad3d4d1 100644
--- a/src/main/java/com/android/tools/r8/shaking/EnqueuerWorklist.java
+++ b/src/main/java/com/android/tools/r8/shaking/EnqueuerWorklist.java
@@ -501,7 +501,8 @@
 
     @Override
     boolean enqueueAssertAction(Action assertion) {
-      throw attemptToEnqueue();
+      assertion.execute();
+      return true;
     }
 
     @Override
diff --git a/src/main/java/com/android/tools/r8/shaking/KeepInfoCollection.java b/src/main/java/com/android/tools/r8/shaking/KeepInfoCollection.java
index 0dcddda..c260f3e 100644
--- a/src/main/java/com/android/tools/r8/shaking/KeepInfoCollection.java
+++ b/src/main/java/com/android/tools/r8/shaking/KeepInfoCollection.java
@@ -244,10 +244,20 @@
       this.methodRuleInstances = methodRuleInstances;
     }
 
-    public void removeKeepInfoForPrunedItems(Set<DexType> removedClasses) {
-      keepClassInfo.keySet().removeIf(removedClasses::contains);
-      keepFieldInfo.keySet().removeIf(field -> removedClasses.contains(field.getHolderType()));
-      keepMethodInfo.keySet().removeIf(method -> removedClasses.contains(method.getHolderType()));
+    public void removeKeepInfoForPrunedItems(Set<? extends DexReference> removedReferences) {
+      keepClassInfo.keySet().removeIf(removedReferences::contains);
+      keepFieldInfo
+          .keySet()
+          .removeIf(
+              field ->
+                  (removedReferences.contains(field)
+                      || removedReferences.contains(field.getHolderType())));
+      keepMethodInfo
+          .keySet()
+          .removeIf(
+              method ->
+                  (removedReferences.contains(method)
+                      || removedReferences.contains(method.getHolderType())));
     }
 
     @Override
diff --git a/src/main/java/com/android/tools/r8/shaking/RootSetUtils.java b/src/main/java/com/android/tools/r8/shaking/RootSetUtils.java
index 73f5c94..72a1510 100644
--- a/src/main/java/com/android/tools/r8/shaking/RootSetUtils.java
+++ b/src/main/java/com/android/tools/r8/shaking/RootSetUtils.java
@@ -42,6 +42,8 @@
 import com.android.tools.r8.graph.PrunedItems;
 import com.android.tools.r8.graph.SubtypingInfo;
 import com.android.tools.r8.ir.analysis.proto.GeneratedMessageLiteBuilderShrinker;
+import com.android.tools.r8.ir.desugar.itf.InterfaceDesugaringSyntheticHelper;
+import com.android.tools.r8.ir.desugar.itf.InterfaceMethodDesugaringBaseEventConsumer;
 import com.android.tools.r8.ir.optimize.info.OptimizationFeedbackSimple;
 import com.android.tools.r8.logging.Log;
 import com.android.tools.r8.shaking.AnnotationMatchResult.AnnotationsIgnoredMatchResult;
@@ -58,6 +60,7 @@
 import com.android.tools.r8.utils.PredicateSet;
 import com.android.tools.r8.utils.StringDiagnostic;
 import com.android.tools.r8.utils.ThreadUtils;
+import com.android.tools.r8.utils.collections.ProgramMethodMap;
 import com.google.common.base.Equivalence.Wrapper;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
@@ -137,6 +140,10 @@
 
     private final OptimizationFeedbackSimple feedback = OptimizationFeedbackSimple.getInstance();
 
+    private final InterfaceDesugaringSyntheticHelper interfaceDesugaringSyntheticHelper;
+    private final ProgramMethodMap<ProgramMethod> pendingMethodMoveInverse =
+        ProgramMethodMap.create();
+
     private RootSetBuilder(
         AppView<? extends AppInfoWithClassHierarchy> appView,
         SubtypingInfo subtypingInfo,
@@ -146,6 +153,10 @@
       this.application = appView.appInfo().app().asDirect();
       this.rules = rules;
       this.options = appView.options();
+      interfaceDesugaringSyntheticHelper =
+          options.isInterfaceMethodDesugaringEnabled()
+              ? new InterfaceDesugaringSyntheticHelper(appView)
+              : null;
     }
 
     private RootSetBuilder(
@@ -383,7 +394,8 @@
           dependentKeepClassCompatRule,
           identifierNameStrings,
           ifRules,
-          Lists.newArrayList(delayedRootSetActionItems));
+          Lists.newArrayList(delayedRootSetActionItems),
+          pendingMethodMoveInverse);
     }
 
     private void propagateAssumeRules(DexClass clazz) {
@@ -453,7 +465,8 @@
           neverClassInline,
           dependentMinimumKeepInfo,
           dependentKeepClassCompatRule,
-          Lists.newArrayList(delayedRootSetActionItems));
+          Lists.newArrayList(delayedRootSetActionItems),
+          pendingMethodMoveInverse);
     }
 
     private static DexProgramClass testAndGetPrecondition(
@@ -1333,25 +1346,6 @@
           // Don't keep lambda deserialization methods.
           return;
         }
-        // If desugaring is enabled, private and static interface methods will be moved to a
-        // companion class. So we don't need to add them to the root set in the beginning.
-        if (options.isInterfaceMethodDesugaringEnabled()
-            && method.getDefinition().hasCode()
-            && (method.getAccessFlags().isPrivate() || method.getAccessFlags().isStatic())) {
-          DexClass holder = appView.definitionFor(method.getHolderType());
-          if (holder != null && holder.isInterface()) {
-            if (rule.isSpecific()) {
-              options.reporter.warning(
-                  new StringDiagnostic(
-                      "The rule `"
-                          + rule
-                          + "` is ignored because the targeting interface method `"
-                          + method.getReference().toSourceString()
-                          + "` will be desugared."));
-            }
-            return;
-          }
-        }
       }
 
       // The reason for keeping should link to the conditional rule as a whole, if present.
@@ -1388,6 +1382,44 @@
         preconditionEvent = UnconditionalKeepInfoEvent.get();
       }
 
+      if (isInterfaceMethodNeedingDesugaring(item)) {
+        ProgramMethod method = item.asMethod();
+        ProgramMethod companion =
+            interfaceDesugaringSyntheticHelper.ensureMethodOfProgramCompanionClassStub(
+                method,
+                new InterfaceMethodDesugaringBaseEventConsumer() {
+                  @Override
+                  public void acceptCompanionClassClinit(ProgramMethod method) {
+                    // No processing of synthesized CC.<clinit>. They will be picked up by tracing.
+                  }
+
+                  @Override
+                  public void acceptCompanionMethod(ProgramMethod method, ProgramMethod companion) {
+                    // The move will be included in the pending-inverse map below.
+                  }
+                });
+        // Add the method to the inverse map as tracing will now directly target the CC method.
+        pendingMethodMoveInverse.put(companion, method);
+        // Only shrinking and optimization are transferred for interface companion methods.
+        if (appView.options().isOptimizationEnabled() && !modifiers.allowsOptimization) {
+          dependentMinimumKeepInfo
+              .getOrCreateMinimumKeepInfoFor(preconditionEvent, companion.getReference())
+              .disallowOptimization();
+          context.markAsUsed();
+        }
+        if (appView.options().isShrinking() && !modifiers.allowsShrinking) {
+          dependentMinimumKeepInfo
+              .getOrCreateMinimumKeepInfoFor(preconditionEvent, companion.getReference())
+              .addRule(keepRule)
+              .disallowShrinking();
+          context.markAsUsed();
+        }
+        if (!item.asMethod().isDefaultMethod()) {
+          // Static and private methods do not apply to the original item.
+          return;
+        }
+      }
+
       if (appView.options().isAccessModificationEnabled() && !modifiers.allowsAccessModification) {
         dependentMinimumKeepInfo
             .getOrCreateMinimumKeepInfoFor(preconditionEvent, item.getReference())
@@ -1436,6 +1468,14 @@
       }
     }
 
+    private boolean isInterfaceMethodNeedingDesugaring(ProgramDefinition item) {
+      return options.isInterfaceMethodDesugaringEnabled()
+          && item.isMethod()
+          && item.asMethod().getHolder().isInterface()
+          && !item.asMethod().getDefinition().isClassInitializer()
+          && item.asMethod().getDefinition().hasCode();
+    }
+
     private void reportAssumeNoSideEffectsWarningForJavaLangClassMethod(
         DexClassAndMethod method, ProguardAssumeNoSideEffectRule context) {
       assert method.getHolderType() == options.dexItemFactory().objectType;
@@ -1486,6 +1526,7 @@
     private final DependentMinimumKeepInfoCollection dependentMinimumKeepInfo;
     final Map<DexType, Set<ProguardKeepRuleBase>> dependentKeepClassCompatRule;
     final List<DelayedRootSetActionItem> delayedRootSetActionItems;
+    public final ProgramMethodMap<ProgramMethod> pendingMethodMoveInverse;
 
     RootSetBase(
         Set<DexMethod> neverInline,
@@ -1493,13 +1534,15 @@
         Set<DexType> neverClassInline,
         DependentMinimumKeepInfoCollection dependentMinimumKeepInfo,
         Map<DexType, Set<ProguardKeepRuleBase>> dependentKeepClassCompatRule,
-        List<DelayedRootSetActionItem> delayedRootSetActionItems) {
+        List<DelayedRootSetActionItem> delayedRootSetActionItems,
+        ProgramMethodMap<ProgramMethod> pendingMethodMoveInverse) {
       this.neverInline = neverInline;
       this.neverInlineDueToSingleCaller = neverInlineDueToSingleCaller;
       this.neverClassInline = neverClassInline;
       this.dependentMinimumKeepInfo = dependentMinimumKeepInfo;
       this.dependentKeepClassCompatRule = dependentKeepClassCompatRule;
       this.delayedRootSetActionItems = delayedRootSetActionItems;
+      this.pendingMethodMoveInverse = pendingMethodMoveInverse;
     }
 
     Set<ProguardKeepRuleBase> getDependentKeepClassCompatRule(DexType type) {
@@ -1560,14 +1603,16 @@
         Map<DexType, Set<ProguardKeepRuleBase>> dependentKeepClassCompatRule,
         Set<DexReference> identifierNameStrings,
         Set<ProguardIfRule> ifRules,
-        List<DelayedRootSetActionItem> delayedRootSetActionItems) {
+        List<DelayedRootSetActionItem> delayedRootSetActionItems,
+        ProgramMethodMap<ProgramMethod> pendingMethodMoveInverse) {
       super(
           neverInline,
           neverInlineDueToSingleCaller,
           neverClassInline,
           dependentMinimumKeepInfo,
           dependentKeepClassCompatRule,
-          delayedRootSetActionItems);
+          delayedRootSetActionItems,
+          pendingMethodMoveInverse);
       this.reasonAsked = reasonAsked;
       this.checkDiscarded = checkDiscarded;
       this.alwaysInline = alwaysInline;
@@ -1880,14 +1925,16 @@
         Set<DexType> neverClassInline,
         DependentMinimumKeepInfoCollection dependentMinimumKeepInfo,
         Map<DexType, Set<ProguardKeepRuleBase>> dependentKeepClassCompatRule,
-        List<DelayedRootSetActionItem> delayedRootSetActionItems) {
+        List<DelayedRootSetActionItem> delayedRootSetActionItems,
+        ProgramMethodMap<ProgramMethod> pendingMethodMoveInverse) {
       super(
           neverInline,
           neverInlineDueToSingleCaller,
           neverClassInline,
           dependentMinimumKeepInfo,
           dependentKeepClassCompatRule,
-          delayedRootSetActionItems);
+          delayedRootSetActionItems,
+          pendingMethodMoveInverse);
     }
 
     static ConsequentRootSetBuilder builder(
@@ -1959,7 +2006,8 @@
           emptyMap(),
           Collections.emptySet(),
           ifRules,
-          delayedRootSetActionItems);
+          delayedRootSetActionItems,
+          ProgramMethodMap.empty());
     }
 
     public static MainDexRootSetBuilder builder(
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 71ae785..474d1c3 100644
--- a/src/main/java/com/android/tools/r8/synthesis/SyntheticItems.java
+++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticItems.java
@@ -731,6 +731,28 @@
     return internalEnsureDexProgramClass(kind, fn, onCreationConsumer, outerContext, type, appView);
   }
 
+  public DexClasspathClass ensureFixedClasspathClassFromType(
+      SyntheticKind kind,
+      DexType contextType,
+      AppView<?> appView,
+      Consumer<SyntheticClasspathClassBuilder> fn) {
+    SynthesizingContext outerContext = SynthesizingContext.fromType(contextType);
+    DexType type = SyntheticNaming.createFixedType(kind, outerContext, appView.dexItemFactory());
+    synchronized (contextType) {
+      DexClass clazz = appView.definitionFor(type);
+      if (clazz != null) {
+        assert clazz.isClasspathClass();
+        return clazz.asClasspathClass();
+      }
+      SyntheticClasspathClassBuilder classBuilder =
+          new SyntheticClasspathClassBuilder(type, kind, outerContext, appView.dexItemFactory());
+      fn.accept(classBuilder);
+      DexClasspathClass definition = classBuilder.build();
+      addPendingDefinition(new SyntheticClasspathClassDefinition(kind, outerContext, definition));
+      return definition;
+    }
+  }
+
   /** Create a single synthetic method item. */
   public ProgramMethod createMethod(
       SyntheticKind kind,
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 216fcfc..0a05c0f 100644
--- a/src/main/java/com/android/tools/r8/synthesis/SyntheticNaming.java
+++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticNaming.java
@@ -39,6 +39,7 @@
     HORIZONTAL_INIT_TYPE_ARGUMENT_1(SYNTHETIC_CLASS_SEPARATOR + "IA$1", 6, false, true),
     HORIZONTAL_INIT_TYPE_ARGUMENT_2(SYNTHETIC_CLASS_SEPARATOR + "IA$2", 7, false, true),
     HORIZONTAL_INIT_TYPE_ARGUMENT_3(SYNTHETIC_CLASS_SEPARATOR + "IA$3", 8, false, true),
+    CONST_DYNAMIC("$Condy", 30, false),
     // Method synthetics.
     ENUM_UNBOXING_CHECK_NOT_ZERO_METHOD("CheckNotZero", 27, true),
     RECORD_HELPER("Record", 9, true),
@@ -53,7 +54,8 @@
     SERVICE_LOADER("ServiceLoad", 18, true),
     OUTLINE("Outline", 19, true),
     API_CONVERSION("APIConversion", 26, true),
-    API_CONVERSION_PARAMETERS("APIConversionParameters", 28, true);
+    API_CONVERSION_PARAMETERS("APIConversionParameters", 28, true),
+    EMULATED_INTERFACE_MARKER_CLASS("", 29, false, true, true);
 
     static {
       assert verifyNoOverlappingIds();
diff --git a/src/main/java/com/android/tools/r8/utils/DesugarUtils.java b/src/main/java/com/android/tools/r8/utils/DesugarUtils.java
new file mode 100644
index 0000000..7b8fba2
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/utils/DesugarUtils.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.utils;
+
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexString;
+
+public class DesugarUtils {
+  public static DexString appendFullyQualifiedHolderToMethodName(
+      DexMethod method, DexItemFactory factory) {
+    return factory.createString(
+        method.name.toString() + "$" + method.holder.getTypeName().replace('.', '-'));
+  }
+}
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 f391fb1..e0a5e76 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -514,15 +514,7 @@
     return !canUseNestBasedAccess();
   }
 
-  public boolean enableExperimentalRecordDesugaring() {
-    // TODO(b/169645628): Remove when records are supported.
-    return testing.enableExperimentalRecordDesugaring;
-  }
-
   public boolean shouldDesugarRecords() {
-    if (!enableExperimentalRecordDesugaring()) {
-      return false;
-    }
     return desugarState.isOn() && !canUseRecords();
   }
 
@@ -1222,15 +1214,15 @@
     // TODO(b/69963623): enable if everything is ready, including signature rewriting at call sites.
     private boolean enableConstantPropagation = false;
     private boolean enableExperimentalArgumentPropagation = false;
-    private boolean enableTypePropagation = true;
+    private boolean enableDynamicTypePropagation = true;
 
     public void disableOptimization() {
       enableConstantPropagation = false;
-      enableTypePropagation = false;
+      enableDynamicTypePropagation = false;
     }
 
-    public void disableTypePropagationForTesting() {
-      enableTypePropagation = false;
+    public void disableDynamicTypePropagationForTesting() {
+      enableDynamicTypePropagation = false;
     }
 
     public int getMaxNumberOfDispatchTargetsBeforeAbandoning() {
@@ -1241,7 +1233,7 @@
       if (!isOptimizing()) {
         return false;
       }
-      return enableConstantPropagation || enableTypePropagation;
+      return enableConstantPropagation || enableDynamicTypePropagation;
     }
 
     public boolean isExperimentalArgumentPropagationEnabled() {
@@ -1252,8 +1244,8 @@
       return enableConstantPropagation;
     }
 
-    public boolean isTypePropagationEnabled() {
-      return enableTypePropagation;
+    public boolean isDynamicTypePropagationEnabled() {
+      return enableDynamicTypePropagation;
     }
 
     public void setEnableConstantPropagation() {
@@ -1261,9 +1253,10 @@
       enableConstantPropagation = true;
     }
 
-    public void setEnableExperimentalArgumentPropagation(
+    public CallSiteOptimizationOptions setEnableExperimentalArgumentPropagation(
         boolean enableExperimentalArgumentPropagation) {
       this.enableExperimentalArgumentPropagation = enableExperimentalArgumentPropagation;
+      return this;
     }
   }
 
@@ -1552,7 +1545,6 @@
     public boolean enableEnumUnboxingDebugLogs = false;
     public boolean forceRedundantConstNumberRemoval = false;
     public boolean enableExperimentalDesugaredLibraryKeepRuleGenerator = false;
-    public boolean enableExperimentalRecordDesugaring = false;
     public boolean invertConditionals = false;
     public boolean placeExceptionalBlocksLast = false;
     public boolean dontCreateMarkerInD8 = false;
@@ -1639,7 +1631,8 @@
       public int numberOfProguardIfRuleMemberEvaluations = 0;
     }
 
-    public Consumer<ProgramMethod> callSiteOptimizationInfoInspector = null;
+    public Consumer<ProgramMethod> callSiteOptimizationInfoInspector =
+        ConsumerUtils.emptyConsumer();
 
     public Predicate<DexMethod> cfByteCodePassThrough = null;
 
diff --git a/src/main/java/com/android/tools/r8/utils/ListUtils.java b/src/main/java/com/android/tools/r8/utils/ListUtils.java
index 089154e..891f1c2 100644
--- a/src/main/java/com/android/tools/r8/utils/ListUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/ListUtils.java
@@ -25,7 +25,7 @@
    * as the singleton list containing {@code v} (i.e., no changes should be made to the given
    * element).
    */
-  public static <T> List<T> flatMap(
+  public static <T> List<T> flatMapSameType(
       List<T> list, Function<T, Collection<T>> fn, List<T> defaultValue) {
     List<T> result = null;
     for (int i = 0; i < list.size(); i++) {
@@ -48,6 +48,12 @@
     return result != null ? result : defaultValue;
   }
 
+  public static <S, T> List<T> flatMap(List<S> list, Function<S, Collection<T>> fn) {
+    List<T> result = new ArrayList<>();
+    list.forEach(element -> result.addAll(fn.apply(element)));
+    return result;
+  }
+
   public static <T> List<T> filter(List<T> list, Predicate<? super T> predicate) {
     ArrayList<T> filtered = new ArrayList<>(list.size());
     list.forEach(
diff --git a/src/main/java/com/android/tools/r8/utils/ZipUtils.java b/src/main/java/com/android/tools/r8/utils/ZipUtils.java
index e7b9b00..da688a8 100644
--- a/src/main/java/com/android/tools/r8/utils/ZipUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/ZipUtils.java
@@ -102,19 +102,24 @@
       throws IOException {
     try (ZipOutputStream stream =
         new ZipOutputStream(new BufferedOutputStream(Files.newOutputStream(zipFile)))) {
-      for (Path path : filesToZip) {
-        ZipEntry zipEntry =
-            new ZipEntry(
-                StreamSupport.stream(
-                        Spliterators.spliteratorUnknownSize(
-                            basePath.relativize(path).iterator(), Spliterator.ORDERED),
-                        false)
-                    .map(Path::toString)
-                    .collect(Collectors.joining("/")));
-        stream.putNextEntry(zipEntry);
-        Files.copy(path, stream);
-        stream.closeEntry();
-      }
+      zip(stream, basePath, filesToZip);
+    }
+  }
+
+  public static void zip(ZipOutputStream stream, Path basePath, Collection<Path> filesToZip)
+      throws IOException {
+    for (Path path : filesToZip) {
+      ZipEntry zipEntry =
+          new ZipEntry(
+              StreamSupport.stream(
+                      Spliterators.spliteratorUnknownSize(
+                          basePath.relativize(path).iterator(), Spliterator.ORDERED),
+                      false)
+                  .map(Path::toString)
+                  .collect(Collectors.joining("/")));
+      stream.putNextEntry(zipEntry);
+      Files.copy(path, stream);
+      stream.closeEntry();
     }
   }
 
diff --git a/src/test/examplesAndroidO/invokecustom/TestGenerator.java b/src/test/examplesAndroidO/invokecustom/TestGenerator.java
index 9964ed0..a4cd89a 100644
--- a/src/test/examplesAndroidO/invokecustom/TestGenerator.java
+++ b/src/test/examplesAndroidO/invokecustom/TestGenerator.java
@@ -21,6 +21,8 @@
 import org.objectweb.asm.Opcodes;
 import org.objectweb.asm.Type;
 
+// TODO(b/167145686): Migrate these tests to the new setup ala
+//  InvokeDynamicVirtualDispatchToDefaultInterfaceMethodTest
 public class TestGenerator {
 
   private final Path classNamePath;
@@ -52,7 +54,6 @@
               generateMethodTest6(cw);
               generateMethodTest7(cw);
               generateMethodTest8(cw);
-              generateMethodTest9(cw);
               generateMethodTest10(cw);
               generateMethodTest11(cw);
               generateMethodTest12(cw);
@@ -89,8 +90,6 @@
     mv.visitMethodInsn(
         Opcodes.INVOKESTATIC, Type.getInternalName(InvokeCustom.class), "test8", "()V", false);
     mv.visitMethodInsn(
-        Opcodes.INVOKESTATIC, Type.getInternalName(InvokeCustom.class), "test9", "()V", false);
-    mv.visitMethodInsn(
         Opcodes.INVOKESTATIC, Type.getInternalName(InvokeCustom.class), "test10", "()V", false);
     mv.visitMethodInsn(
         Opcodes.INVOKESTATIC, Type.getInternalName(InvokeCustom.class), "test11", "()V", false);
@@ -280,29 +279,6 @@
 
   /**
    *  Generate test with an invokedynamic, a static bootstrap method with an extra arg that is a
-   *  MethodHandle of kind invoke virtual. The target method is a method into a class implementing
-   *  an abstract method and that shadows a default method from an interface.
-   */
-  private void generateMethodTest9(ClassVisitor cv) {
-    MethodVisitor mv = cv.visitMethod(Opcodes.ACC_PUBLIC + Opcodes.ACC_STATIC, "test9", "()V",
-        null, null);
-    MethodType mt = MethodType.methodType(CallSite.class, MethodHandles.Lookup.class, String.class,
-        MethodType.class, MethodHandle.class);
-    Handle bootstrap = new Handle(Opcodes.H_INVOKESTATIC, Type.getInternalName(InvokeCustom.class),
-        "bsmCreateCallSite", mt.toMethodDescriptorString(), false);
-    mv.visitTypeInsn(Opcodes.NEW, Type.getInternalName(InvokeCustom.class));
-    mv.visitInsn(Opcodes.DUP);
-    mv.visitMethodInsn(
-        Opcodes.INVOKESPECIAL, Type.getInternalName(InvokeCustom.class), "<init>", "()V", false);
-    mv.visitInvokeDynamicInsn("targetMethodTest10", "(Linvokecustom/InvokeCustom;)V", bootstrap,
-        new Handle(Opcodes.H_INVOKEVIRTUAL, Type.getInternalName(InvokeCustom.class),
-            "targetMethodTest10", "()V", false));
-    mv.visitInsn(Opcodes.RETURN);
-    mv.visitMaxs(-1, -1);
-  }
-
-  /**
-   *  Generate test with an invokedynamic, a static bootstrap method with an extra arg that is a
    *  MethodHandle of kind get static. The method handle read a static field from a class.
    */
   private void generateMethodTest10(ClassVisitor cv) {
diff --git a/src/test/java/com/android/tools/r8/CompileWithJdkClassFileProviderTest.java b/src/test/java/com/android/tools/r8/CompileWithJdkClassFileProviderTest.java
index 848c566..0637a0b 100644
--- a/src/test/java/com/android/tools/r8/CompileWithJdkClassFileProviderTest.java
+++ b/src/test/java/com/android/tools/r8/CompileWithJdkClassFileProviderTest.java
@@ -1,8 +1,8 @@
 package com.android.tools.r8;
 
+import static com.android.tools.r8.DiagnosticsMatcher.diagnosticMessage;
 import static org.hamcrest.CoreMatchers.anyOf;
 import static org.hamcrest.CoreMatchers.containsString;
-import static org.hamcrest.MatcherAssert.assertThat;
 import static org.junit.Assert.fail;
 import static org.junit.Assume.assumeTrue;
 
@@ -10,7 +10,6 @@
 import com.android.tools.r8.TestRuntime.CfVm;
 import com.android.tools.r8.utils.AndroidApiLevel;
 import java.util.List;
-import org.hamcrest.core.StringContains;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
@@ -81,6 +80,7 @@
 
     TestShrinkerBuilder<?, ?, ?, ?, ?> testBuilder =
         testForR8(parameters.getBackend())
+            .setMinApi(AndroidApiLevel.B)
             .addLibraryProvider(provider)
             .addProgramClassFileData(dumpClassWhichUseJava9Flow())
             .addKeepMainRule("MySubscriber");
@@ -90,11 +90,18 @@
         // java.util.concurrent.Flow$Subscriber is not present in JDK8 rt.jar.
         testBuilder.compileWithExpectedDiagnostics(
             diagnostics -> {
-              diagnostics.assertOnlyErrors();
-              diagnostics.assertErrorsCount(1);
-              assertThat(
-                  diagnostics.getErrors().get(0).getDiagnosticMessage(),
-                  StringContains.containsString("java.util.concurrent.Flow$Subscriber"));
+              diagnostics.assertErrorsMatch(
+                  diagnosticMessage(containsString("java.util.concurrent.Flow$Subscriber")));
+              if (parameters.isCfRuntime()) {
+                diagnostics.assertOnlyErrors();
+              } else {
+                // TODO(b/198368663): R8 will double report missing classes in itf desugaring.
+                diagnostics.assertWarningsMatch(
+                    diagnosticMessage(containsString("java.util.concurrent.Flow$Subscriber")));
+                diagnostics.assertErrorsCount(1);
+                diagnostics.assertWarningsCount(1);
+                diagnostics.assertInfosCount(0);
+              }
             });
       } catch (CompilationFailedException e) {
         return;
diff --git a/src/test/java/com/android/tools/r8/R8RunArtTestsTest.java b/src/test/java/com/android/tools/r8/R8RunArtTestsTest.java
index f58f1fd..fbf3878 100644
--- a/src/test/java/com/android/tools/r8/R8RunArtTestsTest.java
+++ b/src/test/java/com/android/tools/r8/R8RunArtTestsTest.java
@@ -644,6 +644,15 @@
   // Tests where the output of R8 fails when run with Art.
   private static final Multimap<String, TestCondition> failingRunWithArt =
       new ImmutableListMultimap.Builder<String, TestCondition>()
+          // The itf cache issue is hit on dx inputs on the runtime with the issue as we no longer
+          // desugar.
+          // TODO(b/198306901): Investigate this behavior change fully. Do we need a workaround?
+          .put(
+              "666-dex-cache-itf",
+              TestCondition.match(
+                  TestCondition.tools(DexTool.DX),
+                  TestCondition.compilers(CompilerUnderTest.R8),
+                  TestCondition.runtimes(DexVm.Version.DEFAULT)))
           // The growth limit test fails after processing by R8 because R8 will eliminate an
           // "unneeded" const store. The following reflective call to the VM's GC will then see the
           // large array as still live and the subsequent allocations will fail to reach the desired
diff --git a/src/test/java/com/android/tools/r8/R8RunExamplesAndroidOTest.java b/src/test/java/com/android/tools/r8/R8RunExamplesAndroidOTest.java
index a92437f..11b41cc 100644
--- a/src/test/java/com/android/tools/r8/R8RunExamplesAndroidOTest.java
+++ b/src/test/java/com/android/tools/r8/R8RunExamplesAndroidOTest.java
@@ -200,7 +200,7 @@
             b ->
                 b.addProguardConfiguration(
                     getProguardOptionsNPlus(enableProguardCompatibilityMode), Origin.unknown()))
-        .withDexCheck(inspector -> checkLambdaCount(inspector, 3, "lambdadesugaringnplus"))
+        .withDexCheck(inspector -> checkLambdaCount(inspector, 2, "lambdadesugaringnplus"))
         .run();
   }
 
diff --git a/src/test/java/com/android/tools/r8/TestAppViewBuilder.java b/src/test/java/com/android/tools/r8/TestAppViewBuilder.java
new file mode 100644
index 0000000..d5ef230
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/TestAppViewBuilder.java
@@ -0,0 +1,129 @@
+// 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;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.shaking.ProguardConfigurationRule;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.AndroidApp;
+import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.ListUtils;
+import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.List;
+import java.util.function.Consumer;
+import java.util.function.Function;
+
+public class TestAppViewBuilder {
+
+  private AndroidApp.Builder builder = AndroidApp.builder();
+  private List<Function<DexItemFactory, List<ProguardConfigurationRule>>> rules = new ArrayList<>();
+  private List<Consumer<InternalOptions>> optionModifications = new ArrayList<>();
+
+  public static TestAppViewBuilder builder() {
+    return new TestAppViewBuilder();
+  }
+
+  private TestAppViewBuilder() {}
+
+  public TestAppViewBuilder addProgramClasses(Class<?>... classes) {
+    return addProgramClasses(Arrays.asList(classes));
+  }
+
+  public TestAppViewBuilder addProgramClasses(Collection<Class<?>> classes) {
+    classes.forEach(clazz -> builder.addProgramFile(ToolHelper.getClassFileForTestClass(clazz)));
+    return this;
+  }
+
+  public TestAppViewBuilder addProgramClassFileData(byte[]... classes) {
+    return addProgramClassFileData(Arrays.asList(classes));
+  }
+
+  public TestAppViewBuilder addProgramClassFileData(Collection<byte[]> classes) {
+    builder.addClassProgramData(classes);
+    return this;
+  }
+
+  public TestAppViewBuilder addAndroidApp(AndroidApp app) {
+    app.getProgramResourceProviders().forEach(builder::addProgramResourceProvider);
+    app.getClasspathResourceProviders().forEach(builder::addClasspathResourceProvider);
+    app.getLibraryResourceProviders().forEach(builder::addLibraryResourceProvider);
+    assert !app.hasMainDexList() : "todo";
+    return this;
+  }
+
+  public TestAppViewBuilder addKeepAllRule() {
+    rules = null;
+    return this;
+  }
+
+  public TestAppViewBuilder addKeepMainRule(Class<?> mainClass) {
+    return addKeepRuleBuilder(
+        factory -> TestBase.buildKeepRuleForClassAndMethods(mainClass, factory));
+  }
+
+  public TestAppViewBuilder addKeepRuleBuilder(
+      Function<DexItemFactory, List<ProguardConfigurationRule>> ruleBuilder) {
+    if (rules != null) {
+      rules.add(ruleBuilder);
+    }
+    return this;
+  }
+
+  public TestAppViewBuilder addOptionsModification(Consumer<InternalOptions> optionsConsumer) {
+    optionModifications.add(optionsConsumer);
+    return this;
+  }
+
+  public AppView<AppInfoWithLiveness> buildWithLiveness() throws Exception {
+    return TestBase.computeAppViewWithLiveness(
+        builder.build(),
+        (rules == null
+            ? null
+            : factory ->
+                TestBase.buildConfigForRules(
+                    factory, ListUtils.flatMap(rules, r -> r.apply(factory)))),
+        options -> optionModifications.forEach(consumer -> consumer.accept(options)));
+  }
+
+  public TestAppViewBuilder setMinApi(AndroidApiLevel minApi) {
+    optionModifications.add(options -> options.minApiLevel = minApi);
+    return this;
+  }
+
+  public TestAppViewBuilder addClasspathClasses(Class<?>... classes) {
+    return addClasspathClasses(Arrays.asList(classes));
+  }
+
+  public TestAppViewBuilder addClasspathClasses(Collection<Class<?>> classes) {
+    classes.forEach(clazz -> addClasspathFiles(ToolHelper.getClassFileForTestClass(clazz)));
+    return this;
+  }
+
+  public TestAppViewBuilder addClasspathFiles(Path... files) {
+    return addClasspathFiles(Arrays.asList(files));
+  }
+
+  public TestAppViewBuilder addClasspathFiles(List<Path> files) {
+    builder.addClasspathFiles(files);
+    return this;
+  }
+
+  public TestAppViewBuilder addLibraryFiles(Path... files) {
+    return addLibraryFiles(Arrays.asList(files));
+  }
+
+  public TestAppViewBuilder addLibraryFiles(List<Path> files) {
+    builder.addLibraryFiles(files);
+    return this;
+  }
+
+  public TestAppViewBuilder addTestingAnnotations() {
+    return addProgramClasses(TestBuilder.getTestingAnnotations());
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/TestBase.java b/src/test/java/com/android/tools/r8/TestBase.java
index f93398c..2ccb378 100644
--- a/src/test/java/com/android/tools/r8/TestBase.java
+++ b/src/test/java/com/android/tools/r8/TestBase.java
@@ -8,7 +8,9 @@
 import static com.android.tools.r8.ToolHelper.R8_TEST_BUCKET;
 import static com.android.tools.r8.utils.InternalOptions.ASM_VERSION;
 import static com.google.common.collect.Lists.cartesianProduct;
+import static com.google.common.io.ByteStreams.toByteArray;
 import static org.hamcrest.CoreMatchers.containsString;
+import static org.junit.Assert.assertArrayEquals;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
 
@@ -93,6 +95,7 @@
 import com.google.common.collect.Lists;
 import com.google.common.collect.Sets;
 import com.google.common.io.ByteStreams;
+import java.io.BufferedInputStream;
 import java.io.File;
 import java.io.FileInputStream;
 import java.io.FileOutputStream;
@@ -775,17 +778,18 @@
 
   protected static AppView<AppInfoWithLiveness> computeAppViewWithLiveness(AndroidApp app)
       throws Exception {
-    return computeAppViewWithLiveness(app, null, null);
+    return TestAppViewBuilder.builder().addAndroidApp(app).addKeepAllRule().buildWithLiveness();
   }
 
   protected static AppView<AppInfoWithLiveness> computeAppViewWithLiveness(
       AndroidApp app, Class<?> mainClass) throws Exception {
-    return computeAppViewWithLiveness(
-        app,
-        factory ->
-            buildConfigForRules(factory, buildKeepRuleForClassAndMethods(mainClass, factory)));
+    return TestAppViewBuilder.builder()
+        .addAndroidApp(app)
+        .addKeepMainRule(mainClass)
+        .buildWithLiveness();
   }
 
+  // We should try to get rid of this usage of keep rule building which is very internal.
   protected static AppView<AppInfoWithLiveness> computeAppViewWithLiveness(
       AndroidApp app, Function<DexItemFactory, ProguardConfiguration> keepConfig) throws Exception {
     return computeAppViewWithLiveness(app, keepConfig, null);
@@ -1912,4 +1916,54 @@
     }
     return false;
   }
+
+  public static boolean assertProgramsEqual(Path expectedJar, Path actualJar) throws Exception {
+    if (filesAreEqual(expectedJar, actualJar)) {
+      return true;
+    }
+    ArchiveClassFileProvider expected = new ArchiveClassFileProvider(expectedJar);
+    ArchiveClassFileProvider actual = new ArchiveClassFileProvider(actualJar);
+    assertEquals(getSortedDescriptorList(expected), getSortedDescriptorList(actual));
+    for (String descriptor : expected.getClassDescriptors()) {
+      assertArrayEquals(
+          "Class " + descriptor + " differs",
+          getClassAsBytes(expected, descriptor),
+          getClassAsBytes(actual, descriptor));
+    }
+    return false;
+  }
+
+  public static boolean filesAreEqual(Path file1, Path file2) throws IOException {
+    long size = Files.size(file1);
+    long sizeOther = Files.size(file2);
+    if (size != sizeOther) {
+      return false;
+    }
+    if (size < 4096) {
+      return Arrays.equals(Files.readAllBytes(file1), Files.readAllBytes(file2));
+    }
+    int byteRead1 = 0;
+    int byteRead2 = 0;
+    try (FileInputStream fs1 = new FileInputStream(file1.toString());
+        FileInputStream fs2 = new FileInputStream(file2.toString())) {
+      BufferedInputStream bs1 = new BufferedInputStream(fs1);
+      BufferedInputStream bs2 = new BufferedInputStream(fs2);
+      while (byteRead1 == byteRead2 && byteRead1 != -1) {
+        byteRead1 = bs1.read();
+        byteRead2 = bs2.read();
+      }
+    }
+    return byteRead1 == byteRead2;
+  }
+
+  private static List<String> getSortedDescriptorList(ArchiveClassFileProvider inputJar) {
+    ArrayList<String> descriptorList = new ArrayList<>(inputJar.getClassDescriptors());
+    Collections.sort(descriptorList);
+    return descriptorList;
+  }
+
+  private static byte[] getClassAsBytes(ArchiveClassFileProvider inputJar, String descriptor)
+      throws Exception {
+    return toByteArray(inputJar.getProgramResource(descriptor).getByteStream());
+  }
 }
diff --git a/src/test/java/com/android/tools/r8/TestParameters.java b/src/test/java/com/android/tools/r8/TestParameters.java
index 1df89b1..7e230ef 100644
--- a/src/test/java/com/android/tools/r8/TestParameters.java
+++ b/src/test/java/com/android/tools/r8/TestParameters.java
@@ -31,6 +31,8 @@
 
   public boolean canUseDefaultAndStaticInterfaceMethods() {
     assert isCfRuntime() || isDexRuntime();
+    assert !isCfRuntime() || apiLevel == null
+        : "Use canUseDefaultAndStaticInterfaceMethodsWhenDesugaring when using CF api levels.";
     return isCfRuntime()
         || getApiLevel()
             .isGreaterThanOrEqualTo(TestBase.apiLevelWithDefaultInterfaceMethodsSupport());
diff --git a/src/test/java/com/android/tools/r8/TestShrinkerBuilder.java b/src/test/java/com/android/tools/r8/TestShrinkerBuilder.java
index 88738f0..fe6cc31 100644
--- a/src/test/java/com/android/tools/r8/TestShrinkerBuilder.java
+++ b/src/test/java/com/android/tools/r8/TestShrinkerBuilder.java
@@ -106,6 +106,10 @@
     return addKeepRules("-dontoptimize");
   }
 
+  public T addDontShrink() {
+    return addKeepRules("-dontshrink");
+  }
+
   public T addDontWarn(Class<?>... classes) {
     for (Class<?> clazz : classes) {
       addDontWarn(clazz.getTypeName());
diff --git a/src/test/java/com/android/tools/r8/apimodel/AndroidApiDatabaseBuilderGeneratorTest.java b/src/test/java/com/android/tools/r8/apimodel/AndroidApiDatabaseBuilderGeneratorTest.java
index 8b3571a..91accca 100644
--- a/src/test/java/com/android/tools/r8/apimodel/AndroidApiDatabaseBuilderGeneratorTest.java
+++ b/src/test/java/com/android/tools/r8/apimodel/AndroidApiDatabaseBuilderGeneratorTest.java
@@ -18,7 +18,6 @@
 import com.android.tools.r8.TestState;
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.apimodel.AndroidApiVersionsXmlParser.ParsedApiClass;
-import com.android.tools.r8.cf.bootstrap.BootstrapCurrentEqualityTest;
 import com.android.tools.r8.references.ClassReference;
 import com.android.tools.r8.references.Reference;
 import com.android.tools.r8.utils.AndroidApiLevel;
@@ -116,7 +115,7 @@
 
   @Test
   public void testDatabaseGenerationUpToDate() throws Exception {
-    BootstrapCurrentEqualityTest.filesAreEqual(generateJar(), API_DATABASE_JAR);
+    TestBase.filesAreEqual(generateJar(), API_DATABASE_JAR);
   }
 
   /**
diff --git a/src/test/java/com/android/tools/r8/apimodel/ApiModelNoInliningOfDefaultInterfaceMethodsTest.java b/src/test/java/com/android/tools/r8/apimodel/ApiModelNoInliningOfDefaultInterfaceMethodsTest.java
index e997894..a580fb1 100644
--- a/src/test/java/com/android/tools/r8/apimodel/ApiModelNoInliningOfDefaultInterfaceMethodsTest.java
+++ b/src/test/java/com/android/tools/r8/apimodel/ApiModelNoInliningOfDefaultInterfaceMethodsTest.java
@@ -6,7 +6,7 @@
 
 import static com.android.tools.r8.apimodel.ApiModelingTestHelper.setMockApiLevelForMethod;
 import static com.android.tools.r8.utils.AndroidApiLevel.L_MR1;
-import static com.android.tools.r8.utils.codeinspector.Matchers.isAbstract;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isAbsent;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
 import static junit.framework.TestCase.assertEquals;
 import static org.hamcrest.MatcherAssert.assertThat;
@@ -59,22 +59,22 @@
               } else {
                 ClassSubject aSubject = inspector.clazz(A.class);
                 ClassSubject apiCaller = inspector.clazz(ApiCaller.class);
-                assertThat(apiCaller, isPresent());
-                MethodSubject callApiLevel = apiCaller.uniqueMethodWithName("callApiLevel");
                 if (parameters.isCfRuntime()) {
+                  assert parameters.canUseDefaultAndStaticInterfaceMethods();
+                  assertThat(apiCaller, isPresent());
                   assertThat(aSubject, isPresent());
+                  MethodSubject callApiLevel = apiCaller.uniqueMethodWithName("callApiLevel");
                   assertThat(callApiLevel, CodeMatchers.invokesMethodWithName("apiLevel22"));
                 } else {
                   assert parameters.isDexRuntime();
-                  // TODO(b/191013385): A has a virtual method that calls callApiLevel on $CC, but
-                  //  that call should be inlined.
-                  assertThat(aSubject, isPresent());
-                  assertThat(callApiLevel, isAbstract());
-                  ClassSubject classSubject = apiCaller.toCompanionClass();
-                  assertThat(classSubject, isPresent());
-                  assertEquals(1, classSubject.allMethods().size());
+                  assert !parameters.canUseDefaultAndStaticInterfaceMethods();
+                  assertThat(apiCaller, isAbsent());
+                  assertThat(aSubject, isAbsent());
+                  ClassSubject companionClass = apiCaller.toCompanionClass();
+                  assertThat(companionClass, isPresent());
+                  assertEquals(1, companionClass.allMethods().size());
                   assertThat(
-                      classSubject.allMethods().get(0),
+                      companionClass.allMethods().get(0),
                       CodeMatchers.invokesMethodWithName("apiLevel22"));
                 }
               }
diff --git a/src/test/java/com/android/tools/r8/apimodel/ApiModelNoInliningOfLambdaTest.java b/src/test/java/com/android/tools/r8/apimodel/ApiModelNoInliningOfLambdaTest.java
index 0525cd5..04dc573 100644
--- a/src/test/java/com/android/tools/r8/apimodel/ApiModelNoInliningOfLambdaTest.java
+++ b/src/test/java/com/android/tools/r8/apimodel/ApiModelNoInliningOfLambdaTest.java
@@ -18,6 +18,7 @@
 import com.android.tools.r8.utils.codeinspector.CodeMatchers;
 import com.android.tools.r8.utils.codeinspector.MethodSubject;
 import java.lang.reflect.Method;
+import org.junit.Assume;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
@@ -47,6 +48,7 @@
 
   @Test
   public void testR8() throws Exception {
+    Assume.assumeTrue("b/197494749", parameters.canUseDefaultAndStaticInterfaceMethods());
     Method apiMethod = Api.class.getDeclaredMethod("apiLevel22");
     testForR8(parameters.getBackend())
         .addProgramClasses(ApiCaller.class, Action.class, Main.class)
@@ -57,7 +59,6 @@
         .apply(setMockApiLevelForMethod(apiMethod, L_MR1))
         .apply(ApiModelingTestHelper::enableApiCallerIdentification)
         .allowAccessModification()
-        .noMinification()
         .compile()
         .inspect(
             inspector -> {
diff --git a/src/test/java/com/android/tools/r8/apimodel/ApiModelNoInliningOfStaticInterfaceMethodsTest.java b/src/test/java/com/android/tools/r8/apimodel/ApiModelNoInliningOfStaticInterfaceMethodsTest.java
index 1e2fd44..086cfa4 100644
--- a/src/test/java/com/android/tools/r8/apimodel/ApiModelNoInliningOfStaticInterfaceMethodsTest.java
+++ b/src/test/java/com/android/tools/r8/apimodel/ApiModelNoInliningOfStaticInterfaceMethodsTest.java
@@ -30,7 +30,7 @@
 
   @Parameters(name = "{0}")
   public static TestParametersCollection data() {
-    return getTestParameters().withAllRuntimes().withAllApiLevelsAlsoForCf().build();
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
   }
 
   public ApiModelNoInliningOfStaticInterfaceMethodsTest(TestParameters parameters) {
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 e41bf8d..1a6717f 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
@@ -5,15 +5,12 @@
 
 import static com.android.tools.r8.graph.GenericSignatureIdentityTest.testParseSignaturesInJar;
 import static com.android.tools.r8.utils.FileUtils.JAR_EXTENSION;
-import static com.google.common.io.ByteStreams.toByteArray;
 import static org.hamcrest.CoreMatchers.containsString;
 import static org.hamcrest.CoreMatchers.not;
 import static org.hamcrest.MatcherAssert.assertThat;
-import static org.junit.Assert.assertArrayEquals;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotEquals;
 
-import com.android.tools.r8.ArchiveClassFileProvider;
 import com.android.tools.r8.CompilationMode;
 import com.android.tools.r8.ExternalR8TestCompileResult;
 import com.android.tools.r8.TestBase;
@@ -29,15 +26,9 @@
 import com.android.tools.r8.utils.Pair;
 import com.android.tools.r8.utils.StringUtils;
 import com.google.common.collect.Lists;
-import java.io.BufferedInputStream;
-import java.io.FileInputStream;
 import java.io.IOException;
-import java.nio.file.Files;
 import java.nio.file.Path;
 import java.nio.file.Paths;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collections;
 import java.util.List;
 import org.junit.BeforeClass;
 import org.junit.ClassRule;
@@ -183,8 +174,7 @@
             .setMode(CompilationMode.RELEASE)
             .compile()
             .outputJar();
-    assert uploadJarsToCloudStorageIfTestFails(
-        BootstrapCurrentEqualityTest::filesAreEqual, runR81, runR82);
+    assert uploadJarsToCloudStorageIfTestFails(TestBase::filesAreEqual, runR81, runR82);
   }
 
   @Test
@@ -247,56 +237,6 @@
         BootstrapCurrentEqualityTest::assertProgramsEqual, result.outputJar(), runR8R8.outputJar());
   }
 
-  public static boolean assertProgramsEqual(Path expectedJar, Path actualJar) throws Exception {
-    if (filesAreEqual(expectedJar, actualJar)) {
-      return true;
-    }
-    ArchiveClassFileProvider expected = new ArchiveClassFileProvider(expectedJar);
-    ArchiveClassFileProvider actual = new ArchiveClassFileProvider(actualJar);
-    assertEquals(getSortedDescriptorList(expected), getSortedDescriptorList(actual));
-    for (String descriptor : expected.getClassDescriptors()) {
-      assertArrayEquals(
-          "Class " + descriptor + " differs",
-          getClassAsBytes(expected, descriptor),
-          getClassAsBytes(actual, descriptor));
-    }
-    return false;
-  }
-
-  public static boolean filesAreEqual(Path file1, Path file2) throws IOException {
-    long size = Files.size(file1);
-    long sizeOther = Files.size(file2);
-    if (size != sizeOther) {
-      return false;
-    }
-    if (size < 4096) {
-      return Arrays.equals(Files.readAllBytes(file1), Files.readAllBytes(file2));
-    }
-    int byteRead1 = 0;
-    int byteRead2 = 0;
-    try (FileInputStream fs1 = new FileInputStream(file1.toString());
-        FileInputStream fs2 = new FileInputStream(file2.toString())) {
-      BufferedInputStream bs1 = new BufferedInputStream(fs1);
-      BufferedInputStream bs2 = new BufferedInputStream(fs2);
-      while (byteRead1 == byteRead2 && byteRead1 != -1) {
-        byteRead1 = bs1.read();
-        byteRead2 = bs2.read();
-      }
-    }
-    return byteRead1 == byteRead2;
-  }
-
-  private static List<String> getSortedDescriptorList(ArchiveClassFileProvider inputJar) {
-    ArrayList<String> descriptorList = new ArrayList<>(inputJar.getClassDescriptors());
-    Collections.sort(descriptorList);
-    return descriptorList;
-  }
-
-  private static byte[] getClassAsBytes(ArchiveClassFileProvider inputJar, String descriptor)
-      throws Exception {
-    return toByteArray(inputJar.getProgramResource(descriptor).getByteStream());
-  }
-
   private static TemporaryFolder newTempFolder() throws IOException {
     TemporaryFolder tempFolder = new TemporaryFolder(testFolder.newFolder());
     tempFolder.create();
diff --git a/src/test/java/com/android/tools/r8/cf/bootstrap/BootstrapTest.java b/src/test/java/com/android/tools/r8/cf/bootstrap/BootstrapTest.java
index 7dd698b..409236d 100644
--- a/src/test/java/com/android/tools/r8/cf/bootstrap/BootstrapTest.java
+++ b/src/test/java/com/android/tools/r8/cf/bootstrap/BootstrapTest.java
@@ -4,11 +4,8 @@
 package com.android.tools.r8.cf.bootstrap;
 
 import static com.android.tools.r8.utils.FileUtils.JAR_EXTENSION;
-import static com.google.common.io.ByteStreams.toByteArray;
-import static org.junit.Assert.assertArrayEquals;
 import static org.junit.Assert.assertEquals;
 
-import com.android.tools.r8.ArchiveClassFileProvider;
 import com.android.tools.r8.ClassFileConsumer;
 import com.android.tools.r8.CompilationMode;
 import com.android.tools.r8.R8Command;
@@ -21,9 +18,6 @@
 import com.google.common.base.Charsets;
 import java.nio.file.Path;
 import java.nio.file.Paths;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
@@ -153,27 +147,4 @@
     String pgMap = FileUtils.readTextFile(pgMapFile, Charsets.UTF_8);
     return new R8Result(processResult, outputJar, pgMap);
   }
-
-  private static void assertProgramsEqual(Path expectedJar, Path actualJar) throws Exception {
-    ArchiveClassFileProvider expected = new ArchiveClassFileProvider(expectedJar);
-    ArchiveClassFileProvider actual = new ArchiveClassFileProvider(actualJar);
-    assertEquals(getSortedDescriptorList(expected), getSortedDescriptorList(actual));
-    for (String descriptor : expected.getClassDescriptors()) {
-      assertArrayEquals(
-          "Class " + descriptor + " differs",
-          getClassAsBytes(expected, descriptor),
-          getClassAsBytes(actual, descriptor));
-    }
-  }
-
-  private static List<String> getSortedDescriptorList(ArchiveClassFileProvider inputJar) {
-    ArrayList<String> descriptorList = new ArrayList<>(inputJar.getClassDescriptors());
-    Collections.sort(descriptorList);
-    return descriptorList;
-  }
-
-  private static byte[] getClassAsBytes(ArchiveClassFileProvider inputJar, String descriptor)
-      throws Exception {
-    return toByteArray(inputJar.getProgramResource(descriptor).getByteStream());
-  }
 }
diff --git a/src/test/java/com/android/tools/r8/classmerging/KeptTargetsIncompleteDiamondTest.java b/src/test/java/com/android/tools/r8/classmerging/KeptTargetsIncompleteDiamondTest.java
index e7c5ce1..51e4d41 100644
--- a/src/test/java/com/android/tools/r8/classmerging/KeptTargetsIncompleteDiamondTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/KeptTargetsIncompleteDiamondTest.java
@@ -7,6 +7,7 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
 
+import com.android.tools.r8.TestAppViewBuilder;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
@@ -20,6 +21,7 @@
 import com.android.tools.r8.graph.MethodResolutionResult;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.shaking.ProguardConfigurationRule;
+import com.android.tools.r8.utils.AndroidApiLevel;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableSet;
 import java.util.HashSet;
@@ -43,18 +45,18 @@
 
   private AppView<AppInfoWithLiveness> computeAppViewWithLiveness(
       Class<?> methodToBeKept, Class<?> classToBeKept) throws Exception {
-    return computeAppViewWithLiveness(
-        buildClasses(I.class, J.class, K.class, L.class, A.class, Main.class)
-            .addLibraryFile(ToolHelper.getJava8RuntimeJar())
-            .build(),
-        factory ->
-            buildConfigForRules(
-                factory,
+    return TestAppViewBuilder.builder()
+        .addProgramClasses(I.class, J.class, K.class, L.class, A.class, Main.class)
+        .addLibraryFiles(ToolHelper.getJava8RuntimeJar())
+        .addKeepRuleBuilder(
+            factory ->
                 ImmutableList.<ProguardConfigurationRule>builder()
                     .addAll(buildKeepRuleForClassAndMethods(methodToBeKept, factory))
                     .addAll(buildKeepRuleForClass(classToBeKept, factory))
                     .addAll(buildKeepRuleForClassAndMethods(Main.class, factory))
-                    .build()));
+                    .build())
+        .setMinApi(AndroidApiLevel.N)
+        .buildWithLiveness();
   }
 
   @Test
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/PrivateAndInterfaceMethodCollisionTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/PrivateAndInterfaceMethodCollisionTest.java
index f440689..837f374 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/PrivateAndInterfaceMethodCollisionTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/PrivateAndInterfaceMethodCollisionTest.java
@@ -9,7 +9,6 @@
 import com.android.tools.r8.NoUnusedInterfaceRemoval;
 import com.android.tools.r8.NoVerticalClassMerging;
 import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.utils.codeinspector.HorizontallyMergedClassesInspector;
 import org.junit.Test;
 
 public class PrivateAndInterfaceMethodCollisionTest extends HorizontalClassMergingTestBase {
@@ -24,7 +23,15 @@
         .addInnerClasses(getClass())
         .addKeepMainRule(Main.class)
         .addHorizontallyMergedClassesInspector(
-            HorizontallyMergedClassesInspector::assertNoClassesMerged)
+            i -> {
+              if (parameters.canUseDefaultAndStaticInterfaceMethods()) {
+                i.assertClassesMerged();
+              } else {
+                // With default method desugaring all uses of J::foo are eliminated so A and B
+                // merge.
+                i.assertIsCompleteMergeGroup(A.class, B.class);
+              }
+            })
         .enableInliningAnnotations()
         .enableNeverClassInliningAnnotations()
         .enableNoUnusedInterfaceRemovalAnnotations()
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/StaticAndInterfaceMethodCollisionTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/StaticAndInterfaceMethodCollisionTest.java
index 48844ff..c21469c 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/StaticAndInterfaceMethodCollisionTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/StaticAndInterfaceMethodCollisionTest.java
@@ -9,7 +9,6 @@
 import com.android.tools.r8.NoUnusedInterfaceRemoval;
 import com.android.tools.r8.NoVerticalClassMerging;
 import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.utils.codeinspector.HorizontallyMergedClassesInspector;
 import org.junit.Test;
 
 public class StaticAndInterfaceMethodCollisionTest extends HorizontalClassMergingTestBase {
@@ -24,7 +23,15 @@
         .addInnerClasses(getClass())
         .addKeepMainRule(Main.class)
         .addHorizontallyMergedClassesInspector(
-            HorizontallyMergedClassesInspector::assertNoClassesMerged)
+            i -> {
+              if (parameters.canUseDefaultAndStaticInterfaceMethods()) {
+                i.assertNoClassesMerged();
+              } else {
+                // When desugaring the call to C.foo in main will be inlined to target the CC.
+                // With J.foo now unused the classes A and B are safe to merge.
+                i.assertIsCompleteMergeGroup(A.class, B.class);
+              }
+            })
         .enableInliningAnnotations()
         .enableNeverClassInliningAnnotations()
         .enableNoUnusedInterfaceRemovalAnnotations()
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 e2eef5f..4e38b33 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
@@ -30,7 +30,9 @@
         .addHorizontallyMergedClassesInspector(
             inspector ->
                 inspector
-                    .assertIsCompleteMergeGroup(I.class, J.class)
+                    .applyIf(
+                        parameters.canUseDefaultAndStaticInterfaceMethods(),
+                        i -> i.assertIsCompleteMergeGroup(I.class, J.class))
                     .applyIf(
                         !parameters.canUseDefaultAndStaticInterfaceMethods(),
                         i -> i.assertIsCompleteMergeGroup(B1.class, B2.class))
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 0af1e85..f7f2e0d 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
@@ -14,6 +14,7 @@
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.classmerging.horizontal.HorizontalClassMergingTestBase;
 import com.android.tools.r8.synthesis.SyntheticItemsTestUtils;
+import org.junit.Assume;
 import org.junit.Test;
 
 public class OverrideDefaultMethodTest extends HorizontalClassMergingTestBase {
@@ -23,6 +24,7 @@
 
   @Test
   public void testR8() throws Exception {
+    Assume.assumeTrue("b/197494749", parameters.canUseDefaultAndStaticInterfaceMethods());
     testForR8(parameters.getBackend())
         .addInnerClasses(getClass())
         .addKeepMainRule(Main.class)
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 0ea8597..313dec7 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
@@ -38,11 +38,17 @@
                 inspector.assertNoClassesMerged();
               } else {
                 inspector
-                    .assertClassesNotMerged(A.class, B.class)
                     .assertIsCompleteMergeGroup(I.class, J.class)
-                    .assertIsCompleteMergeGroup(
-                        SyntheticItemsTestUtils.syntheticCompanionClass(I.class),
-                        SyntheticItemsTestUtils.syntheticCompanionClass(J.class))
+                    .applyIf(
+                        parameters.canUseDefaultAndStaticInterfaceMethods(),
+                        i ->
+                            i.assertClassesNotMerged(A.class, B.class)
+                                .assertIsCompleteMergeGroup(
+                                    SyntheticItemsTestUtils.syntheticCompanionClass(I.class),
+                                    SyntheticItemsTestUtils.syntheticCompanionClass(J.class)))
+                    .applyIf(
+                        !parameters.canUseDefaultAndStaticInterfaceMethods(),
+                        i -> i.assertClassesMerged(A.class, B.class))
                     .assertNoOtherClassesMerged();
               }
             })
@@ -56,7 +62,9 @@
                   onlyIf(parameters.canUseDefaultAndStaticInterfaceMethods(), isPresent()));
               assertThat(codeInspector.clazz(Parent.class), isPresent());
               assertThat(codeInspector.clazz(A.class), isPresent());
-              assertThat(codeInspector.clazz(B.class), isPresent());
+              assertThat(
+                  codeInspector.clazz(B.class),
+                  onlyIf(parameters.canUseDefaultAndStaticInterfaceMethods(), isPresent()));
               assertThat(codeInspector.clazz(C.class), isPresent());
             });
   }
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/dispatch/OverrideMergeAbsentTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/dispatch/OverrideMergeAbsentTest.java
index 6f5e761..1ec170d 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/dispatch/OverrideMergeAbsentTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/dispatch/OverrideMergeAbsentTest.java
@@ -4,6 +4,7 @@
 
 package com.android.tools.r8.classmerging.horizontal.dispatch;
 
+import static com.android.tools.r8.utils.codeinspector.Matchers.isAbsent;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
 import static org.hamcrest.MatcherAssert.assertThat;
 
@@ -12,7 +13,6 @@
 import com.android.tools.r8.NoVerticalClassMerging;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.classmerging.horizontal.HorizontalClassMergingTestBase;
-import com.android.tools.r8.utils.codeinspector.HorizontallyMergedClassesInspector;
 import org.junit.Test;
 
 public class OverrideMergeAbsentTest extends HorizontalClassMergingTestBase {
@@ -30,14 +30,23 @@
         .enableNoVerticalClassMergingAnnotations()
         .setMinApi(parameters.getApiLevel())
         .addHorizontallyMergedClassesInspector(
-            HorizontallyMergedClassesInspector::assertNoClassesMerged)
+            inspector -> {
+              if (parameters.canUseDefaultAndStaticInterfaceMethods()) {
+                inspector.assertNoClassesMerged();
+              } else {
+                // When desugaring B.m is moved and A and B can be merged.
+                inspector.assertIsCompleteMergeGroup(A.class, B.class);
+              }
+            })
         .run(parameters.getRuntime(), Main.class)
         .assertSuccessWithOutputLines("A", "B", "A", "J")
         .inspect(
             codeInspector -> {
               assertThat(codeInspector.clazz(J.class), isPresent());
               assertThat(codeInspector.clazz(A.class), isPresent());
-              assertThat(codeInspector.clazz(B.class), isPresent());
+              assertThat(
+                  codeInspector.clazz(B.class),
+                  parameters.canUseDefaultAndStaticInterfaceMethods() ? isPresent() : isAbsent());
               assertThat(codeInspector.clazz(C.class), isPresent());
             });
   }
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 cc19dd2..6529d20 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
@@ -4,6 +4,7 @@
 
 package com.android.tools.r8.classmerging.horizontal.interfaces;
 
+import static com.android.tools.r8.utils.codeinspector.Matchers.isAbsent;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isImplementing;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
 import static org.hamcrest.MatcherAssert.assertThat;
@@ -55,11 +56,10 @@
                   .assertMergedInto(B.class, A.class);
               if (parameters.canUseDefaultAndStaticInterfaceMethods()) {
                 inspector.assertClassesNotMerged(I.class, J.class, K.class);
-              } else {
-                inspector
-                    .assertIsCompleteMergeGroup(I.class, J.class)
-                    .assertClassesNotMerged(K.class);
+              } else if (enableInterfaceMergingInInitial) {
+                inspector.assertIsCompleteMergeGroup(I.class, J.class);
               }
+              inspector.assertNoOtherClassesMerged();
             })
         .addOptionsModification(
             options -> {
@@ -82,14 +82,12 @@
               assertThat(aClassSubject, isImplementing(inspector.clazz(K.class)));
 
               ClassSubject cClassSubject = inspector.clazz(C.class);
-              assertThat(cClassSubject, isPresent());
-              assertThat(
-                  cClassSubject,
-                  isImplementing(
-                      inspector.clazz(
-                          parameters.canUseDefaultAndStaticInterfaceMethods()
-                              ? J.class
-                              : I.class)));
+              if (parameters.canUseDefaultAndStaticInterfaceMethods()) {
+                assertThat(cClassSubject, isPresent());
+                assertThat(cClassSubject, isImplementing(inspector.clazz(J.class)));
+              } else {
+                assertThat(cClassSubject, isAbsent());
+              }
             })
         .run(parameters.getRuntime(), Main.class)
         .assertSuccessWithOutputLines("A", "K", "J");
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 e18ce93..5ee0365 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
@@ -4,6 +4,7 @@
 
 package com.android.tools.r8.classmerging.horizontal.interfaces;
 
+import static com.android.tools.r8.utils.codeinspector.Matchers.isAbsent;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isImplementing;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
 import static org.hamcrest.MatcherAssert.assertThat;
@@ -16,7 +17,9 @@
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.synthesis.SyntheticItemsTestUtils;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import org.junit.Assume;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
@@ -37,6 +40,7 @@
 
   @Test
   public void test() throws Exception {
+    Assume.assumeTrue("b/197494749", parameters.canUseDefaultAndStaticInterfaceMethods());
     testForR8(parameters.getBackend())
         .addInnerClasses(getClass())
         .addKeepMainRule(Main.class)
@@ -47,7 +51,10 @@
               if (parameters.canUseDefaultAndStaticInterfaceMethods()) {
                 inspector.assertNoClassesMerged();
               } else {
-                inspector.assertIsCompleteMergeGroup(I.class, J.class);
+                // J is removed as part of desugaring. This enables merging of its CC class.
+                inspector.assertIsCompleteMergeGroup(
+                    SyntheticItemsTestUtils.syntheticCompanionClass(J.class),
+                    SyntheticItemsTestUtils.syntheticCompanionClass(K.class));
               }
             })
         .enableInliningAnnotations()
@@ -60,19 +67,18 @@
         .inspect(
             inspector -> {
               ClassSubject aClassSubject = inspector.clazz(A.class);
-              assertThat(aClassSubject, isPresent());
-              assertThat(aClassSubject, isImplementing(inspector.clazz(I.class)));
-              assertThat(aClassSubject, isImplementing(inspector.clazz(K.class)));
-
               ClassSubject bClassSubject = inspector.clazz(B.class);
-              assertThat(bClassSubject, isPresent());
-              assertThat(
-                  bClassSubject,
-                  isImplementing(
-                      inspector.clazz(
-                          parameters.canUseDefaultAndStaticInterfaceMethods()
-                              ? J.class
-                              : I.class)));
+              if (parameters.canUseDefaultAndStaticInterfaceMethods()) {
+                assertThat(aClassSubject, isPresent());
+                assertThat(aClassSubject, isImplementing(inspector.clazz(I.class)));
+                assertThat(aClassSubject, isImplementing(inspector.clazz(K.class)));
+                assertThat(bClassSubject, isPresent());
+                assertThat(bClassSubject, isImplementing(inspector.clazz(J.class)));
+              } else {
+                // When desugaring the calls in main will directly target the CC classes.
+                assertThat(aClassSubject, isAbsent());
+                assertThat(bClassSubject, isAbsent());
+              }
             })
         .run(parameters.getRuntime(), Main.class)
         .assertSuccessWithOutputLines("K", "J");
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 2324f03..a1dbbaf 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
@@ -4,6 +4,7 @@
 
 package com.android.tools.r8.classmerging.horizontal.interfaces;
 
+import static com.android.tools.r8.utils.codeinspector.Matchers.isAbsent;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isImplementing;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
 import static org.hamcrest.MatcherAssert.assertThat;
@@ -61,11 +62,15 @@
         .inspect(
             inspector -> {
               ClassSubject aClassSubject = inspector.clazz(A.class);
-              assertThat(aClassSubject, isPresent());
-              if (parameters.isCfRuntime()) {
-                assertThat(aClassSubject, isImplementing(inspector.clazz(I.class)));
+              if (parameters.canUseDefaultAndStaticInterfaceMethods()) {
+                assertThat(aClassSubject, isPresent());
+                if (parameters.isCfRuntime()) {
+                  assertThat(aClassSubject, isImplementing(inspector.clazz(I.class)));
+                } else {
+                  assertThat(aClassSubject, isImplementing(inspector.clazz(J.class)));
+                }
               } else {
-                assertThat(aClassSubject, isImplementing(inspector.clazz(J.class)));
+                assertThat(aClassSubject, isAbsent());
               }
             })
         .run(parameters.getRuntime(), Main.class)
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 5a656d9..2c40a58 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
@@ -4,6 +4,7 @@
 
 package com.android.tools.r8.classmerging.horizontal.interfaces;
 
+import static com.android.tools.r8.utils.codeinspector.Matchers.isAbsent;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isImplementing;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
 import static org.hamcrest.MatcherAssert.assertThat;
@@ -40,7 +41,13 @@
         .addInnerClasses(getClass())
         .addKeepMainRule(Main.class)
         .addHorizontallyMergedClassesInspector(
-            inspector -> inspector.assertIsCompleteMergeGroup(I.class, J.class))
+            inspector -> {
+              if (parameters.canUseDefaultAndStaticInterfaceMethods()) {
+                inspector.assertIsCompleteMergeGroup(I.class, J.class);
+              } else {
+                inspector.assertNoClassesMerged();
+              }
+            })
         .enableInliningAnnotations()
         .enableNeverClassInliningAnnotations()
         .enableNoUnusedInterfaceRemovalAnnotations()
@@ -50,8 +57,12 @@
         .inspect(
             inspector -> {
               ClassSubject aClassSubject = inspector.clazz(A.class);
-              assertThat(aClassSubject, isPresent());
-              assertThat(aClassSubject, isImplementing(inspector.clazz(I.class)));
+              if (parameters.canUseDefaultAndStaticInterfaceMethods()) {
+                assertThat(aClassSubject, isPresent());
+                assertThat(aClassSubject, isImplementing(inspector.clazz(I.class)));
+              } else {
+                assertThat(aClassSubject, isAbsent());
+              }
             })
         .run(parameters.getRuntime(), Main.class)
         .assertSuccessWithOutputLines("I", "J");
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 a84dbac..f68dd90 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
@@ -4,6 +4,7 @@
 
 package com.android.tools.r8.classmerging.horizontal.interfaces;
 
+import static com.android.tools.r8.utils.codeinspector.Matchers.isAbsent;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isImplementing;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
 import static org.hamcrest.MatcherAssert.assertThat;
@@ -17,6 +18,7 @@
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.HorizontallyMergedClassesInspector;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
@@ -42,13 +44,7 @@
         .addKeepMainRule(Main.class)
         // I and J are not eligible for merging, since they declare the same default method.
         .addHorizontallyMergedClassesInspector(
-            inspector -> {
-              if (parameters.canUseDefaultAndStaticInterfaceMethods()) {
-                inspector.assertNoClassesMerged();
-              } else {
-                inspector.assertIsCompleteMergeGroup(I.class, J.class);
-              }
-            })
+            HorizontallyMergedClassesInspector::assertNoClassesMerged)
         .enableInliningAnnotations()
         .enableNeverClassInliningAnnotations()
         .enableNoHorizontalClassMergingAnnotations()
@@ -59,10 +55,17 @@
         .inspect(
             inspector -> {
               ClassSubject aClassSubject = inspector.clazz(A.class);
+              ClassSubject bClassSubject = inspector.clazz(B.class);
+              if (!parameters.canUseDefaultAndStaticInterfaceMethods()) {
+                // When desugaring, the forwarding methods to the CC.m methods will be inlined and
+                // the class instances become dead code.
+                assertThat(aClassSubject, isAbsent());
+                assertThat(bClassSubject, isAbsent());
+                return;
+              }
               assertThat(aClassSubject, isPresent());
               assertThat(aClassSubject, isImplementing(inspector.clazz(I.class)));
 
-              ClassSubject bClassSubject = inspector.clazz(B.class);
               assertThat(bClassSubject, isPresent());
               assertThat(
                   bClassSubject,
diff --git a/src/test/java/com/android/tools/r8/classmerging/vertical/VerticalClassMergerTest.java b/src/test/java/com/android/tools/r8/classmerging/vertical/VerticalClassMergerTest.java
index 2f157d0..3b35a12 100644
--- a/src/test/java/com/android/tools/r8/classmerging/vertical/VerticalClassMergerTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/vertical/VerticalClassMergerTest.java
@@ -50,6 +50,7 @@
 import java.util.Set;
 import java.util.function.Consumer;
 import java.util.function.Predicate;
+import org.junit.Assume;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
@@ -355,6 +356,7 @@
 
   @Test
   public void testNestedDefaultInterfaceMethodsTest() throws Throwable {
+    Assume.assumeTrue("b/197494749", parameters.canUseDefaultAndStaticInterfaceMethods());
     String main = "classmerging.NestedDefaultInterfaceMethodsTest";
     Path[] programFiles =
         new Path[] {
diff --git a/src/test/java/com/android/tools/r8/code/invokedynamic/InvokeDynamicVirtualDispatchToDefaultInterfaceMethodTest.java b/src/test/java/com/android/tools/r8/code/invokedynamic/InvokeDynamicVirtualDispatchToDefaultInterfaceMethodTest.java
new file mode 100644
index 0000000..7494b13
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/code/invokedynamic/InvokeDynamicVirtualDispatchToDefaultInterfaceMethodTest.java
@@ -0,0 +1,144 @@
+// 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.code.invokedynamic;
+
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.references.ClassReference;
+import com.android.tools.r8.references.MethodReference;
+import com.android.tools.r8.references.Reference;
+import com.android.tools.r8.utils.StringUtils;
+import java.lang.invoke.CallSite;
+import java.lang.invoke.ConstantCallSite;
+import java.lang.invoke.MethodHandle;
+import java.lang.invoke.MethodHandles;
+import java.lang.invoke.MethodHandles.Lookup;
+import java.lang.invoke.MethodType;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.objectweb.asm.Handle;
+import org.objectweb.asm.Opcodes;
+
+/**
+ * Test invokedynamic with a static bootstrap method with an extra arg that is a MethodHandle of
+ * kind invoke virtual. The target method is a method into a class implementing an abstract method
+ * and that shadows a default method from an interface.
+ */
+// TODO(b/167145686): Copy this test and implement all of the variants in
+//  ...AndroidOTest.invokeCustom... and then delete those tests.
+@RunWith(Parameterized.class)
+public class InvokeDynamicVirtualDispatchToDefaultInterfaceMethodTest extends TestBase {
+
+  static final String EXPECTED = StringUtils.lines("Called I.foo");
+
+  private final TestParameters parameters;
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters()
+        .withAllRuntimes()
+        .withApiLevelsStartingAtIncluding(apiLevelWithInvokeCustomSupport())
+        .enableApiLevelsForCf()
+        .build();
+  }
+
+  public InvokeDynamicVirtualDispatchToDefaultInterfaceMethodTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testReference() throws Exception {
+    testForDesugaring(parameters)
+        .addProgramClasses(I.class, A.class)
+        .addProgramClassFileData(getTransformedTestClass())
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutput(EXPECTED);
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    assumeTrue(
+        "Only test one R8/CF build.",
+        parameters.isDexRuntime() || parameters.getApiLevel() == apiLevelWithInvokeCustomSupport());
+    testForR8(parameters.getBackend())
+        .allowAccessModification()
+        .addProgramClasses(I.class, A.class)
+        .addProgramClassFileData(getTransformedTestClass())
+        .setMinApi(parameters.getApiLevel())
+        .addKeepMainRule(TestClass.class)
+        .addKeepRules("-keepclassmembers class * { *** foo(...); }")
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutput(EXPECTED);
+  }
+
+  private byte[] getTransformedTestClass() throws Exception {
+    ClassReference aClass = Reference.classFromClass(A.class);
+    MethodReference iFoo = Reference.methodFromMethod(I.class.getDeclaredMethod("foo"));
+    MethodReference bsm =
+        Reference.methodFromMethod(
+            TestClass.class.getDeclaredMethod(
+                "bsmCreateCallSite",
+                Lookup.class,
+                String.class,
+                MethodType.class,
+                MethodHandle.class));
+    return transformer(TestClass.class)
+        .transformMethodInsnInMethod(
+            "main",
+            (opcode, owner, name, descriptor, isInterface, visitor) -> {
+              if (name.equals("replaced")) {
+                visitor.visitInvokeDynamicInsn(
+                    iFoo.getMethodName(),
+                    "(" + aClass.getDescriptor() + ")V",
+                    new Handle(
+                        Opcodes.H_INVOKESTATIC,
+                        bsm.getHolderClass().getBinaryName(),
+                        bsm.getMethodName(),
+                        bsm.getMethodDescriptor(),
+                        false),
+                    new Handle(
+                        Opcodes.H_INVOKEVIRTUAL,
+                        aClass.getBinaryName(),
+                        iFoo.getMethodName(),
+                        iFoo.getMethodDescriptor(),
+                        false));
+              } else {
+                visitor.visitMethodInsn(opcode, owner, name, descriptor, isInterface);
+              }
+            })
+        .transform();
+  }
+
+  public interface I {
+
+    default void foo() {
+      System.out.println("Called I.foo");
+    }
+  }
+
+  public static class A implements I {
+    // Instantiation with default from I.
+  }
+
+  static class TestClass {
+
+    public static CallSite bsmCreateCallSite(
+        MethodHandles.Lookup caller, String name, MethodType type, MethodHandle handle)
+        throws Throwable {
+      return new ConstantCallSite(handle);
+    }
+
+    public static void replaced(Object o) {
+      throw new RuntimeException("unreachable!");
+    }
+
+    public static void main(String[] args) {
+      replaced(new A());
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/desugar/DefaultLambdaWithUnderscoreThisTestRunner.java b/src/test/java/com/android/tools/r8/desugar/DefaultLambdaWithUnderscoreThisTestRunner.java
index 7cde792..2e9abfb 100644
--- a/src/test/java/com/android/tools/r8/desugar/DefaultLambdaWithUnderscoreThisTestRunner.java
+++ b/src/test/java/com/android/tools/r8/desugar/DefaultLambdaWithUnderscoreThisTestRunner.java
@@ -100,9 +100,9 @@
         testForD8()
             .addProgramClassesAndInnerClasses(CLASS)
             .setMinApiThreshold(AndroidApiLevel.K)
-            .compile();
+            .compile()
+            .assertNoMessages();
     compileResult
-        // TODO(b/123506120): Add .assertNoMessages()
         .run(parameters.getRuntime(), CLASS)
         .assertSuccessWithOutput(EXPECTED);
     runDebugger(compileResult.debugConfig(), true);
diff --git a/src/test/java/com/android/tools/r8/desugar/DesugarInnerClassesInInterfaces.java b/src/test/java/com/android/tools/r8/desugar/DesugarInnerClassesInInterfaces.java
index 1c8d5b9..8902cd2 100644
--- a/src/test/java/com/android/tools/r8/desugar/DesugarInnerClassesInInterfaces.java
+++ b/src/test/java/com/android/tools/r8/desugar/DesugarInnerClassesInInterfaces.java
@@ -4,11 +4,13 @@
 package com.android.tools.r8.desugar;
 
 import static com.android.tools.r8.ir.desugar.itf.InterfaceDesugaringForTesting.getCompanionClassNameSuffix;
+import static org.junit.Assume.assumeTrue;
 
 import com.android.tools.r8.DesugarTestConfiguration;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.AndroidApiLevel;
 import com.google.common.collect.ImmutableList;
 import java.util.List;
 import java.util.concurrent.Callable;
@@ -30,13 +32,6 @@
           WithLocalInner.class.getName() + getCompanionClassNameSuffix(),
           "true");
 
-  private final List<String> EXPECTED_RESULT_WITH_DESUGARING_B168697955 =
-      ImmutableList.of(
-          WithAnonymousInner.class.getName() + getCompanionClassNameSuffix(),
-          "false",
-          WithLocalInner.class.getName() + getCompanionClassNameSuffix(),
-          "false");
-
   @Parameterized.Parameters(name = "{0}")
   public static TestParametersCollection data() {
     return getTestParameters().withAllRuntimes().withAllApiLevelsAlsoForCf().build();
@@ -69,6 +64,7 @@
 
   @Test
   public void testR8Compat() throws Exception {
+    assumeTrue(parameters.isDexRuntime() || parameters.getApiLevel() == AndroidApiLevel.B);
     testForR8Compat(parameters.getBackend())
         .addInnerClasses(DesugarInnerClassesInInterfaces.class)
         .setMinApi(parameters.getApiLevel())
@@ -78,37 +74,14 @@
         .compile()
         .run(parameters.getRuntime(), TestClass.class)
         .applyIf(
-            parameters.canUseDefaultAndStaticInterfaceMethods(),
+            parameters.isCfRuntime() || parameters.canUseDefaultAndStaticInterfaceMethods(),
             result -> result.assertSuccessWithOutputLines(EXPECTED_RESULT_WITHOUT_DESUGARING),
-            // The static method which is moved to the companion class is inlined and causing
-            // this different output. The rule "-keep class * { *; }" does not keep the static
-            // method from being inlined after it has moved. Turning off inlining produces the
-            // expected result. The inlining cause the getEnclosingClass() to return null.
-            // See b/168697955.
-            result ->
-                result.assertSuccessWithOutputLines(EXPECTED_RESULT_WITH_DESUGARING_B168697955));
-  }
-
-  @Test
-  public void testR8_B168697955() throws Exception {
-    testForR8(parameters.getBackend())
-        .addInnerClasses(DesugarInnerClassesInInterfaces.class)
-        .setMinApi(parameters.getApiLevel())
-        .addKeepAllClassesRule()
-        .addKeepAttributeInnerClassesAndEnclosingMethod()
-        // With inlining turned off we get the expected result.
-        .addOptionsModification(options -> options.enableInlining = false)
-        .compile()
-        .run(parameters.getRuntime(), TestClass.class)
-        .applyIf(
-            parameters.canUseDefaultAndStaticInterfaceMethods(),
-            result -> result.assertSuccessWithOutputLines(EXPECTED_RESULT_WITHOUT_DESUGARING),
-            // TODO(b/187377562): We remove the attribute due to not pinning the moved methods.
-            result -> result.assertFailureWithErrorThatThrows(NullPointerException.class));
+            result -> result.assertSuccessWithOutputLines(EXPECTED_RESULT_WITH_DESUGARING));
   }
 
   @Test
   public void testR8Full() throws Exception {
+    assumeTrue(parameters.isDexRuntime() || parameters.getApiLevel() == AndroidApiLevel.B);
     testForR8(parameters.getBackend())
         .addInnerClasses(DesugarInnerClassesInInterfaces.class)
         .setMinApi(parameters.getApiLevel())
@@ -117,10 +90,9 @@
         .compile()
         .run(parameters.getRuntime(), TestClass.class)
         .applyIf(
-            parameters.canUseDefaultAndStaticInterfaceMethods(),
+            parameters.isCfRuntime() || parameters.canUseDefaultAndStaticInterfaceMethods(),
             result -> result.assertSuccessWithOutputLines(EXPECTED_RESULT_WITHOUT_DESUGARING),
-            // TODO(b/187377562): We remove the attribute due to not pinning the moved methods.
-            result -> result.assertFailureWithErrorThatThrows(NullPointerException.class));
+            result -> result.assertSuccessWithOutputLines(EXPECTED_RESULT_WITH_DESUGARING));
   }
 
   interface WithAnonymousInner {
diff --git a/src/test/java/com/android/tools/r8/desugar/DesugarLambdaWithAnonymousClass.java b/src/test/java/com/android/tools/r8/desugar/DesugarLambdaWithAnonymousClass.java
index 9691b07..23436d3 100644
--- a/src/test/java/com/android/tools/r8/desugar/DesugarLambdaWithAnonymousClass.java
+++ b/src/test/java/com/android/tools/r8/desugar/DesugarLambdaWithAnonymousClass.java
@@ -34,7 +34,8 @@
 
   private final List<String> EXPECTED_D8_DESUGARED_RESULT =
       ImmutableList.of(
-          "Hello from inside lambda$test$0$DesugarLambdaWithAnonymousClass$TestClass",
+          "Hello from inside"
+              + " lambda$test$0$com-android-tools-r8-desugar-DesugarLambdaWithAnonymousClass$TestClass",
           "Hello from inside lambda$testStatic$1");
 
   @Parameterized.Parameters(name = "{0}")
diff --git a/src/test/java/com/android/tools/r8/desugar/DesugarLambdaWithLocalClass.java b/src/test/java/com/android/tools/r8/desugar/DesugarLambdaWithLocalClass.java
index 62c36fd..60fde2f 100644
--- a/src/test/java/com/android/tools/r8/desugar/DesugarLambdaWithLocalClass.java
+++ b/src/test/java/com/android/tools/r8/desugar/DesugarLambdaWithLocalClass.java
@@ -33,7 +33,8 @@
 
   private List<String> EXPECTED_D8_DESUGARED_RESULT =
       ImmutableList.of(
-          "Hello from inside lambda$test$0$DesugarLambdaWithLocalClass$TestClass",
+          "Hello from inside"
+              + " lambda$test$0$com-android-tools-r8-desugar-DesugarLambdaWithLocalClass$TestClass",
           "Hello from inside lambda$testStatic$1");
 
   @Parameterized.Parameters(name = "{0}")
diff --git a/src/test/java/com/android/tools/r8/desugar/constantdynamic/BasicConstantDynamicTest.java b/src/test/java/com/android/tools/r8/desugar/constantdynamic/BasicConstantDynamicTest.java
index b0d261e..aede694 100644
--- a/src/test/java/com/android/tools/r8/desugar/constantdynamic/BasicConstantDynamicTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/constantdynamic/BasicConstantDynamicTest.java
@@ -4,19 +4,16 @@
 package com.android.tools.r8.desugar.constantdynamic;
 
 import static com.android.tools.r8.DiagnosticsMatcher.diagnosticMessage;
-import static com.android.tools.r8.DiagnosticsMatcher.diagnosticOrigin;
-import static com.android.tools.r8.OriginMatcher.hasParent;
-import static org.hamcrest.CoreMatchers.allOf;
 import static org.hamcrest.CoreMatchers.containsString;
 import static org.junit.Assert.assertThrows;
 import static org.junit.Assume.assumeTrue;
 
 import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.DesugarTestConfiguration;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestRuntime.CfVm;
 import com.android.tools.r8.cf.CfVersion;
-import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.StringUtils;
 import java.io.IOException;
@@ -40,6 +37,7 @@
   }
 
   private static final String EXPECTED_OUTPUT = StringUtils.lines("true", "true");
+  private static final Class<?> MAIN_CLASS = A.class;
 
   @Test
   public void testReference() throws Exception {
@@ -49,49 +47,73 @@
 
     testForJvm()
         .addProgramClassFileData(getTransformedClasses())
-        .run(parameters.getRuntime(), A.class)
+        .run(parameters.getRuntime(), MAIN_CLASS)
         .assertSuccessWithOutput(EXPECTED_OUTPUT);
   }
 
   @Test
+  public void TestD8Cf() throws Exception {
+    testForDesugaring(parameters)
+        .addProgramClassFileData(getTransformedClasses())
+        .run(parameters.getRuntime(), MAIN_CLASS)
+        .applyIf(
+            // When not desugaring the CF code requires JDK 11.
+            DesugarTestConfiguration::isNotDesugared,
+            r -> {
+              if (parameters.isCfRuntime()
+                  && parameters.getRuntime().asCf().isNewerThanOrEqual(CfVm.JDK11)) {
+                r.assertSuccessWithOutput(EXPECTED_OUTPUT);
+              } else {
+                r.assertFailureWithErrorThatThrows(UnsupportedClassVersionError.class);
+              }
+            })
+        .applyIf(
+            DesugarTestConfiguration::isDesugared, r -> r.assertSuccessWithOutput(EXPECTED_OUTPUT));
+  }
+
+  @Test
   public void testD8() throws Exception {
     assumeTrue(parameters.getRuntime().isDex());
 
-    assertThrows(
-        CompilationFailedException.class,
-        () ->
-            testForD8(parameters.getBackend())
-                .addProgramClassFileData(getTransformedClasses())
-                .setMinApi(parameters.getApiLevel())
-                .compileWithExpectedDiagnostics(
-                    diagnostics -> {
-                      diagnostics.assertOnlyErrors();
-                      diagnostics.assertErrorsMatch(
-                          allOf(
-                              diagnosticMessage(containsString("Unsupported dynamic constant")),
-                              diagnosticOrigin(hasParent(Origin.unknown()))));
-                    }));
+    testForD8(parameters.getBackend())
+        .addProgramClassFileData(getTransformedClasses())
+        .setMinApi(parameters.getApiLevel())
+        .run(parameters.getRuntime(), MAIN_CLASS)
+        .assertSuccessWithOutput(EXPECTED_OUTPUT);
   }
 
   @Test
   public void testR8() throws Exception {
     assumeTrue(parameters.isDexRuntime() || parameters.getApiLevel().isEqualTo(AndroidApiLevel.B));
 
-    assertThrows(
-        CompilationFailedException.class,
-        () ->
-            testForR8(parameters.getBackend())
-                .addProgramClassFileData(getTransformedClasses())
-                .setMinApi(parameters.getApiLevel())
-                .addKeepMainRule(A.class)
-                .compileWithExpectedDiagnostics(
-                    diagnostics -> {
-                      diagnostics.assertOnlyErrors();
-                      diagnostics.assertErrorsMatch(
-                          allOf(
-                              diagnosticMessage(containsString("Unsupported dynamic constant")),
-                              diagnosticOrigin(hasParent(Origin.unknown()))));
-                    }));
+    testForR8(parameters.getBackend())
+        .addProgramClassFileData(getTransformedClasses())
+        .setMinApi(parameters.getApiLevel())
+        .addKeepMainRule(MAIN_CLASS)
+        // TODO(b/198142613): There should not be a warnings on class references which are
+        //  desugared away.
+        .applyIf(
+            parameters.getApiLevel().isLessThan(AndroidApiLevel.O),
+            b -> b.addDontWarn("java.lang.invoke.MethodHandles$Lookup"))
+        // TODO(b/198142625): Support CONSTANT_Dynamic output for class files.
+        .applyIf(
+            parameters.isCfRuntime(),
+            r -> {
+              assertThrows(
+                  CompilationFailedException.class,
+                  () ->
+                      r.compileWithExpectedDiagnostics(
+                          diagnostics -> {
+                            diagnostics.assertOnlyErrors();
+                            diagnostics.assertErrorsMatch(
+                                diagnosticMessage(
+                                    containsString(
+                                        "Unsupported dynamic constant (not desugaring)")));
+                          }));
+            },
+            r ->
+                r.run(parameters.getRuntime(), MAIN_CLASS)
+                    .assertSuccessWithOutput(EXPECTED_OUTPUT));
   }
 
   private byte[] getTransformedClasses() throws IOException {
diff --git a/src/test/java/com/android/tools/r8/cf/ConstantDynamicHolderTest.java b/src/test/java/com/android/tools/r8/desugar/constantdynamic/ConstantDynamicHolderTest.java
similarity index 79%
rename from src/test/java/com/android/tools/r8/cf/ConstantDynamicHolderTest.java
rename to src/test/java/com/android/tools/r8/desugar/constantdynamic/ConstantDynamicHolderTest.java
index 1ec23ca..48cbeaf 100644
--- a/src/test/java/com/android/tools/r8/cf/ConstantDynamicHolderTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/constantdynamic/ConstantDynamicHolderTest.java
@@ -2,7 +2,7 @@
 // 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.cf;
+package com.android.tools.r8.desugar.constantdynamic;
 
 import static org.hamcrest.CoreMatchers.containsString;
 import static org.junit.Assert.assertEquals;
@@ -14,10 +14,12 @@
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.TestRuntime.CfVm;
+import com.android.tools.r8.utils.AndroidApiLevel;
 import java.io.IOException;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
 import org.junit.runners.Parameterized.Parameters;
 import org.objectweb.asm.ConstantDynamic;
 import org.objectweb.asm.Handle;
@@ -25,24 +27,19 @@
 @RunWith(Parameterized.class)
 public class ConstantDynamicHolderTest extends TestBase {
 
+  @Parameter() public TestParameters parameters;
+
   @Parameters(name = "{0}")
   public static TestParametersCollection data() {
-    return getTestParameters()
-        .withCfRuntimesStartingFromIncluding(CfVm.JDK11)
-        .withDexRuntimes()
-        .withAllApiLevels()
-        .build();
-  }
-
-  final TestParameters parameters;
-
-  public ConstantDynamicHolderTest(TestParameters parameters) {
-    this.parameters = parameters;
+    return getTestParameters().withAllRuntimes().withAllApiLevelsAlsoForCf().build();
   }
 
   @Test
   public void testReference() throws Exception {
     assumeTrue(parameters.isCfRuntime());
+    assumeTrue(parameters.getRuntime().asCf().isNewerThanOrEqual(CfVm.JDK11));
+    assumeTrue(parameters.getApiLevel().isEqualTo(AndroidApiLevel.B));
+
     testForJvm()
         .addProgramClassFileData(getTransformedMain())
         .run(parameters.getRuntime(), Main.class)
@@ -52,17 +49,21 @@
   @Test(expected = CompilationFailedException.class)
   public void testD8() throws Exception {
     assumeTrue(parameters.isDexRuntime());
+
     testForD8()
         .addProgramClassFileData(getTransformedMain())
         .setMinApi(parameters.getApiLevel())
         .compileWithExpectedDiagnostics(
             diagnostics ->
                 diagnostics.assertErrorMessageThatMatches(
-                    containsString("Unsupported dynamic constant")));
+                    containsString(
+                        "Unsupported dynamic constant (runtime provided bootstrap method)")));
   }
 
   @Test(expected = CompilationFailedException.class)
   public void testR8() throws Exception {
+    assumeTrue(parameters.isDexRuntime() || parameters.getApiLevel().isEqualTo(AndroidApiLevel.B));
+
     testForR8(parameters.getBackend())
         .addProgramClassFileData(getTransformedMain())
         .setMinApi(parameters.getApiLevel())
@@ -70,7 +71,8 @@
         .compileWithExpectedDiagnostics(
             diagnostics ->
                 diagnostics.assertErrorMessageThatMatches(
-                    containsString("Unsupported dynamic constant")));
+                    containsString(
+                        "Unsupported dynamic constant (runtime provided bootstrap method)")));
   }
 
   private byte[] getTransformedMain() throws IOException {
diff --git a/src/test/java/com/android/tools/r8/desugar/constantdynamic/HierarchyConstantDynamicTest.java b/src/test/java/com/android/tools/r8/desugar/constantdynamic/HierarchyConstantDynamicTest.java
new file mode 100644
index 0000000..edc3cab
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/constantdynamic/HierarchyConstantDynamicTest.java
@@ -0,0 +1,142 @@
+// 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.constantdynamic;
+
+import static com.android.tools.r8.DiagnosticsMatcher.diagnosticMessage;
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.junit.Assert.assertThrows;
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestRuntime.CfVm;
+import com.android.tools.r8.cf.CfVersion;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.StringUtils;
+import com.google.common.collect.ImmutableList;
+import java.io.IOException;
+import java.lang.invoke.MethodHandles;
+import java.util.Collection;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class HierarchyConstantDynamicTest extends TestBase {
+
+  @Parameter() public TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static List<Object[]> data() {
+    return buildParameters(
+        getTestParameters().withAllRuntimes().withAllApiLevelsAlsoForCf().build());
+  }
+
+  private static final String EXPECTED_OUTPUT = StringUtils.lines("true", "true", "true");
+  private static final Class<?> MAIN_CLASS = A.class;
+
+  @Test
+  public void testReference() throws Exception {
+    assumeTrue(parameters.isCfRuntime());
+    assumeTrue(parameters.getRuntime().asCf().isNewerThanOrEqual(CfVm.JDK11));
+    assumeTrue(parameters.getApiLevel().isEqualTo(AndroidApiLevel.B));
+
+    testForJvm()
+        .addProgramClassFileData(getTransformedClasses())
+        .run(parameters.getRuntime(), A.class)
+        .assertSuccessWithOutput(EXPECTED_OUTPUT);
+  }
+
+  @Test
+  public void testD8() throws Exception {
+    assumeTrue(parameters.isDexRuntime());
+
+    testForD8(parameters.getBackend())
+        .addProgramClassFileData(getTransformedClasses())
+        .setMinApi(parameters.getApiLevel())
+        .run(parameters.getRuntime(), MAIN_CLASS)
+        .assertSuccessWithOutput(EXPECTED_OUTPUT);
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    assumeTrue(parameters.isDexRuntime() || parameters.getApiLevel().isEqualTo(AndroidApiLevel.B));
+
+    testForR8(parameters.getBackend())
+        .addProgramClassFileData(getTransformedClasses())
+        .setMinApi(parameters.getApiLevel())
+        .addKeepMainRule(MAIN_CLASS)
+        // TODO(b/198142613): There should not be a warnings on class references which are
+        //  desugared away.
+        .applyIf(
+            parameters.getApiLevel().isLessThan(AndroidApiLevel.O),
+            b -> b.addDontWarn("java.lang.invoke.MethodHandles$Lookup"))
+        // TODO(b/198142625): Support CONSTANT_Dynamic output for class files.
+        .applyIf(
+            parameters.isCfRuntime(),
+            r -> {
+              assertThrows(
+                  CompilationFailedException.class,
+                  () ->
+                      r.compileWithExpectedDiagnostics(
+                          diagnostics -> {
+                            diagnostics.assertOnlyErrors();
+                            diagnostics.assertErrorsMatch(
+                                diagnosticMessage(
+                                    containsString(
+                                        "Unsupported dynamic constant (not desugaring)")));
+                          }));
+            },
+            r ->
+                r.run(parameters.getRuntime(), MAIN_CLASS)
+                    .assertSuccessWithOutput(EXPECTED_OUTPUT));
+  }
+
+  private Collection<byte[]> getTransformedClasses() throws IOException {
+    return ImmutableList.of(
+        transformer(A.class)
+            .setVersion(CfVersion.V11)
+            .transformConstStringToConstantDynamic(
+                "condy1", A.class, "myConstant", "constantName", Object.class)
+            .transform(),
+        transformer(B.class)
+            .setVersion(CfVersion.V11)
+            .transformConstStringToConstantDynamic(
+                "condy1", B.class, "myConstant", "constantName", Object.class)
+            .transform());
+  }
+
+  // The name of the bootstrap method is the same in both A and B.
+  public static class A {
+
+    public static Object f() {
+      return "condy1"; // Will be transformed to Constant_DYNAMIC.
+    }
+
+    public static void main(String[] args) {
+      System.out.println(f() != null);
+      System.out.println(B.f() != null);
+      System.out.println(f() != B.f());
+    }
+
+    private static Object myConstant(MethodHandles.Lookup lookup, String name, Class<?> type) {
+      return new Object();
+    }
+  }
+
+  public static class B extends A {
+
+    public static Object f() {
+      return "condy1"; // Will be transformed to Constant_DYNAMIC.
+    }
+
+    private static Object myConstant(MethodHandles.Lookup lookup, String name, Class<?> type) {
+      return new Object();
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/desugar/constantdynamic/JacocoConstantDynamicTest.java b/src/test/java/com/android/tools/r8/desugar/constantdynamic/JacocoConstantDynamicTest.java
index d1a4d35..86fad6d 100644
--- a/src/test/java/com/android/tools/r8/desugar/constantdynamic/JacocoConstantDynamicTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/constantdynamic/JacocoConstantDynamicTest.java
@@ -3,20 +3,13 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.desugar.constantdynamic;
 
-import static com.android.tools.r8.DiagnosticsMatcher.diagnosticMessage;
-import static com.android.tools.r8.DiagnosticsMatcher.diagnosticOrigin;
-import static com.android.tools.r8.OriginMatcher.hasParent;
 import static com.android.tools.r8.utils.DescriptorUtils.JAVA_PACKAGE_SEPARATOR;
 import static com.android.tools.r8.utils.FileUtils.CLASS_EXTENSION;
 import static com.android.tools.r8.utils.FileUtils.JAR_EXTENSION;
-import static org.hamcrest.CoreMatchers.allOf;
-import static org.hamcrest.CoreMatchers.containsString;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertThrows;
 import static org.junit.Assume.assumeTrue;
 
-import com.android.tools.r8.CompilationFailedException;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestRuntime.CfVm;
@@ -24,7 +17,6 @@
 import com.android.tools.r8.ToolHelper.DexVm;
 import com.android.tools.r8.ToolHelper.ProcessResult;
 import com.android.tools.r8.cf.CfVersion;
-import com.android.tools.r8.utils.ArchiveResourceProvider;
 import com.android.tools.r8.utils.BooleanUtils;
 import com.android.tools.r8.utils.StringUtils;
 import com.android.tools.r8.utils.ZipUtils;
@@ -45,8 +37,7 @@
 @RunWith(Parameterized.class)
 public class JacocoConstantDynamicTest extends TestBase {
 
-  @Parameter(0)
-  public TestParameters parameters;
+  @Parameter() public TestParameters parameters;
 
   @Parameter(1)
   public boolean useConstantDynamic;
@@ -140,25 +131,11 @@
         assertFalse(Files.exists(agentOutput));
       }
     } else {
-      assertThrows(
-          CompilationFailedException.class,
-          () -> {
-            ArchiveResourceProvider provider =
-                ArchiveResourceProvider.fromArchive(testClasses.getInstrumented(), true);
-            testForD8(parameters.getBackend())
-                .addProgramResourceProviders(provider)
-                .addProgramFiles(ToolHelper.JACOCO_AGENT)
-                .setMinApi(parameters.getApiLevel())
-                .compileWithExpectedDiagnostics(
-                    diagnostics -> {
-                      // Check that the error is reported as an error to the diagnostics handler.
-                      diagnostics.assertOnlyErrors();
-                      diagnostics.assertErrorsMatch(
-                          allOf(
-                              diagnosticMessage(containsString("Unsupported dynamic constant")),
-                              diagnosticOrigin(hasParent(provider.getOrigin()))));
-                    });
-          });
+      testForD8(parameters.getBackend())
+          .addProgramFiles(testClasses.getInstrumented())
+          .addProgramFiles(ToolHelper.JACOCO_AGENT)
+          .setMinApi(parameters.getApiLevel())
+          .compile();
     }
   }
 
diff --git a/src/test/java/com/android/tools/r8/desugar/constantdynamic/MultipleNamedConstantDynamicTest.java b/src/test/java/com/android/tools/r8/desugar/constantdynamic/MultipleNamedConstantDynamicTest.java
index 69cd883..e1574f1 100644
--- a/src/test/java/com/android/tools/r8/desugar/constantdynamic/MultipleNamedConstantDynamicTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/constantdynamic/MultipleNamedConstantDynamicTest.java
@@ -4,19 +4,16 @@
 package com.android.tools.r8.desugar.constantdynamic;
 
 import static com.android.tools.r8.DiagnosticsMatcher.diagnosticMessage;
-import static com.android.tools.r8.DiagnosticsMatcher.diagnosticOrigin;
-import static com.android.tools.r8.OriginMatcher.hasParent;
-import static org.hamcrest.CoreMatchers.allOf;
 import static org.hamcrest.CoreMatchers.containsString;
 import static org.junit.Assert.assertThrows;
 import static org.junit.Assume.assumeTrue;
 
 import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.DesugarTestConfiguration;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestRuntime.CfVm;
 import com.android.tools.r8.cf.CfVersion;
-import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.StringUtils;
 import java.io.IOException;
@@ -42,6 +39,7 @@
 
   private static final String EXPECTED_OUTPUT =
       StringUtils.lines("true", "true", "true", "true", "true");
+  private static final Class<?> MAIN_CLASS = A.class;
 
   @Test
   public void testReference() throws Exception {
@@ -51,28 +49,39 @@
 
     testForJvm()
         .addProgramClassFileData(getTransformedClasses())
-        .run(parameters.getRuntime(), A.class)
+        .run(parameters.getRuntime(), MAIN_CLASS)
         .assertSuccessWithOutput(EXPECTED_OUTPUT);
   }
 
   @Test
+  public void TestD8Cf() throws Exception {
+    testForDesugaring(parameters)
+        .addProgramClassFileData(getTransformedClasses())
+        .run(parameters.getRuntime(), MAIN_CLASS)
+        .applyIf(
+            // When not desugaring the CF code requires JDK 11.
+            DesugarTestConfiguration::isNotDesugared,
+            r -> {
+              if (parameters.isCfRuntime()
+                  && parameters.getRuntime().asCf().isNewerThanOrEqual(CfVm.JDK11)) {
+                r.assertSuccessWithOutput(EXPECTED_OUTPUT);
+              } else {
+                r.assertFailureWithErrorThatThrows(UnsupportedClassVersionError.class);
+              }
+            })
+        .applyIf(
+            DesugarTestConfiguration::isDesugared, r -> r.assertSuccessWithOutput(EXPECTED_OUTPUT));
+  }
+
+  @Test
   public void testD8() throws Exception {
     assumeTrue(parameters.isDexRuntime());
 
-    assertThrows(
-        CompilationFailedException.class,
-        () ->
-            testForD8(parameters.getBackend())
-                .addProgramClassFileData(getTransformedClasses())
-                .setMinApi(parameters.getApiLevel())
-                .compileWithExpectedDiagnostics(
-                    diagnostics -> {
-                      diagnostics.assertOnlyErrors();
-                      diagnostics.assertErrorsMatch(
-                          allOf(
-                              diagnosticMessage(containsString("Unsupported dynamic constant")),
-                              diagnosticOrigin(hasParent(Origin.unknown()))));
-                    }));
+    testForD8(parameters.getBackend())
+        .addProgramClassFileData(getTransformedClasses())
+        .setMinApi(parameters.getApiLevel())
+        .run(parameters.getRuntime(), MAIN_CLASS)
+        .assertSuccessWithOutput(EXPECTED_OUTPUT);
   }
 
   @Test
@@ -80,21 +89,34 @@
     assumeTrue(
         parameters.getRuntime().isDex() || parameters.getApiLevel().isEqualTo(AndroidApiLevel.B));
 
-    assertThrows(
-        CompilationFailedException.class,
-        () ->
-            testForR8(parameters.getBackend())
-                .addProgramClassFileData(getTransformedClasses())
-                .setMinApi(parameters.getApiLevel())
-                .addKeepMainRule(A.class)
-                .compileWithExpectedDiagnostics(
-                    diagnostics -> {
-                      diagnostics.assertOnlyErrors();
-                      diagnostics.assertErrorsMatch(
-                          allOf(
-                              diagnosticMessage(containsString("Unsupported dynamic constant")),
-                              diagnosticOrigin(hasParent(Origin.unknown()))));
-                    }));
+    testForR8(parameters.getBackend())
+        .addProgramClassFileData(getTransformedClasses())
+        .setMinApi(parameters.getApiLevel())
+        .addKeepMainRule(A.class)
+        // TODO(b/198142613): There should not be a warnings on class references which are
+        //  desugared away.
+        .applyIf(
+            parameters.getApiLevel().isLessThan(AndroidApiLevel.O),
+            b -> b.addDontWarn("java.lang.invoke.MethodHandles$Lookup"))
+        // TODO(b/198142625): Support CONSTANT_Dynamic output for class files.
+        .applyIf(
+            parameters.isCfRuntime(),
+            r -> {
+              assertThrows(
+                  CompilationFailedException.class,
+                  () ->
+                      r.compileWithExpectedDiagnostics(
+                          diagnostics -> {
+                            diagnostics.assertOnlyErrors();
+                            diagnostics.assertErrorsMatch(
+                                diagnosticMessage(
+                                    containsString(
+                                        "Unsupported dynamic constant (not desugaring)")));
+                          }));
+            },
+            r ->
+                r.run(parameters.getRuntime(), MAIN_CLASS)
+                    .assertSuccessWithOutput(EXPECTED_OUTPUT));
   }
 
   private byte[] getTransformedClasses() throws IOException {
@@ -138,6 +160,9 @@
     }
 
     private static Object myConstant(MethodHandles.Lookup lookup, String name, Class<?> type) {
+      // TODO(b/178172809): Use a un-moddeled library method to prevent R8 from seeing that name
+      //  is unused.
+      name.toCharArray();
       return new Object();
     }
   }
diff --git a/src/test/java/com/android/tools/r8/desugar/constantdynamic/MultipleTypesConstantDynamicTest.java b/src/test/java/com/android/tools/r8/desugar/constantdynamic/MultipleTypesConstantDynamicTest.java
index b04792f..e0082a0 100644
--- a/src/test/java/com/android/tools/r8/desugar/constantdynamic/MultipleTypesConstantDynamicTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/constantdynamic/MultipleTypesConstantDynamicTest.java
@@ -4,19 +4,16 @@
 package com.android.tools.r8.desugar.constantdynamic;
 
 import static com.android.tools.r8.DiagnosticsMatcher.diagnosticMessage;
-import static com.android.tools.r8.DiagnosticsMatcher.diagnosticOrigin;
-import static com.android.tools.r8.OriginMatcher.hasParent;
-import static org.hamcrest.CoreMatchers.allOf;
 import static org.hamcrest.CoreMatchers.containsString;
 import static org.junit.Assert.assertThrows;
 import static org.junit.Assume.assumeTrue;
 
 import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.DesugarTestConfiguration;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestRuntime.CfVm;
 import com.android.tools.r8.cf.CfVersion;
-import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.StringUtils;
 import java.io.IOException;
@@ -41,6 +38,7 @@
 
   private static final String EXPECTED_OUTPUT =
       StringUtils.lines("true", "true", "true", "true", "true");
+  private static final Class<?> MAIN_CLASS = A.class;
 
   @Test
   public void testReference() throws Exception {
@@ -55,23 +53,34 @@
   }
 
   @Test
+  public void TestD8Cf() throws Exception {
+    testForDesugaring(parameters)
+        .addProgramClassFileData(getTransformedClasses())
+        .run(parameters.getRuntime(), MAIN_CLASS)
+        .applyIf(
+            // When not desugaring the CF code requires JDK 11.
+            DesugarTestConfiguration::isNotDesugared,
+            r -> {
+              if (parameters.isCfRuntime()
+                  && parameters.getRuntime().asCf().isNewerThanOrEqual(CfVm.JDK11)) {
+                r.assertSuccessWithOutput(EXPECTED_OUTPUT);
+              } else {
+                r.assertFailureWithErrorThatThrows(UnsupportedClassVersionError.class);
+              }
+            })
+        .applyIf(
+            DesugarTestConfiguration::isDesugared, r -> r.assertSuccessWithOutput(EXPECTED_OUTPUT));
+  }
+
+  @Test
   public void testD8() throws Exception {
     assumeTrue(parameters.isDexRuntime());
 
-    assertThrows(
-        CompilationFailedException.class,
-        () ->
-            testForD8(parameters.getBackend())
-                .addProgramClassFileData(getTransformedClasses())
-                .setMinApi(parameters.getApiLevel())
-                .compileWithExpectedDiagnostics(
-                    diagnostics -> {
-                      diagnostics.assertOnlyErrors();
-                      diagnostics.assertErrorsMatch(
-                          allOf(
-                              diagnosticMessage(containsString("Unsupported dynamic constant")),
-                              diagnosticOrigin(hasParent(Origin.unknown()))));
-                    }));
+    testForD8(parameters.getBackend())
+        .addProgramClassFileData(getTransformedClasses())
+        .setMinApi(parameters.getApiLevel())
+        .run(parameters.getRuntime(), MAIN_CLASS)
+        .assertSuccessWithOutput(EXPECTED_OUTPUT);
   }
 
   @Test
@@ -79,21 +88,34 @@
     assumeTrue(
         parameters.getRuntime().isDex() || parameters.getApiLevel().isEqualTo(AndroidApiLevel.B));
 
-    assertThrows(
-        CompilationFailedException.class,
-        () ->
-            testForR8(parameters.getBackend())
-                .addProgramClassFileData(getTransformedClasses())
-                .setMinApi(parameters.getApiLevel())
-                .addKeepMainRule(A.class)
-                .compileWithExpectedDiagnostics(
-                    diagnostics -> {
-                      diagnostics.assertOnlyErrors();
-                      diagnostics.assertErrorsMatch(
-                          allOf(
-                              diagnosticMessage(containsString("Unsupported dynamic constant")),
-                              diagnosticOrigin(hasParent(Origin.unknown()))));
-                    }));
+    testForR8(parameters.getBackend())
+        .addProgramClassFileData(getTransformedClasses())
+        .setMinApi(parameters.getApiLevel())
+        .addKeepMainRule(MAIN_CLASS)
+        // TODO(b/198142613): There should not be a warnings on class references which are
+        //  desugared away.
+        .applyIf(
+            parameters.getApiLevel().isLessThan(AndroidApiLevel.O),
+            b -> b.addDontWarn("java.lang.invoke.MethodHandles$Lookup"))
+        // TODO(b/198142625): Support CONSTANT_Dynamic output for class files.
+        .applyIf(
+            parameters.isCfRuntime(),
+            r -> {
+              assertThrows(
+                  CompilationFailedException.class,
+                  () ->
+                      r.compileWithExpectedDiagnostics(
+                          diagnostics -> {
+                            diagnostics.assertOnlyErrors();
+                            diagnostics.assertErrorsMatch(
+                                diagnosticMessage(
+                                    containsString(
+                                        "Unsupported dynamic constant (not desugaring)")));
+                          }));
+            },
+            r ->
+                r.run(parameters.getRuntime(), MAIN_CLASS)
+                    .assertSuccessWithOutput(EXPECTED_OUTPUT));
   }
 
   private byte[] getTransformedClasses() throws IOException {
diff --git a/src/test/java/com/android/tools/r8/desugar/constantdynamic/SharedBootstrapMethodConstantDynamicTest.java b/src/test/java/com/android/tools/r8/desugar/constantdynamic/SharedBootstrapMethodConstantDynamicTest.java
index 24c9ed8..355a5bd 100644
--- a/src/test/java/com/android/tools/r8/desugar/constantdynamic/SharedBootstrapMethodConstantDynamicTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/constantdynamic/SharedBootstrapMethodConstantDynamicTest.java
@@ -44,6 +44,7 @@
 
   private static final String EXPECTED_OUTPUT =
       StringUtils.lines("true", "true", "true", "true", "true");
+  private static final Class<?> MAIN_CLASS = A.class;
 
   @Test
   public void testReference() throws Exception {
@@ -53,11 +54,28 @@
 
     testForJvm()
         .addProgramClassFileData(getTransformedClasses())
-        .run(parameters.getRuntime(), A.class)
+        .run(parameters.getRuntime(), MAIN_CLASS)
         .assertSuccessWithOutput(EXPECTED_OUTPUT);
   }
 
   @Test
+  public void TestD8Cf() throws Exception {
+    assertThrows(
+        CompilationFailedException.class,
+        () ->
+            testForD8(Backend.CF)
+                .addProgramClassFileData(getTransformedClasses())
+                .setMinApi(parameters.getApiLevel())
+                .compileWithExpectedDiagnostics(
+                    diagnostics -> {
+                      diagnostics.assertOnlyErrors();
+                      diagnostics.assertErrorsMatch(
+                          diagnosticMessage(
+                              containsString("Unsupported dynamic constant (different owner)")));
+                    }));
+  }
+
+  @Test
   public void testD8() throws Exception {
     assumeTrue(parameters.isDexRuntime());
 
@@ -72,7 +90,8 @@
                       diagnostics.assertOnlyErrors();
                       diagnostics.assertErrorsMatch(
                           allOf(
-                              diagnosticMessage(containsString("Unsupported dynamic constant")),
+                              diagnosticMessage(
+                                  containsString("Unsupported dynamic constant (different owner)")),
                               diagnosticOrigin(hasParent(Origin.unknown()))));
                     }));
   }
@@ -87,13 +106,14 @@
             testForR8(parameters.getBackend())
                 .addProgramClassFileData(getTransformedClasses())
                 .setMinApi(parameters.getApiLevel())
-                .addKeepMainRule(A.class)
+                .addKeepMainRule(MAIN_CLASS)
                 .compileWithExpectedDiagnostics(
                     diagnostics -> {
                       diagnostics.assertOnlyErrors();
                       diagnostics.assertErrorsMatch(
                           allOf(
-                              diagnosticMessage(containsString("Unsupported dynamic constant")),
+                              diagnosticMessage(
+                                  containsString("Unsupported dynamic constant (different owner)")),
                               diagnosticOrigin(hasParent(Origin.unknown()))));
                     }));
   }
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/LintFilesTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/LintFilesTest.java
index f253398..5e1b8c0 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/LintFilesTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/LintFilesTest.java
@@ -26,7 +26,6 @@
 import java.util.ArrayList;
 import java.util.Comparator;
 import java.util.List;
-import org.junit.Assume;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
@@ -113,10 +112,6 @@
 
   @Test
   public void testFileContent() throws Exception {
-    // Test require r8.jar not r8lib.jar, as the class com.android.tools.r8.GenerateLintFiles in
-    // not kept.
-    Assume.assumeTrue(!ToolHelper.isTestingR8Lib());
-
     Path directory = temp.newFolder().toPath();
     GenerateLintFiles.main(
         new String[] {
@@ -131,6 +126,9 @@
             .parse(StringResource.fromFile(ToolHelper.getDesugarLibJsonForTesting()));
 
     for (AndroidApiLevel apiLevel : AndroidApiLevel.values()) {
+      if (apiLevel == AndroidApiLevel.UNKNOWN) {
+        continue;
+      }
       Path compileApiLevelDirectory = directory.resolve("compile_api_level_" + apiLevel.getLevel());
       if (apiLevel.getLevel()
           < desugaredLibraryConfiguration.getRequiredCompilationApiLevel().getLevel()) {
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/r8ondex/R8CompiledThroughDexTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/r8ondex/R8CompiledThroughDexTest.java
index bb66f2e..01eb3b8 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/r8ondex/R8CompiledThroughDexTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/r8ondex/R8CompiledThroughDexTest.java
@@ -12,13 +12,13 @@
 import com.android.tools.r8.R8;
 import com.android.tools.r8.R8Command;
 import com.android.tools.r8.R8Command.Builder;
+import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.TestRuntime;
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.ToolHelper.DexVm.Version;
 import com.android.tools.r8.ToolHelper.ProcessResult;
-import com.android.tools.r8.cf.bootstrap.BootstrapCurrentEqualityTest;
 import com.android.tools.r8.desugar.desugaredlibrary.DesugaredLibraryTestBase;
 import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.Pair;
@@ -166,7 +166,7 @@
               + " differ from the external run which uses "
               + r8jar
               + ". If up-to-date, the likely cause of this error is that R8 is non-deterministic.",
-          BootstrapCurrentEqualityTest.filesAreEqual(outputThroughCf, outputThroughCfExternal));
+          TestBase.filesAreEqual(outputThroughCf, outputThroughCfExternal));
     }
 
     // Finally compile R8 on the ART runtime using the already compiled DEX version of R8.
@@ -194,7 +194,7 @@
       assertEquals(0, artProcessResult.exitCode);
       assertTrue(
           "The output of R8/JVM in-process and R8/ART external differ.",
-          BootstrapCurrentEqualityTest.filesAreEqual(outputThroughCf, outputThroughDex));
+          TestBase.filesAreEqual(outputThroughCf, outputThroughDex));
     }
   }
 }
diff --git a/src/test/java/com/android/tools/r8/desugar/enclosingmethod/EnclosingMethodRewriteTest.java b/src/test/java/com/android/tools/r8/desugar/enclosingmethod/EnclosingMethodRewriteTest.java
index e7cc9c9..9a47de9 100644
--- a/src/test/java/com/android/tools/r8/desugar/enclosingmethod/EnclosingMethodRewriteTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/enclosingmethod/EnclosingMethodRewriteTest.java
@@ -6,7 +6,6 @@
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNull;
 import static org.junit.Assume.assumeTrue;
 
 import com.android.tools.r8.TestBase;
@@ -73,8 +72,6 @@
         "interface " + A.class.getTypeName(), "public int " + A.class.getTypeName() + ".def()", "42"
       };
 
-  private final String[] EXPECTED_FULL = new String[] {"null", "null", "42"};
-
   @Parameterized.Parameters(name = "{0}")
   public static TestParametersCollection data() {
     return getTestParameters().withAllRuntimesAndApiLevels().build();
@@ -102,7 +99,7 @@
         .addKeepAttributeInnerClassesAndEnclosingMethod()
         .setMinApi(parameters.getApiLevel())
         .compile()
-        .inspect(inspector -> inspect(inspector, true))
+        .inspect(this::inspect)
         .run(parameters.getRuntime(), MAIN)
         .applyIf(
             parameters.canUseDefaultAndStaticInterfaceMethods(),
@@ -126,8 +123,7 @@
         .addKeepAttributeInnerClassesAndEnclosingMethod()
         .setMinApi(parameters.getApiLevel())
         .compile()
-        .inspect(
-            inspector -> inspect(inspector, parameters.canUseDefaultAndStaticInterfaceMethods()))
+        .inspect(this::inspect)
         .run(parameters.getRuntime(), MAIN)
         .applyIf(
             parameters.canUseDefaultAndStaticInterfaceMethods(),
@@ -139,25 +135,21 @@
                 result.assertSuccessWithOutputLines(EXPECTED);
               }
             },
-            result -> result.assertSuccessWithOutputLines(EXPECTED_FULL));
+            result -> result.assertSuccessWithOutputLines(EXPECTED_CC));
   }
 
-  private void inspect(CodeInspector inspector, boolean hasEnclosingMethod) {
+  private void inspect(CodeInspector inspector) {
     ClassSubject cImplSubject = inspector.clazz(A.class.getTypeName() + "$1");
     assertThat(cImplSubject, isPresent());
     ClassSubject enclosingClassSubject =
         parameters.canUseDefaultAndStaticInterfaceMethods()
             ? inspector.clazz(A.class.getTypeName())
-            : inspector.clazz(A.class.getTypeName() + "$-CC");
+            : inspector.clazz(A.class.getTypeName()).toCompanionClass();
     assertThat(enclosingClassSubject, isPresent());
     EnclosingMethodAttribute enclosingMethodAttribute =
         cImplSubject.getDexProgramClass().getEnclosingMethodAttribute();
-    if (hasEnclosingMethod) {
-      assertEquals(
-          enclosingClassSubject.getDexProgramClass().getType(),
-          enclosingMethodAttribute.getEnclosingMethod().getHolderType());
-    } else {
-      assertNull(enclosingMethodAttribute);
-    }
+    assertEquals(
+        enclosingClassSubject.getDexProgramClass().getType(),
+        enclosingMethodAttribute.getEnclosingMethod().getHolderType());
   }
 }
diff --git a/src/test/java/com/android/tools/r8/desugar/lambdas/OverlappingLambdaMethodInSubclassWithSameNameTest.java b/src/test/java/com/android/tools/r8/desugar/lambdas/OverlappingLambdaMethodInSubclassWithSameNameTest.java
new file mode 100644
index 0000000..9f5fa37
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/lambdas/OverlappingLambdaMethodInSubclassWithSameNameTest.java
@@ -0,0 +1,73 @@
+// Copyright (c) 2021, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.desugar.lambdas;
+
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.desugar.lambdas.b197625454.OverlappingLambdaMethodInSubclassWithSameNameTestA;
+import com.android.tools.r8.desugar.lambdas.b197625454.OverlappingLambdaMethodInSubclassWithSameNameTestB;
+import com.android.tools.r8.utils.StringUtils;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+
+// See b/197625454 for details.
+@RunWith(Parameterized.class)
+public class OverlappingLambdaMethodInSubclassWithSameNameTest extends TestBase {
+
+  static final String EXPECTED =
+      StringUtils.lines("Superclass lambda: Hello!", "Superclass lambda: Hello!");
+
+  private static final Class<?> MAIN_CLASS =
+      com.android.tools.r8.desugar.lambdas.b197625454.subpackage
+          .OverlappingLambdaMethodInSubclassWithSameNameTestA.class;
+
+  @Parameter() public TestParameters parameters;
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimes().withAllApiLevelsAlsoForCf().build();
+  }
+
+  @Test
+  public void testJvm() throws Exception {
+    assumeTrue(parameters.isCfRuntime());
+    testForRuntime(parameters)
+        .addProgramClasses(
+            MAIN_CLASS,
+            OverlappingLambdaMethodInSubclassWithSameNameTestA.class,
+            OverlappingLambdaMethodInSubclassWithSameNameTestB.class)
+        .run(parameters.getRuntime(), MAIN_CLASS)
+        .assertSuccessWithOutput(EXPECTED);
+  }
+
+  @Test
+  public void testD8() throws Exception {
+    testForD8(parameters.getBackend())
+        .addProgramClasses(
+            MAIN_CLASS,
+            OverlappingLambdaMethodInSubclassWithSameNameTestA.class,
+            OverlappingLambdaMethodInSubclassWithSameNameTestB.class)
+        .setMinApi(parameters.getApiLevel())
+        .run(parameters.getRuntime(), MAIN_CLASS)
+        .assertSuccessWithOutput(EXPECTED);
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    testForR8(parameters.getBackend())
+        .addProgramClasses(
+            MAIN_CLASS,
+            OverlappingLambdaMethodInSubclassWithSameNameTestA.class,
+            OverlappingLambdaMethodInSubclassWithSameNameTestB.class)
+        .setMinApi(parameters.getApiLevel())
+        .addKeepMainRule(MAIN_CLASS)
+        .run(parameters.getRuntime(), MAIN_CLASS)
+        .assertSuccessWithOutput(EXPECTED);
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/desugar/lambdas/b197625454/OverlappingLambdaMethodInSubclassWithSameNameTestA.java b/src/test/java/com/android/tools/r8/desugar/lambdas/b197625454/OverlappingLambdaMethodInSubclassWithSameNameTestA.java
new file mode 100644
index 0000000..25be7d6
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/lambdas/b197625454/OverlappingLambdaMethodInSubclassWithSameNameTestA.java
@@ -0,0 +1,24 @@
+// Copyright (c) 2021, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.desugar.lambdas.b197625454;
+
+// Same base class name as the class it extends. See b/197625454 for details.
+public class OverlappingLambdaMethodInSubclassWithSameNameTestA
+    extends com.android.tools.r8.desugar.lambdas.b197625454.subpackage
+        .OverlappingLambdaMethodInSubclassWithSameNameTestA {
+
+  @Override
+  public void myMethod() {
+    super.myMethod();
+    if (Math.random() < 0) { // always false
+      Runnable local = () -> System.out.println("Subclass lambda: " + getString());
+    }
+    r.run();
+  }
+
+  @Override
+  public String getString() {
+    return "Hello!";
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/desugar/lambdas/b197625454/OverlappingLambdaMethodInSubclassWithSameNameTestB.java b/src/test/java/com/android/tools/r8/desugar/lambdas/b197625454/OverlappingLambdaMethodInSubclassWithSameNameTestB.java
new file mode 100644
index 0000000..45193ef
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/lambdas/b197625454/OverlappingLambdaMethodInSubclassWithSameNameTestB.java
@@ -0,0 +1,24 @@
+// Copyright (c) 2021, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.desugar.lambdas.b197625454;
+
+import com.android.tools.r8.desugar.lambdas.b197625454.subpackage.OverlappingLambdaMethodInSubclassWithSameNameTestA;
+
+public class OverlappingLambdaMethodInSubclassWithSameNameTestB
+    extends OverlappingLambdaMethodInSubclassWithSameNameTestA {
+
+  @Override
+  public void myMethod() {
+    super.myMethod();
+    if (Math.random() < 0) { // always false
+      Runnable local = () -> System.out.println("Subclass lambda: " + getString());
+    }
+    r.run();
+  }
+
+  @Override
+  public String getString() {
+    return "Hello!";
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/desugar/lambdas/b197625454/subpackage/OverlappingLambdaMethodInSubclassWithSameNameTestA.java b/src/test/java/com/android/tools/r8/desugar/lambdas/b197625454/subpackage/OverlappingLambdaMethodInSubclassWithSameNameTestA.java
new file mode 100644
index 0000000..3f8bdcd
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/lambdas/b197625454/subpackage/OverlappingLambdaMethodInSubclassWithSameNameTestA.java
@@ -0,0 +1,24 @@
+// Copyright (c) 2021, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.desugar.lambdas.b197625454.subpackage;
+
+import com.android.tools.r8.desugar.lambdas.b197625454.OverlappingLambdaMethodInSubclassWithSameNameTestB;
+
+public abstract class OverlappingLambdaMethodInSubclassWithSameNameTestA {
+
+  public Runnable r;
+
+  public void myMethod() {
+    r = () -> System.out.println("Superclass lambda: " + getString());
+  }
+
+  public abstract String getString();
+
+  public static void main(String[] args) {
+    new com.android.tools.r8.desugar.lambdas.b197625454
+            .OverlappingLambdaMethodInSubclassWithSameNameTestA()
+        .myMethod();
+    new OverlappingLambdaMethodInSubclassWithSameNameTestB().myMethod();
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/desugar/records/EmptyRecordTest.java b/src/test/java/com/android/tools/r8/desugar/records/EmptyRecordTest.java
index 389744e..10e2d10 100644
--- a/src/test/java/com/android/tools/r8/desugar/records/EmptyRecordTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/records/EmptyRecordTest.java
@@ -55,7 +55,6 @@
         .addProgramClassFileData(PROGRAM_DATA)
         .setMinApi(parameters.getApiLevel())
         .addOptionsModification(TestingOptions::allowExperimentClassFileVersion)
-        .addOptionsModification(opt -> opt.testing.enableExperimentalRecordDesugaring = true)
         .compile()
         .run(parameters.getRuntime(), MAIN_TYPE)
         .assertSuccessWithOutput(EXPECTED_RESULT);
@@ -72,7 +71,6 @@
               .addKeepMainRule(MAIN_TYPE)
               .addLibraryFiles(RecordTestUtils.getJdk15LibraryFiles(temp))
               .addOptionsModification(TestingOptions::allowExperimentClassFileVersion)
-              .addOptionsModification(opt -> opt.testing.enableExperimentalRecordDesugaring = true)
               .compile()
               .writeToZip();
       RecordTestUtils.assertRecordsAreRecords(output);
@@ -89,7 +87,6 @@
         .addKeepRules(RECORD_KEEP_RULE)
         .addKeepMainRule(MAIN_TYPE)
         .addOptionsModification(TestingOptions::allowExperimentClassFileVersion)
-        .addOptionsModification(opt -> opt.testing.enableExperimentalRecordDesugaring = true)
         .compile()
         .run(parameters.getRuntime(), MAIN_TYPE)
         .assertSuccessWithOutput(EXPECTED_RESULT);
diff --git a/src/test/java/com/android/tools/r8/desugar/records/InvalidRecordAttributeTest.java b/src/test/java/com/android/tools/r8/desugar/records/InvalidRecordAttributeTest.java
deleted file mode 100644
index 7064af6..0000000
--- a/src/test/java/com/android/tools/r8/desugar/records/InvalidRecordAttributeTest.java
+++ /dev/null
@@ -1,111 +0,0 @@
-// Copyright (c) 2021, the R8 project authors. Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
-
-package com.android.tools.r8.desugar.records;
-
-import static com.android.tools.r8.DiagnosticsMatcher.diagnosticMessage;
-import static org.hamcrest.CoreMatchers.containsString;
-import static org.junit.Assert.assertThrows;
-
-import com.android.tools.r8.CompilationFailedException;
-import com.android.tools.r8.TestBase;
-import com.android.tools.r8.TestDiagnosticMessages;
-import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.TestRuntime.CfRuntime;
-import com.android.tools.r8.utils.AndroidApiLevel;
-import java.util.List;
-import org.junit.Assume;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.Parameterized;
-import org.junit.runners.Parameterized.Parameters;
-
-/** Remove this test when Records are supported by default. */
-@RunWith(Parameterized.class)
-public class InvalidRecordAttributeTest extends TestBase {
-
-  private final TestParameters parameters;
-  private final Backend backend;
-
-  private static final String EMPTY_RECORD = "EmptyRecord";
-  private static final byte[][] EMPTY_RECORD_PROGRAM_DATA =
-      RecordTestUtils.getProgramData(EMPTY_RECORD);
-  private static final String SIMPLE_RECORD = "SimpleRecord";
-  private static final byte[][] SIMPLE_RECORD_PROGRAM_DATA =
-      RecordTestUtils.getProgramData(SIMPLE_RECORD);
-
-  @Parameters(name = "{0} back: {1}")
-  public static List<Object[]> data() {
-    // TODO(b/174431251): This should be replaced with .withCfRuntimes(start = jdk16).
-    return buildParameters(
-        getTestParameters().withCustomRuntime(CfRuntime.getCheckedInJdk16()).build(),
-        Backend.values());
-  }
-
-  public InvalidRecordAttributeTest(TestParameters parameters, Backend backend) {
-    this.parameters = parameters;
-    this.backend = backend;
-  }
-
-  @Test
-  public void testD8EmptyRecord() throws Exception {
-    Assume.assumeTrue(backend.isDex());
-    assertThrows(
-        CompilationFailedException.class,
-        () -> {
-          testForD8(backend)
-              .addProgramClassFileData(EMPTY_RECORD_PROGRAM_DATA)
-              .setMinApi(AndroidApiLevel.B)
-              .compileWithExpectedDiagnostics(
-                  InvalidRecordAttributeTest::assertUnsupportedRecordError);
-        });
-  }
-
-  @Test
-  public void testD8SimpleRecord() throws Exception {
-    Assume.assumeTrue(backend.isDex());
-    assertThrows(
-        CompilationFailedException.class,
-        () -> {
-          testForD8(backend)
-              .addProgramClassFileData(RecordTestUtils.getProgramData(SIMPLE_RECORD))
-              .setMinApi(AndroidApiLevel.B)
-              .compileWithExpectedDiagnostics(
-                  InvalidRecordAttributeTest::assertUnsupportedRecordError);
-        });
-  }
-
-  @Test
-  public void testR8EmptyRecord() throws Exception {
-    assertThrows(
-        CompilationFailedException.class,
-        () -> {
-          testForR8(backend)
-              .addProgramClassFileData(EMPTY_RECORD_PROGRAM_DATA)
-              .setMinApi(AndroidApiLevel.B)
-              .addKeepMainRule(RecordTestUtils.getMainType(EMPTY_RECORD))
-              .compileWithExpectedDiagnostics(
-                  InvalidRecordAttributeTest::assertUnsupportedRecordError);
-        });
-  }
-
-  @Test
-  public void testR8SimpleRecord() throws Exception {
-    assertThrows(
-        CompilationFailedException.class,
-        () -> {
-          testForR8(backend)
-              .addProgramClassFileData(SIMPLE_RECORD_PROGRAM_DATA)
-              .setMinApi(AndroidApiLevel.B)
-              .addKeepMainRule(RecordTestUtils.getMainType(SIMPLE_RECORD))
-              .compileWithExpectedDiagnostics(
-                  InvalidRecordAttributeTest::assertUnsupportedRecordError);
-        });
-  }
-
-  private static void assertUnsupportedRecordError(TestDiagnosticMessages diagnostics) {
-    diagnostics.assertErrorThatMatches(
-        diagnosticMessage(containsString("Records are not supported")));
-  }
-}
diff --git a/src/test/java/com/android/tools/r8/desugar/records/RecordInstanceOfTest.java b/src/test/java/com/android/tools/r8/desugar/records/RecordInstanceOfTest.java
index 08b0e85..d6639de 100644
--- a/src/test/java/com/android/tools/r8/desugar/records/RecordInstanceOfTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/records/RecordInstanceOfTest.java
@@ -55,7 +55,7 @@
         .addProgramClassFileData(PROGRAM_DATA)
         .setMinApi(parameters.getApiLevel())
         .addOptionsModification(TestingOptions::allowExperimentClassFileVersion)
-        .addOptionsModification(opt -> opt.testing.enableExperimentalRecordDesugaring = true)
+        
         .compile()
         .run(parameters.getRuntime(), MAIN_TYPE)
         .assertSuccessWithOutput(EXPECTED_RESULT);
@@ -72,7 +72,6 @@
               .addKeepMainRule(MAIN_TYPE)
               .addLibraryFiles(RecordTestUtils.getJdk15LibraryFiles(temp))
               .addOptionsModification(TestingOptions::allowExperimentClassFileVersion)
-              .addOptionsModification(opt -> opt.testing.enableExperimentalRecordDesugaring = true)
               .compile()
               .writeToZip();
       RecordTestUtils.assertRecordsAreRecords(output);
@@ -89,7 +88,6 @@
         .addKeepRules(RECORD_KEEP_RULE)
         .addKeepMainRule(MAIN_TYPE)
         .addOptionsModification(TestingOptions::allowExperimentClassFileVersion)
-        .addOptionsModification(opt -> opt.testing.enableExperimentalRecordDesugaring = true)
         .compile()
         .run(parameters.getRuntime(), MAIN_TYPE)
         .assertSuccessWithOutput(EXPECTED_RESULT);
diff --git a/src/test/java/com/android/tools/r8/desugar/records/RecordInvokeCustomSplitDesugaringTest.java b/src/test/java/com/android/tools/r8/desugar/records/RecordInvokeCustomSplitDesugaringTest.java
new file mode 100644
index 0000000..0383285
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/records/RecordInvokeCustomSplitDesugaringTest.java
@@ -0,0 +1,89 @@
+// Copyright (c) 2021, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.desugar.records;
+
+import static com.android.tools.r8.desugar.records.RecordTestUtils.RECORD_KEEP_RULE;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.utils.InternalOptions.TestingOptions;
+import com.android.tools.r8.utils.StringUtils;
+import java.nio.file.Path;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class RecordInvokeCustomSplitDesugaringTest extends TestBase {
+
+  private static final String RECORD_NAME = "RecordInvokeCustom";
+  private static final byte[][] PROGRAM_DATA = RecordTestUtils.getProgramData(RECORD_NAME);
+  private static final String MAIN_TYPE = RecordTestUtils.getMainType(RECORD_NAME);
+  private static final String EXPECTED_RESULT =
+      StringUtils.lines(
+          "Empty[]",
+          "true",
+          "true",
+          "true",
+          "true",
+          "true",
+          "false",
+          "true",
+          "true",
+          "false",
+          "false",
+          "Person[name=Jane Doe, age=42]");
+
+  private final TestParameters parameters;
+
+  public RecordInvokeCustomSplitDesugaringTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Parameterized.Parameters(name = "{0}")
+  public static List<Object[]> data() {
+    return buildParameters(
+        getTestParameters().withDexRuntimes().withAllApiLevelsAlsoForCf().build());
+  }
+
+  @Test
+  public void testD8() throws Exception {
+    Path desugared =
+        testForD8(Backend.CF)
+            .addProgramClassFileData(PROGRAM_DATA)
+            .setMinApi(parameters.getApiLevel())
+            .addOptionsModification(TestingOptions::allowExperimentClassFileVersion)
+            .compile()
+            .writeToZip();
+    testForD8(parameters.getBackend())
+        .addProgramFiles(desugared)
+        .setMinApi(parameters.getApiLevel())
+        .addOptionsModification(TestingOptions::allowExperimentClassFileVersion)
+        .compile()
+        .run(parameters.getRuntime(), MAIN_TYPE)
+        .assertSuccessWithOutput(EXPECTED_RESULT);
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    Path desugared =
+        testForD8(Backend.CF)
+            .addProgramClassFileData(PROGRAM_DATA)
+            .setMinApi(parameters.getApiLevel())
+            .addOptionsModification(TestingOptions::allowExperimentClassFileVersion)
+            .compile()
+            .writeToZip();
+    testForR8(parameters.getBackend())
+        .addProgramFiles(desugared)
+        .setMinApi(parameters.getApiLevel())
+        .addKeepRules(RECORD_KEEP_RULE)
+        .addKeepMainRule(MAIN_TYPE)
+        .addOptionsModification(TestingOptions::allowExperimentClassFileVersion)
+        .compile()
+        .run(parameters.getRuntime(), MAIN_TYPE)
+        .assertSuccessWithOutput(EXPECTED_RESULT);
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/desugar/records/RecordInvokeCustomTest.java b/src/test/java/com/android/tools/r8/desugar/records/RecordInvokeCustomTest.java
index 9bf3327..dac4ff8 100644
--- a/src/test/java/com/android/tools/r8/desugar/records/RecordInvokeCustomTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/records/RecordInvokeCustomTest.java
@@ -68,7 +68,7 @@
         .addProgramClassFileData(PROGRAM_DATA)
         .setMinApi(parameters.getApiLevel())
         .addOptionsModification(TestingOptions::allowExperimentClassFileVersion)
-        .addOptionsModification(opt -> opt.testing.enableExperimentalRecordDesugaring = true)
+        
         .compile()
         .run(parameters.getRuntime(), MAIN_TYPE)
         .assertSuccessWithOutput(EXPECTED_RESULT);
@@ -85,7 +85,6 @@
               .addKeepMainRule(MAIN_TYPE)
               .addLibraryFiles(RecordTestUtils.getJdk15LibraryFiles(temp))
               .addOptionsModification(TestingOptions::allowExperimentClassFileVersion)
-              .addOptionsModification(opt -> opt.testing.enableExperimentalRecordDesugaring = true)
               .compile()
               .writeToZip();
       RecordTestUtils.assertRecordsAreRecords(output);
@@ -102,7 +101,6 @@
         .addKeepRules(RECORD_KEEP_RULE)
         .addKeepMainRule(MAIN_TYPE)
         .addOptionsModification(TestingOptions::allowExperimentClassFileVersion)
-        .addOptionsModification(opt -> opt.testing.enableExperimentalRecordDesugaring = true)
         .compile()
         .run(parameters.getRuntime(), MAIN_TYPE)
         .assertSuccessWithOutput(EXPECTED_RESULT);
diff --git a/src/test/java/com/android/tools/r8/desugar/records/RecordMergeTest.java b/src/test/java/com/android/tools/r8/desugar/records/RecordMergeTest.java
index 240e128..ef11ef4 100644
--- a/src/test/java/com/android/tools/r8/desugar/records/RecordMergeTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/records/RecordMergeTest.java
@@ -56,7 +56,6 @@
             .addProgramClassFileData(PROGRAM_DATA_1)
             .setMinApi(parameters.getApiLevel())
             .addOptionsModification(TestingOptions::allowExperimentClassFileVersion)
-            .addOptionsModification(opt -> opt.testing.enableExperimentalRecordDesugaring = true)
             .compile()
             .writeToZip();
     Path output2 =
@@ -64,7 +63,6 @@
             .addProgramClassFileData(PROGRAM_DATA_2)
             .setMinApi(parameters.getApiLevel())
             .addOptionsModification(TestingOptions::allowExperimentClassFileVersion)
-            .addOptionsModification(opt -> opt.testing.enableExperimentalRecordDesugaring = true)
             .compile()
             .writeToZip();
     D8TestCompileResult result =
@@ -84,7 +82,6 @@
             .addProgramClassFileData(PROGRAM_DATA_1)
             .setMinApi(parameters.getApiLevel())
             .addOptionsModification(TestingOptions::allowExperimentClassFileVersion)
-            .addOptionsModification(opt -> opt.testing.enableExperimentalRecordDesugaring = true)
             .compile()
             .writeToZip();
     D8TestCompileResult result =
@@ -93,7 +90,6 @@
             .addProgramClassFileData(PROGRAM_DATA_2)
             .setMinApi(parameters.getApiLevel())
             .addOptionsModification(TestingOptions::allowExperimentClassFileVersion)
-            .addOptionsModification(opt -> opt.testing.enableExperimentalRecordDesugaring = true)
             .compile();
     result.run(parameters.getRuntime(), MAIN_TYPE_1).assertSuccessWithOutput(EXPECTED_RESULT_1);
     result.run(parameters.getRuntime(), MAIN_TYPE_2).assertSuccessWithOutput(EXPECTED_RESULT_2);
diff --git a/src/test/java/com/android/tools/r8/desugar/records/RecordReflectionTest.java b/src/test/java/com/android/tools/r8/desugar/records/RecordReflectionTest.java
index 9663f43..e67ccd5 100644
--- a/src/test/java/com/android/tools/r8/desugar/records/RecordReflectionTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/records/RecordReflectionTest.java
@@ -67,7 +67,6 @@
             .addKeepMainRule(MAIN_TYPE)
             .addLibraryFiles(RecordTestUtils.getJdk15LibraryFiles(temp))
             .addOptionsModification(TestingOptions::allowExperimentClassFileVersion)
-            .addOptionsModification(opt -> opt.testing.enableExperimentalRecordDesugaring = true)
             .compile()
             .writeToZip();
     RecordTestUtils.assertRecordsAreRecords(output);
diff --git a/src/test/java/com/android/tools/r8/desugar/records/RecordTestUtils.java b/src/test/java/com/android/tools/r8/desugar/records/RecordTestUtils.java
index 86e30a6..bf74698 100644
--- a/src/test/java/com/android/tools/r8/desugar/records/RecordTestUtils.java
+++ b/src/test/java/com/android/tools/r8/desugar/records/RecordTestUtils.java
@@ -99,8 +99,7 @@
   }
 
   public static void assertRecordsAreRecords(Path output) throws IOException {
-    CodeInspector inspector =
-        new CodeInspector(output, opt -> opt.testing.enableExperimentalRecordDesugaring = true);
+    CodeInspector inspector = new CodeInspector(output);
     for (FoundClassSubject clazz : inspector.allClasses()) {
       if (clazz.getDexProgramClass().superType.toString().equals("java.lang.Record")) {
         assertTrue(clazz.getDexProgramClass().isRecord());
diff --git a/src/test/java/com/android/tools/r8/desugar/records/RecordWithMembersTest.java b/src/test/java/com/android/tools/r8/desugar/records/RecordWithMembersTest.java
index 36b2377..cff0cab 100644
--- a/src/test/java/com/android/tools/r8/desugar/records/RecordWithMembersTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/records/RecordWithMembersTest.java
@@ -57,7 +57,6 @@
         .addProgramClassFileData(PROGRAM_DATA)
         .setMinApi(parameters.getApiLevel())
         .addOptionsModification(TestingOptions::allowExperimentClassFileVersion)
-        .addOptionsModification(opt -> opt.testing.enableExperimentalRecordDesugaring = true)
         .compile()
         .run(parameters.getRuntime(), MAIN_TYPE)
         .assertSuccessWithOutput(EXPECTED_RESULT);
@@ -74,7 +73,6 @@
               .addKeepMainRule(MAIN_TYPE)
               .addLibraryFiles(RecordTestUtils.getJdk15LibraryFiles(temp))
               .addOptionsModification(TestingOptions::allowExperimentClassFileVersion)
-              .addOptionsModification(opt -> opt.testing.enableExperimentalRecordDesugaring = true)
               .compile()
               .writeToZip();
       RecordTestUtils.assertRecordsAreRecords(output);
@@ -91,7 +89,6 @@
         .addKeepRules(RECORD_KEEP_RULE)
         .addKeepMainRule(MAIN_TYPE)
         .addOptionsModification(TestingOptions::allowExperimentClassFileVersion)
-        .addOptionsModification(opt -> opt.testing.enableExperimentalRecordDesugaring = true)
         .compile()
         .run(parameters.getRuntime(), MAIN_TYPE)
         .assertSuccessWithOutput(EXPECTED_RESULT);
diff --git a/src/test/java/com/android/tools/r8/desugar/records/SimpleRecordTest.java b/src/test/java/com/android/tools/r8/desugar/records/SimpleRecordTest.java
index 08c9468..40f8af0 100644
--- a/src/test/java/com/android/tools/r8/desugar/records/SimpleRecordTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/records/SimpleRecordTest.java
@@ -56,7 +56,6 @@
         .addProgramClassFileData(PROGRAM_DATA)
         .setMinApi(parameters.getApiLevel())
         .addOptionsModification(TestingOptions::allowExperimentClassFileVersion)
-        .addOptionsModification(opt -> opt.testing.enableExperimentalRecordDesugaring = true)
         .compile()
         .run(parameters.getRuntime(), MAIN_TYPE)
         .assertSuccessWithOutput(EXPECTED_RESULT);
@@ -73,7 +72,6 @@
               .addKeepMainRule(MAIN_TYPE)
               .addLibraryFiles(RecordTestUtils.getJdk15LibraryFiles(temp))
               .addOptionsModification(TestingOptions::allowExperimentClassFileVersion)
-              .addOptionsModification(opt -> opt.testing.enableExperimentalRecordDesugaring = true)
               .compile()
               .writeToZip();
       RecordTestUtils.assertRecordsAreRecords(output);
@@ -90,7 +88,6 @@
         .addKeepRules(RECORD_KEEP_RULE)
         .addKeepMainRule(MAIN_TYPE)
         .addOptionsModification(TestingOptions::allowExperimentClassFileVersion)
-        .addOptionsModification(opt -> opt.testing.enableExperimentalRecordDesugaring = true)
         .compile()
         .run(parameters.getRuntime(), MAIN_TYPE)
         .assertSuccessWithOutput(EXPECTED_RESULT);
diff --git a/src/test/java/com/android/tools/r8/desugar/staticinterfacemethod/InvokeStaticDesugarTest.java b/src/test/java/com/android/tools/r8/desugar/staticinterfacemethod/InvokeStaticDesugarTest.java
index 35e52b4..8cd35aa 100644
--- a/src/test/java/com/android/tools/r8/desugar/staticinterfacemethod/InvokeStaticDesugarTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/staticinterfacemethod/InvokeStaticDesugarTest.java
@@ -7,6 +7,7 @@
 import static com.android.tools.r8.desugar.staticinterfacemethod.InvokeStaticDesugarTest.Library.foo;
 import static org.hamcrest.CoreMatchers.containsString;
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 import static org.junit.Assume.assumeFalse;
 
@@ -151,12 +152,14 @@
 
   private Set<FoundMethodSubject> getSyntheticMethods(CodeInspector inspector) {
     Set<FoundMethodSubject> methods = new HashSet<>();
-    assert inspector.allClasses().stream()
-        .allMatch(
+    inspector
+        .allClasses()
+        .forEach(
             c ->
-                !SyntheticItemsTestUtils.isExternalSynthetic(c.getFinalReference())
-                    || SyntheticItemsTestUtils.isExternalStaticInterfaceCall(
-                        c.getFinalReference()));
+                assertTrue(
+                    !SyntheticItemsTestUtils.isExternalSynthetic(c.getFinalReference())
+                        || SyntheticItemsTestUtils.isExternalStaticInterfaceCall(
+                            c.getFinalReference())));
     inspector.allClasses().stream()
         .filter(c -> SyntheticItemsTestUtils.isExternalStaticInterfaceCall(c.getFinalReference()))
         .forEach(c -> methods.addAll(c.allMethods(m -> !m.isInstanceInitializer())));
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
index ee911d3..dfbbd6e 100644
--- a/src/test/java/com/android/tools/r8/desugar/staticinterfacemethod/MutuallyRecursiveMethodsTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/staticinterfacemethod/MutuallyRecursiveMethodsTest.java
@@ -35,6 +35,16 @@
         .assertSuccessWithOutput(EXPECTED);
   }
 
+  @Test
+  public void testR8() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(MutuallyRecursiveMethodsTest.class)
+        .setMinApi(parameters.getApiLevel())
+        .addKeepMainRule(TestClass.class)
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutput(EXPECTED);
+  }
+
   interface I {
     static boolean isEven(int i) {
       return i == 0 || isOdd(i - 1);
diff --git a/src/test/java/com/android/tools/r8/desugaring/interfacemethods/DefaultInterfaceMethodDesugaringWithPublicStaticResolutionInvokeVirtualTest.java b/src/test/java/com/android/tools/r8/desugaring/interfacemethods/DefaultInterfaceMethodDesugaringWithPublicStaticResolutionInvokeVirtualTest.java
index 12869e2..c960b33 100644
--- a/src/test/java/com/android/tools/r8/desugaring/interfacemethods/DefaultInterfaceMethodDesugaringWithPublicStaticResolutionInvokeVirtualTest.java
+++ b/src/test/java/com/android/tools/r8/desugaring/interfacemethods/DefaultInterfaceMethodDesugaringWithPublicStaticResolutionInvokeVirtualTest.java
@@ -102,22 +102,21 @@
   private void checkResult(TestRunResult<?> result, boolean isR8) {
     // Invalid invoke case is where the invoke-virtual targets C.m.
     if (invalidInvoke) {
-      if (parameters.isCfRuntime()) {
-        result.assertFailureWithErrorThatThrows(IncompatibleClassChangeError.class);
-        return;
-      }
-      if (parameters.getDexRuntimeVersion().isInRangeInclusive(Version.V5_1_1, Version.V7_0_0)) {
+      if (parameters.isDexRuntimeVersion(Version.V7_0_0)
+          && parameters.canUseDefaultAndStaticInterfaceMethodsWhenDesugaring()) {
+        // The v7 VM incorrectly fails to throw.
         result.assertSuccessWithOutput(EXPECTED);
-        return;
+      } else {
+        result.assertFailureWithErrorThatThrows(IncompatibleClassChangeError.class);
       }
-      result.assertFailureWithErrorThatThrows(IncompatibleClassChangeError.class);
       return;
     }
 
     if (isR8
         && parameters.isDexRuntime()
         && parameters.getDexRuntimeVersion().isNewerThan(Version.V6_0_1)
-        && parameters.getDexRuntimeVersion().isOlderThan(Version.V12_0_0)) {
+        && parameters.getDexRuntimeVersion().isOlderThan(Version.V12_0_0)
+        && parameters.canUseDefaultAndStaticInterfaceMethodsWhenDesugaring()) {
       // TODO(b/182255398): This should be EXPECTED.
       result.assertSuccessWithOutput(EXPECTED_R8);
       return;
diff --git a/src/test/java/com/android/tools/r8/desugaring/interfacemethods/DefaultInterfaceMethodDesugaringWithPublicStaticResolutionOnClassTest.java b/src/test/java/com/android/tools/r8/desugaring/interfacemethods/DefaultInterfaceMethodDesugaringWithPublicStaticResolutionOnClassTest.java
index 1684c5a..b5949f7 100644
--- a/src/test/java/com/android/tools/r8/desugaring/interfacemethods/DefaultInterfaceMethodDesugaringWithPublicStaticResolutionOnClassTest.java
+++ b/src/test/java/com/android/tools/r8/desugaring/interfacemethods/DefaultInterfaceMethodDesugaringWithPublicStaticResolutionOnClassTest.java
@@ -70,10 +70,9 @@
         .setMinApi(parameters.getApiLevel())
         .compile()
         .run(parameters.getRuntime(), TestClass.class)
-        // TODO(b/182335909): Ideally, this should also throw ICCE when desugaring.
         .applyIf(
-            !parameters.canUseDefaultAndStaticInterfaceMethodsWhenDesugaring()
-                || parameters.isDexRuntimeVersion(Version.V7_0_0),
+            parameters.canUseDefaultAndStaticInterfaceMethodsWhenDesugaring()
+                && parameters.isDexRuntimeVersion(Version.V7_0_0),
             r -> r.assertSuccessWithOutput(EXPECTED_INVALID),
             r -> r.assertFailureWithErrorThatThrows(IncompatibleClassChangeError.class));
   }
diff --git a/src/test/java/com/android/tools/r8/desugaring/interfacemethods/InvokeSuperInDefaultInterfaceMethodToNonImmediateInterfaceTest.java b/src/test/java/com/android/tools/r8/desugaring/interfacemethods/InvokeSuperInDefaultInterfaceMethodToNonImmediateInterfaceTest.java
index 698cf50..f4fec42 100644
--- a/src/test/java/com/android/tools/r8/desugaring/interfacemethods/InvokeSuperInDefaultInterfaceMethodToNonImmediateInterfaceTest.java
+++ b/src/test/java/com/android/tools/r8/desugaring/interfacemethods/InvokeSuperInDefaultInterfaceMethodToNonImmediateInterfaceTest.java
@@ -16,6 +16,7 @@
 import com.android.tools.r8.utils.BooleanUtils;
 import com.android.tools.r8.utils.StringUtils;
 import com.google.common.collect.ImmutableList;
+import org.junit.Ignore;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
@@ -36,6 +37,11 @@
   }
 
   @Test
+  @Ignore("b/197494749 and b/120130831")
+  // TODO(b/197494749): Update this test now that desugaring is moved up. In particular this must
+  //  be rewritten with CF based transformers as R8 does not support interface desugaring on DEX.
+  // TODO(b/120130831): With the move of desugaring this issue should be resolved. The bug indicates
+  //  that a workaround for issues related to the IR implementation can now be reverted.
   public void test() throws Exception {
     // Note that the expected output is independent of the presence of J.m().
     String expectedOutput = StringUtils.lines("I.m()", "JImpl.m()");
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/InterfaceEnumUnboxingTest.java b/src/test/java/com/android/tools/r8/enumunboxing/InterfaceEnumUnboxingTest.java
index 2f27855..3bc5c02 100644
--- a/src/test/java/com/android/tools/r8/enumunboxing/InterfaceEnumUnboxingTest.java
+++ b/src/test/java/com/android/tools/r8/enumunboxing/InterfaceEnumUnboxingTest.java
@@ -59,9 +59,12 @@
                             SuccessUnusedDefaultMethod.EnumInterface.class,
                             SuccessUnusedDefaultMethodOverride.EnumInterface.class,
                             SuccessUnusedDefaultMethodOverrideEnum.EnumInterface.class)
-                        .assertNotUnboxed(
-                            FailureDefaultMethodUsed.EnumInterface.class,
-                            FailureUsedAsInterface.EnumInterface.class))
+                        .assertNotUnboxed(FailureUsedAsInterface.EnumInterface.class)
+                        // When desugaring interfaces the dispatch will inline the forwarding method
+                        // to the CC method allowing unboxing.
+                        .assertUnboxedIf(
+                            !parameters.canUseDefaultAndStaticInterfaceMethods(),
+                            FailureDefaultMethodUsed.EnumInterface.class))
             .noMinification()
             .enableNoVerticalClassMergingAnnotations()
             .enableInliningAnnotations()
diff --git a/src/test/java/com/android/tools/r8/graph/invokespecial/InvokeSpecialOnOtherInterfaceTest.java b/src/test/java/com/android/tools/r8/graph/invokespecial/InvokeSpecialOnOtherInterfaceTest.java
index 509bfa2..99ab7b3 100644
--- a/src/test/java/com/android/tools/r8/graph/invokespecial/InvokeSpecialOnOtherInterfaceTest.java
+++ b/src/test/java/com/android/tools/r8/graph/invokespecial/InvokeSpecialOnOtherInterfaceTest.java
@@ -38,9 +38,9 @@
         .addProgramClasses(I.class)
         .addProgramClassFileData(getClassWithTransformedInvoked())
         .run(parameters.getRuntime(), Main.class)
+        .assertFailureWithErrorThatThrowsIf(parameters.isCfRuntime(), VerifyError.class)
         // TODO(b/144410139): Consider making this a compilation failure when generating DEX.
-        .assertSuccessWithOutputLinesIf(parameters.isDexRuntime(), "Hello World!")
-        .assertFailureWithErrorThatThrowsIf(parameters.isCfRuntime(), VerifyError.class);
+        .assertSuccessWithOutputLinesIf(parameters.isDexRuntime(), "Hello World!");
   }
 
   @Test
@@ -52,11 +52,14 @@
         .addKeepMainRule(Main.class)
         .setMinApi(parameters.getApiLevel())
         .run(parameters.getRuntime(), Main.class)
-        .assertFailureWithErrorThatThrows(
-            parameters.isCfRuntime()
-                ? VerifyError.class
-                // TODO(b/144410139): Consider making this a compilation failure.
-                : NullPointerException.class);
+        .assertFailureWithErrorThatThrowsIf(parameters.isCfRuntime(), VerifyError.class)
+        // TODO(b/144410139): Consider making this a compilation failure when generating DEX.
+        .assertSuccessWithOutputLinesIf(
+            parameters.isDexRuntime() && !parameters.canUseDefaultAndStaticInterfaceMethods(),
+            "Hello World!")
+        .assertFailureWithErrorThatThrowsIf(
+            parameters.isDexRuntime() && parameters.canUseDefaultAndStaticInterfaceMethods(),
+            NullPointerException.class);
   }
 
   private byte[] getClassWithTransformedInvoked() throws IOException {
diff --git a/src/test/java/com/android/tools/r8/graph/invokevirtual/InvokeVirtualPrivateBaseWithDefaultDirectInvokeTest.java b/src/test/java/com/android/tools/r8/graph/invokevirtual/InvokeVirtualPrivateBaseWithDefaultDirectInvokeTest.java
index f989728..a8a222d 100644
--- a/src/test/java/com/android/tools/r8/graph/invokevirtual/InvokeVirtualPrivateBaseWithDefaultDirectInvokeTest.java
+++ b/src/test/java/com/android/tools/r8/graph/invokevirtual/InvokeVirtualPrivateBaseWithDefaultDirectInvokeTest.java
@@ -12,7 +12,6 @@
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.TestRuntime.CfVm;
-import com.android.tools.r8.utils.AndroidApiLevel;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
@@ -38,10 +37,7 @@
     testForJvm()
         .addInnerClasses(getClass())
         .run(parameters.getRuntime(), Main.class)
-        .applyIf(
-            parameters.isCfRuntime(CfVm.JDK11),
-            r -> r.assertSuccessWithOutputLines("I::foo"),
-            r -> r.assertFailureWithErrorThatThrows(IllegalAccessError.class));
+        .apply(r -> assertResultIsCorrect(r, true));
   }
 
   @Test
@@ -50,7 +46,7 @@
         .addInnerClasses(getClass())
         .setMinApi(parameters.getApiLevel())
         .run(parameters.getRuntime(), Main.class)
-        .apply(this::assertResultIsCorrect);
+        .apply(r -> assertResultIsCorrect(r, false));
   }
 
   @Test
@@ -63,17 +59,23 @@
         .setMinApi(parameters.getApiLevel())
         .run(parameters.getRuntime(), Main.class)
         // TODO(b/182189123): This should have the same behavior as D8.
-        .assertSuccessWithOutputLines("I::foo");
+        .applyIf(
+            parameters.isCfRuntime(),
+            r -> r.assertSuccessWithOutputLines("I::foo"),
+            r -> assertResultIsCorrect(r, true));
   }
 
-  public void assertResultIsCorrect(SingleTestRunResult<?> result) {
-    if (parameters.isCfRuntime(CfVm.JDK11)
-        && parameters.getApiLevel().isGreaterThan(AndroidApiLevel.M)) {
+  public void assertResultIsCorrect(SingleTestRunResult<?> result, boolean nonDesugaredCf) {
+    boolean isNotDesugared =
+        (nonDesugaredCf && parameters.isCfRuntime())
+            || parameters.canUseDefaultAndStaticInterfaceMethodsWhenDesugaring();
+    // JDK 11 allows this incorrect dispatch for some reason.
+    if (parameters.isCfRuntime(CfVm.JDK11) && isNotDesugared) {
       result.assertSuccessWithOutputLines("I::foo");
       return;
     }
-    // TODO(b/152199517): Should be illegal access for DEX.
-    if (parameters.isDexRuntime() && parameters.getApiLevel().isGreaterThan(AndroidApiLevel.M)) {
+    // TODO(b/152199517): Should become an illegal access on future DEX VM.
+    if (parameters.isDexRuntime() && isNotDesugared) {
       result.assertSuccessWithOutputLines("I::foo");
       return;
     }
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/R8InliningTest.java b/src/test/java/com/android/tools/r8/ir/optimize/R8InliningTest.java
index fd13bbf..b60f6c9 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/R8InliningTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/R8InliningTest.java
@@ -125,7 +125,7 @@
               // Tests depend on nullability of receiver and argument in general. Learning very
               // accurate
               // nullability from actual usage in tests bothers what we want to test.
-              o.callSiteOptimizationOptions().disableTypePropagationForTesting();
+              o.callSiteOptimizationOptions().disableDynamicTypePropagationForTesting();
               o.testing.horizontallyMergedClassesConsumer = this::fixInliningNullabilityClass;
               o.testing.horizontalClassMergingTarget =
                   (appView, candidates, target) -> {
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/callsites/HashCodeTest.java b/src/test/java/com/android/tools/r8/ir/optimize/callsites/HashCodeTest.java
index a36a63c..509465b 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/callsites/HashCodeTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/callsites/HashCodeTest.java
@@ -6,24 +6,30 @@
 import com.android.tools.r8.NoVerticalClassMerging;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.utils.BooleanUtils;
+import java.util.List;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
 
 @RunWith(Parameterized.class)
 public class HashCodeTest extends TestBase {
+
   private static final Class<?> MAIN = TestClass.class;
 
-  @Parameterized.Parameters(name = "{0}")
-  public static TestParametersCollection data() {
-    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  @Parameters(name = "{1}, experimental: {0}")
+  public static List<Object[]> data() {
+    return buildParameters(
+        BooleanUtils.values(), getTestParameters().withAllRuntimesAndApiLevels().build());
   }
 
+  private final boolean enableExperimentalArgumentPropagation;
   private final TestParameters parameters;
 
-  public HashCodeTest(TestParameters parameters) {
+  public HashCodeTest(boolean enableExperimentalArgumentPropagation, TestParameters parameters) {
+    this.enableExperimentalArgumentPropagation = enableExperimentalArgumentPropagation;
     this.parameters = parameters;
   }
 
@@ -36,6 +42,8 @@
         .addOptionsModification(
             o -> {
               o.testing.callSiteOptimizationInfoInspector = this::callSiteOptimizationInfoInspect;
+              o.callSiteOptimizationOptions()
+                  .setEnableExperimentalArgumentPropagation(enableExperimentalArgumentPropagation);
             })
         .setMinApi(parameters.getApiLevel())
         .run(parameters.getRuntime(), MAIN)
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/callsites/InvokeInterfaceWithRefinedReceiverTest.java b/src/test/java/com/android/tools/r8/ir/optimize/callsites/InvokeInterfaceWithRefinedReceiverTest.java
index b53f23c..2bf775f 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/callsites/InvokeInterfaceWithRefinedReceiverTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/callsites/InvokeInterfaceWithRefinedReceiverTest.java
@@ -14,29 +14,36 @@
 import com.android.tools.r8.NoVerticalClassMerging;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.optimize.info.CallSiteOptimizationInfo;
+import com.android.tools.r8.utils.BooleanUtils;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.android.tools.r8.utils.codeinspector.InstructionSubject;
 import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import java.util.List;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
 
 @RunWith(Parameterized.class)
 public class InvokeInterfaceWithRefinedReceiverTest extends TestBase {
+
   private static final Class<?> MAIN = Main.class;
 
-  @Parameterized.Parameters(name = "{0}")
-  public static TestParametersCollection data() {
-    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  @Parameters(name = "{1}, experimental: {0}")
+  public static List<Object[]> data() {
+    return buildParameters(
+        BooleanUtils.values(), getTestParameters().withAllRuntimesAndApiLevels().build());
   }
 
+  private final boolean enableExperimentalArgumentPropagation;
   private final TestParameters parameters;
 
-  public InvokeInterfaceWithRefinedReceiverTest(TestParameters parameters) {
+  public InvokeInterfaceWithRefinedReceiverTest(
+      boolean enableExperimentalArgumentPropagation, TestParameters parameters) {
+    this.enableExperimentalArgumentPropagation = enableExperimentalArgumentPropagation;
     this.parameters = parameters;
   }
 
@@ -55,6 +62,8 @@
               // target.
               o.enableDevirtualization = false;
               o.testing.callSiteOptimizationInfoInspector = this::callSiteOptimizationInfoInspect;
+              o.callSiteOptimizationOptions()
+                  .setEnableExperimentalArgumentPropagation(enableExperimentalArgumentPropagation);
             })
         .setMinApi(parameters.getApiLevel())
         .run(parameters.getRuntime(), MAIN)
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/callsites/InvokeVirtualWithRefinedReceiverTest.java b/src/test/java/com/android/tools/r8/ir/optimize/callsites/InvokeVirtualWithRefinedReceiverTest.java
index 3ac7e29..2556bf4 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/callsites/InvokeVirtualWithRefinedReceiverTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/callsites/InvokeVirtualWithRefinedReceiverTest.java
@@ -14,29 +14,35 @@
 import com.android.tools.r8.NoVerticalClassMerging;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.optimize.info.CallSiteOptimizationInfo;
+import com.android.tools.r8.utils.BooleanUtils;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.android.tools.r8.utils.codeinspector.InstructionSubject;
 import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import java.util.List;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
 
 @RunWith(Parameterized.class)
 public class InvokeVirtualWithRefinedReceiverTest extends TestBase {
   private static final Class<?> MAIN = Main.class;
 
-  @Parameterized.Parameters(name = "{0}")
-  public static TestParametersCollection data() {
-    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  @Parameters(name = "{1}, experimental: {0}")
+  public static List<Object[]> data() {
+    return buildParameters(
+        BooleanUtils.values(), getTestParameters().withAllRuntimesAndApiLevels().build());
   }
 
+  private final boolean enableExperimentalArgumentPropagation;
   private final TestParameters parameters;
 
-  public InvokeVirtualWithRefinedReceiverTest(TestParameters parameters) {
+  public InvokeVirtualWithRefinedReceiverTest(
+      boolean enableExperimentalArgumentPropagation, TestParameters parameters) {
+    this.enableExperimentalArgumentPropagation = enableExperimentalArgumentPropagation;
     this.parameters = parameters;
   }
 
@@ -52,6 +58,8 @@
         .addOptionsModification(
             o -> {
               o.testing.callSiteOptimizationInfoInspector = this::callSiteOptimizationInfoInspect;
+              o.callSiteOptimizationOptions()
+                  .setEnableExperimentalArgumentPropagation(enableExperimentalArgumentPropagation);
             })
         .setMinApi(parameters.getApiLevel())
         .run(parameters.getRuntime(), MAIN)
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/callsites/PropagationFromSiblingTest.java b/src/test/java/com/android/tools/r8/ir/optimize/callsites/PropagationFromSiblingTest.java
index 429b5a2..0c04377 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/callsites/PropagationFromSiblingTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/callsites/PropagationFromSiblingTest.java
@@ -9,7 +9,8 @@
 import com.android.tools.r8.NoVerticalClassMerging;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.BooleanUtils;
+import java.util.List;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
@@ -18,14 +19,18 @@
 @RunWith(Parameterized.class)
 public class PropagationFromSiblingTest extends TestBase {
 
+  private final boolean enableExperimentalArgumentPropagation;
   private final TestParameters parameters;
 
-  @Parameters(name = "{0}")
-  public static TestParametersCollection data() {
-    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  @Parameters(name = "{1}, experimental: {0}")
+  public static List<Object[]> data() {
+    return buildParameters(
+        BooleanUtils.values(), getTestParameters().withAllRuntimesAndApiLevels().build());
   }
 
-  public PropagationFromSiblingTest(TestParameters parameters) {
+  public PropagationFromSiblingTest(
+      boolean enableExperimentalArgumentPropagation, TestParameters parameters) {
+    this.enableExperimentalArgumentPropagation = enableExperimentalArgumentPropagation;
     this.parameters = parameters;
   }
 
@@ -34,7 +39,13 @@
     testForR8(parameters.getBackend())
         .addInnerClasses(PropagationFromSiblingTest.class)
         .addKeepMainRule(TestClass.class)
-        .addOptionsModification(options -> options.enableUnusedInterfaceRemoval = false)
+        .addOptionsModification(
+            options -> {
+              options.enableUnusedInterfaceRemoval = false;
+              options
+                  .callSiteOptimizationOptions()
+                  .setEnableExperimentalArgumentPropagation(enableExperimentalArgumentPropagation);
+            })
         .enableInliningAnnotations()
         .enableNoVerticalClassMergingAnnotations()
         .enableNeverClassInliningAnnotations()
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/callsites/WithStaticizerTest.java b/src/test/java/com/android/tools/r8/ir/optimize/callsites/WithStaticizerTest.java
index 04cbc5f..3c7307c 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/callsites/WithStaticizerTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/callsites/WithStaticizerTest.java
@@ -13,28 +13,35 @@
 import com.android.tools.r8.NoHorizontalClassMerging;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.BooleanUtils;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.android.tools.r8.utils.codeinspector.InstructionSubject;
 import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import java.util.List;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
 
 @RunWith(Parameterized.class)
 public class WithStaticizerTest extends TestBase {
+
   private static final Class<?> MAIN = Main.class;
 
-  @Parameterized.Parameters(name = "{0}")
-  public static TestParametersCollection data() {
+  @Parameters(name = "{1}, experimental: {0}")
+  public static List<Object[]> data() {
     // TODO(b/112831361): support for class staticizer in CF backend.
-    return getTestParameters().withDexRuntimes().withAllApiLevels().build();
+    return buildParameters(
+        BooleanUtils.values(), getTestParameters().withDexRuntimes().withAllApiLevels().build());
   }
 
+  private final boolean enableExperimentalArgumentPropagation;
   private final TestParameters parameters;
 
-  public WithStaticizerTest(TestParameters parameters) {
+  public WithStaticizerTest(
+      boolean enableExperimentalArgumentPropagation, TestParameters parameters) {
+    this.enableExperimentalArgumentPropagation = enableExperimentalArgumentPropagation;
     this.parameters = parameters;
   }
 
@@ -43,6 +50,12 @@
     testForR8(parameters.getBackend())
         .addInnerClasses(WithStaticizerTest.class)
         .addKeepMainRule(MAIN)
+        .addOptionsModification(
+            options ->
+                options
+                    .callSiteOptimizationOptions()
+                    .setEnableExperimentalArgumentPropagation(
+                        enableExperimentalArgumentPropagation))
         .enableInliningAnnotations()
         .enableNeverClassInliningAnnotations()
         .enableNoHorizontalClassMergingAnnotations()
@@ -62,8 +75,12 @@
     assertThat(host, isPresent());
     MethodSubject foo = host.uniqueMethodWithName("foo");
     assertThat(foo, isPresent());
-    // TODO(b/139246447): Can optimize branches since `arg` is definitely not null.
-    assertTrue(foo.streamInstructions().anyMatch(InstructionSubject::isIf));
+    if (enableExperimentalArgumentPropagation) {
+      assertTrue(foo.streamInstructions().noneMatch(InstructionSubject::isIf));
+    } else {
+      // TODO(b/139246447): Can optimize branches since `arg` is definitely not null.
+      assertTrue(foo.streamInstructions().anyMatch(InstructionSubject::isIf));
+    }
   }
 
   @NeverClassInline
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/callsites/constants/InvokeDirectNegativeTest.java b/src/test/java/com/android/tools/r8/ir/optimize/callsites/constants/InvokeDirectNegativeTest.java
index 2e76fa2..3716b6d 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/callsites/constants/InvokeDirectNegativeTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/callsites/constants/InvokeDirectNegativeTest.java
@@ -11,30 +11,36 @@
 import com.android.tools.r8.NeverInline;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.optimize.info.CallSiteOptimizationInfo;
+import com.android.tools.r8.utils.BooleanUtils;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.android.tools.r8.utils.codeinspector.InstructionSubject;
 import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import java.util.List;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
 
 @RunWith(Parameterized.class)
 public class InvokeDirectNegativeTest extends TestBase {
 
   private static final Class<?> MAIN = Main.class;
 
-  @Parameterized.Parameters(name = "{0}")
-  public static TestParametersCollection data() {
-    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  @Parameters(name = "{1}, experimental: {0}")
+  public static List<Object[]> data() {
+    return buildParameters(
+        BooleanUtils.values(), getTestParameters().withAllRuntimesAndApiLevels().build());
   }
 
+  private final boolean enableExperimentalArgumentPropagation;
   private final TestParameters parameters;
 
-  public InvokeDirectNegativeTest(TestParameters parameters) {
+  public InvokeDirectNegativeTest(
+      boolean enableExperimentalArgumentPropagation, TestParameters parameters) {
+    this.enableExperimentalArgumentPropagation = enableExperimentalArgumentPropagation;
     this.parameters = parameters;
   }
 
@@ -48,6 +54,8 @@
         .addOptionsModification(
             o -> {
               o.testing.callSiteOptimizationInfoInspector = this::callSiteOptimizationInfoInspect;
+              o.callSiteOptimizationOptions()
+                  .setEnableExperimentalArgumentPropagation(enableExperimentalArgumentPropagation);
             })
         .setMinApi(parameters.getApiLevel())
         .run(parameters.getRuntime(), MAIN)
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/callsites/constants/InvokeDirectPositiveTest.java b/src/test/java/com/android/tools/r8/ir/optimize/callsites/constants/InvokeDirectPositiveTest.java
index e3fa7d7..4b220b6 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/callsites/constants/InvokeDirectPositiveTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/callsites/constants/InvokeDirectPositiveTest.java
@@ -11,32 +11,38 @@
 import com.android.tools.r8.NeverInline;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.analysis.value.AbstractValue;
 import com.android.tools.r8.ir.optimize.info.CallSiteOptimizationInfo;
+import com.android.tools.r8.utils.BooleanUtils;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.android.tools.r8.utils.codeinspector.InstructionSubject;
 import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import java.util.List;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
 
 @RunWith(Parameterized.class)
 public class InvokeDirectPositiveTest extends TestBase {
 
   private static final Class<?> MAIN = Main.class;
 
-  @Parameterized.Parameters(name = "{0}")
-  public static TestParametersCollection data() {
-    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  @Parameters(name = "{1}, experimental: {0}")
+  public static List<Object[]> data() {
+    return buildParameters(
+        BooleanUtils.values(), getTestParameters().withAllRuntimesAndApiLevels().build());
   }
 
+  private final boolean enableExperimentalArgumentPropagation;
   private final TestParameters parameters;
 
-  public InvokeDirectPositiveTest(TestParameters parameters) {
+  public InvokeDirectPositiveTest(
+      boolean enableExperimentalArgumentPropagation, TestParameters parameters) {
+    this.enableExperimentalArgumentPropagation = enableExperimentalArgumentPropagation;
     this.parameters = parameters;
   }
 
@@ -50,6 +56,8 @@
         .addOptionsModification(
             o -> {
               o.testing.callSiteOptimizationInfoInspector = this::callSiteOptimizationInfoInspect;
+              o.callSiteOptimizationOptions()
+                  .setEnableExperimentalArgumentPropagation(enableExperimentalArgumentPropagation);
             })
         .setMinApi(parameters.getApiLevel())
         .addOptionsModification(InternalOptions::enableConstantArgumentPropagationForTesting)
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/callsites/constants/InvokeInterfaceNegativeTest.java b/src/test/java/com/android/tools/r8/ir/optimize/callsites/constants/InvokeInterfaceNegativeTest.java
index 7c2eeee..896d548 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/callsites/constants/InvokeInterfaceNegativeTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/callsites/constants/InvokeInterfaceNegativeTest.java
@@ -12,30 +12,36 @@
 import com.android.tools.r8.NoVerticalClassMerging;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.optimize.info.CallSiteOptimizationInfo;
+import com.android.tools.r8.utils.BooleanUtils;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.android.tools.r8.utils.codeinspector.InstructionSubject;
 import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import java.util.List;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
 
 @RunWith(Parameterized.class)
 public class InvokeInterfaceNegativeTest extends TestBase {
 
   private static final Class<?> MAIN = Main.class;
 
-  @Parameterized.Parameters(name = "{0}")
-  public static TestParametersCollection data() {
-    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  @Parameters(name = "{1}, experimental: {0}")
+  public static List<Object[]> data() {
+    return buildParameters(
+        BooleanUtils.values(), getTestParameters().withAllRuntimesAndApiLevels().build());
   }
 
+  private final boolean enableExperimentalArgumentPropagation;
   private final TestParameters parameters;
 
-  public InvokeInterfaceNegativeTest(TestParameters parameters) {
+  public InvokeInterfaceNegativeTest(
+      boolean enableExperimentalArgumentPropagation, TestParameters parameters) {
+    this.enableExperimentalArgumentPropagation = enableExperimentalArgumentPropagation;
     this.parameters = parameters;
   }
 
@@ -53,6 +59,8 @@
               // target.
               o.enableDevirtualization = false;
               o.testing.callSiteOptimizationInfoInspector = this::callSiteOptimizationInfoInspect;
+              o.callSiteOptimizationOptions()
+                  .setEnableExperimentalArgumentPropagation(enableExperimentalArgumentPropagation);
             })
         .setMinApi(parameters.getApiLevel())
         .run(parameters.getRuntime(), MAIN)
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/callsites/constants/InvokeInterfacePositiveTest.java b/src/test/java/com/android/tools/r8/ir/optimize/callsites/constants/InvokeInterfacePositiveTest.java
index 93594ff..553ee4b 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/callsites/constants/InvokeInterfacePositiveTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/callsites/constants/InvokeInterfacePositiveTest.java
@@ -12,32 +12,38 @@
 import com.android.tools.r8.NoHorizontalClassMerging;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.analysis.value.AbstractValue;
 import com.android.tools.r8.ir.optimize.info.CallSiteOptimizationInfo;
+import com.android.tools.r8.utils.BooleanUtils;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.android.tools.r8.utils.codeinspector.InstructionSubject;
 import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import java.util.List;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
 
 @RunWith(Parameterized.class)
 public class InvokeInterfacePositiveTest extends TestBase {
 
   private static final Class<?> MAIN = Main.class;
 
-  @Parameterized.Parameters(name = "{0}")
-  public static TestParametersCollection data() {
-    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  @Parameters(name = "{1}, experimental: {0}")
+  public static List<Object[]> data() {
+    return buildParameters(
+        BooleanUtils.values(), getTestParameters().withAllRuntimesAndApiLevels().build());
   }
 
+  private final boolean enableExperimentalArgumentPropagation;
   private final TestParameters parameters;
 
-  public InvokeInterfacePositiveTest(TestParameters parameters) {
+  public InvokeInterfacePositiveTest(
+      boolean enableExperimentalArgumentPropagation, TestParameters parameters) {
+    this.enableExperimentalArgumentPropagation = enableExperimentalArgumentPropagation;
     this.parameters = parameters;
   }
 
@@ -56,6 +62,8 @@
               // target.
               o.enableDevirtualization = false;
               o.testing.callSiteOptimizationInfoInspector = this::callSiteOptimizationInfoInspect;
+              o.callSiteOptimizationOptions()
+                  .setEnableExperimentalArgumentPropagation(enableExperimentalArgumentPropagation);
             })
         .setMinApi(parameters.getApiLevel())
         .run(parameters.getRuntime(), MAIN)
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/callsites/constants/InvokeStaticNegativeTest.java b/src/test/java/com/android/tools/r8/ir/optimize/callsites/constants/InvokeStaticNegativeTest.java
index df1fb98..8457c5c 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/callsites/constants/InvokeStaticNegativeTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/callsites/constants/InvokeStaticNegativeTest.java
@@ -10,30 +10,36 @@
 import com.android.tools.r8.NeverInline;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.optimize.info.CallSiteOptimizationInfo;
+import com.android.tools.r8.utils.BooleanUtils;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.android.tools.r8.utils.codeinspector.InstructionSubject;
 import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import java.util.List;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
 
 @RunWith(Parameterized.class)
 public class InvokeStaticNegativeTest extends TestBase {
 
   private static final Class<?> MAIN = Main.class;
 
-  @Parameterized.Parameters(name = "{0}")
-  public static TestParametersCollection data() {
-    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  @Parameters(name = "{1}, experimental: {0}")
+  public static List<Object[]> data() {
+    return buildParameters(
+        BooleanUtils.values(), getTestParameters().withAllRuntimesAndApiLevels().build());
   }
 
+  private final boolean enableExperimentalArgumentPropagation;
   private final TestParameters parameters;
 
-  public InvokeStaticNegativeTest(TestParameters parameters) {
+  public InvokeStaticNegativeTest(
+      boolean enableExperimentalArgumentPropagation, TestParameters parameters) {
+    this.enableExperimentalArgumentPropagation = enableExperimentalArgumentPropagation;
     this.parameters = parameters;
   }
 
@@ -43,9 +49,12 @@
         .addInnerClasses(InvokeStaticNegativeTest.class)
         .addKeepMainRule(MAIN)
         .enableInliningAnnotations()
-        .addOptionsModification(o -> {
-          o.testing.callSiteOptimizationInfoInspector = this::callSiteOptimizationInfoInspect;
-        })
+        .addOptionsModification(
+            o -> {
+              o.testing.callSiteOptimizationInfoInspector = this::callSiteOptimizationInfoInspect;
+              o.callSiteOptimizationOptions()
+                  .setEnableExperimentalArgumentPropagation(enableExperimentalArgumentPropagation);
+            })
         .setMinApi(parameters.getApiLevel())
         .run(parameters.getRuntime(), MAIN)
         .assertSuccessWithOutputLines("null", "non-null")
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/callsites/constants/InvokeStaticPositiveTest.java b/src/test/java/com/android/tools/r8/ir/optimize/callsites/constants/InvokeStaticPositiveTest.java
index 096362a..fe38d71 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/callsites/constants/InvokeStaticPositiveTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/callsites/constants/InvokeStaticPositiveTest.java
@@ -10,32 +10,38 @@
 import com.android.tools.r8.NeverInline;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.analysis.value.AbstractValue;
 import com.android.tools.r8.ir.optimize.info.CallSiteOptimizationInfo;
+import com.android.tools.r8.utils.BooleanUtils;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.android.tools.r8.utils.codeinspector.InstructionSubject;
 import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import java.util.List;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
 
 @RunWith(Parameterized.class)
 public class InvokeStaticPositiveTest extends TestBase {
 
   private static final Class<?> MAIN = Main.class;
 
-  @Parameterized.Parameters(name = "{0}")
-  public static TestParametersCollection data() {
-    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  @Parameters(name = "{1}, experimental: {0}")
+  public static List<Object[]> data() {
+    return buildParameters(
+        BooleanUtils.values(), getTestParameters().withAllRuntimesAndApiLevels().build());
   }
 
+  private final boolean enableExperimentalArgumentPropagation;
   private final TestParameters parameters;
 
-  public InvokeStaticPositiveTest(TestParameters parameters) {
+  public InvokeStaticPositiveTest(
+      boolean enableExperimentalArgumentPropagation, TestParameters parameters) {
+    this.enableExperimentalArgumentPropagation = enableExperimentalArgumentPropagation;
     this.parameters = parameters;
   }
 
@@ -48,6 +54,8 @@
         .addOptionsModification(
             o -> {
               o.testing.callSiteOptimizationInfoInspector = this::callSiteOptimizationInfoInspect;
+              o.callSiteOptimizationOptions()
+                  .setEnableExperimentalArgumentPropagation(enableExperimentalArgumentPropagation);
             })
         .setMinApi(parameters.getApiLevel())
         .addOptionsModification(InternalOptions::enableConstantArgumentPropagationForTesting)
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/callsites/constants/InvokeVirtualNegativeTest.java b/src/test/java/com/android/tools/r8/ir/optimize/callsites/constants/InvokeVirtualNegativeTest.java
index 7be71b2..7464d26 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/callsites/constants/InvokeVirtualNegativeTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/callsites/constants/InvokeVirtualNegativeTest.java
@@ -12,30 +12,36 @@
 import com.android.tools.r8.NoVerticalClassMerging;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.optimize.info.CallSiteOptimizationInfo;
+import com.android.tools.r8.utils.BooleanUtils;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.android.tools.r8.utils.codeinspector.InstructionSubject;
 import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import java.util.List;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
 
 @RunWith(Parameterized.class)
 public class InvokeVirtualNegativeTest extends TestBase {
 
   private static final Class<?> MAIN = Main.class;
 
-  @Parameterized.Parameters(name = "{0}")
-  public static TestParametersCollection data() {
-    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  @Parameters(name = "{1}, experimental: {0}")
+  public static List<Object[]> data() {
+    return buildParameters(
+        BooleanUtils.values(), getTestParameters().withAllRuntimesAndApiLevels().build());
   }
 
+  private final boolean enableExperimentalArgumentPropagation;
   private final TestParameters parameters;
 
-  public InvokeVirtualNegativeTest(TestParameters parameters) {
+  public InvokeVirtualNegativeTest(
+      boolean enableExperimentalArgumentPropagation, TestParameters parameters) {
+    this.enableExperimentalArgumentPropagation = enableExperimentalArgumentPropagation;
     this.parameters = parameters;
   }
 
@@ -50,6 +56,8 @@
         .addOptionsModification(
             o -> {
               o.testing.callSiteOptimizationInfoInspector = this::callSiteOptimizationInfoInspect;
+              o.callSiteOptimizationOptions()
+                  .setEnableExperimentalArgumentPropagation(enableExperimentalArgumentPropagation);
             })
         .setMinApi(parameters.getApiLevel())
         .run(parameters.getRuntime(), MAIN)
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/callsites/constants/InvokeVirtualPositiveTest.java b/src/test/java/com/android/tools/r8/ir/optimize/callsites/constants/InvokeVirtualPositiveTest.java
index e254dc6..9853a4c 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/callsites/constants/InvokeVirtualPositiveTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/callsites/constants/InvokeVirtualPositiveTest.java
@@ -12,31 +12,37 @@
 import com.android.tools.r8.NoVerticalClassMerging;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.analysis.value.AbstractValue;
 import com.android.tools.r8.ir.optimize.info.CallSiteOptimizationInfo;
+import com.android.tools.r8.utils.BooleanUtils;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.android.tools.r8.utils.codeinspector.InstructionSubject;
 import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import java.util.List;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
 
 @RunWith(Parameterized.class)
 public class InvokeVirtualPositiveTest extends TestBase {
   private static final Class<?> MAIN = Main.class;
 
-  @Parameterized.Parameters(name = "{0}")
-  public static TestParametersCollection data() {
-    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  @Parameters(name = "{1}, experimental: {0}")
+  public static List<Object[]> data() {
+    return buildParameters(
+        BooleanUtils.values(), getTestParameters().withAllRuntimesAndApiLevels().build());
   }
 
+  private final boolean enableExperimentalArgumentPropagation;
   private final TestParameters parameters;
 
-  public InvokeVirtualPositiveTest(TestParameters parameters) {
+  public InvokeVirtualPositiveTest(
+      boolean enableExperimentalArgumentPropagation, TestParameters parameters) {
+    this.enableExperimentalArgumentPropagation = enableExperimentalArgumentPropagation;
     this.parameters = parameters;
   }
 
@@ -51,6 +57,8 @@
         .addOptionsModification(
             o -> {
               o.testing.callSiteOptimizationInfoInspector = this::callSiteOptimizationInfoInspect;
+              o.callSiteOptimizationOptions()
+                  .setEnableExperimentalArgumentPropagation(enableExperimentalArgumentPropagation);
             })
         .setMinApi(parameters.getApiLevel())
         .addOptionsModification(InternalOptions::enableConstantArgumentPropagationForTesting)
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/callsites/dynamicupperboundtype/InvokeDirectNegativeTest.java b/src/test/java/com/android/tools/r8/ir/optimize/callsites/dynamicupperboundtype/InvokeDirectNegativeTest.java
index 0cc9bb1..4a6d03d 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/callsites/dynamicupperboundtype/InvokeDirectNegativeTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/callsites/dynamicupperboundtype/InvokeDirectNegativeTest.java
@@ -11,30 +11,36 @@
 import com.android.tools.r8.NeverInline;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.optimize.info.CallSiteOptimizationInfo;
+import com.android.tools.r8.utils.BooleanUtils;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.android.tools.r8.utils.codeinspector.InstructionSubject;
 import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import java.util.List;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
 
 @RunWith(Parameterized.class)
 public class InvokeDirectNegativeTest extends TestBase {
   private static final Class<?> MAIN = Main.class;
 
-  @Parameterized.Parameters(name = "{0}")
-  public static TestParametersCollection data() {
-    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  @Parameters(name = "{1}, experimental: {0}")
+  public static List<Object[]> data() {
+    return buildParameters(
+        BooleanUtils.values(), getTestParameters().withAllRuntimesAndApiLevels().build());
   }
 
+  private final boolean enableExperimentalArgumentPropagation;
   private final TestParameters parameters;
 
-  public InvokeDirectNegativeTest(TestParameters parameters) {
+  public InvokeDirectNegativeTest(
+      boolean enableExperimentalArgumentPropagation, TestParameters parameters) {
+    this.enableExperimentalArgumentPropagation = enableExperimentalArgumentPropagation;
     this.parameters = parameters;
   }
 
@@ -48,6 +54,8 @@
         .addOptionsModification(
             o -> {
               o.testing.callSiteOptimizationInfoInspector = this::callSiteOptimizationInfoInspect;
+              o.callSiteOptimizationOptions()
+                  .setEnableExperimentalArgumentPropagation(enableExperimentalArgumentPropagation);
             })
         .setMinApi(parameters.getApiLevel())
         .run(parameters.getRuntime(), MAIN)
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/callsites/dynamicupperboundtype/InvokeDirectPositiveTest.java b/src/test/java/com/android/tools/r8/ir/optimize/callsites/dynamicupperboundtype/InvokeDirectPositiveTest.java
index 51175f8..d23ed49 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/callsites/dynamicupperboundtype/InvokeDirectPositiveTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/callsites/dynamicupperboundtype/InvokeDirectPositiveTest.java
@@ -12,30 +12,36 @@
 import com.android.tools.r8.NoVerticalClassMerging;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.optimize.info.CallSiteOptimizationInfo;
+import com.android.tools.r8.utils.BooleanUtils;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.android.tools.r8.utils.codeinspector.InstructionSubject;
 import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import java.util.List;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
 
 @RunWith(Parameterized.class)
 public class InvokeDirectPositiveTest extends TestBase {
   private static final Class<?> MAIN = Main.class;
 
-  @Parameterized.Parameters(name = "{0}")
-  public static TestParametersCollection data() {
-    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  @Parameters(name = "{1}, experimental: {0}")
+  public static List<Object[]> data() {
+    return buildParameters(
+        BooleanUtils.values(), getTestParameters().withAllRuntimesAndApiLevels().build());
   }
 
+  private final boolean enableExperimentalArgumentPropagation;
   private final TestParameters parameters;
 
-  public InvokeDirectPositiveTest(TestParameters parameters) {
+  public InvokeDirectPositiveTest(
+      boolean enableExperimentalArgumentPropagation, TestParameters parameters) {
+    this.enableExperimentalArgumentPropagation = enableExperimentalArgumentPropagation;
     this.parameters = parameters;
   }
 
@@ -50,6 +56,8 @@
         .addOptionsModification(
             o -> {
               o.testing.callSiteOptimizationInfoInspector = this::callSiteOptimizationInfoInspect;
+              o.callSiteOptimizationOptions()
+                  .setEnableExperimentalArgumentPropagation(enableExperimentalArgumentPropagation);
             })
         .setMinApi(parameters.getApiLevel())
         .run(parameters.getRuntime(), MAIN)
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/callsites/dynamicupperboundtype/InvokeInterfaceNegativeTest.java b/src/test/java/com/android/tools/r8/ir/optimize/callsites/dynamicupperboundtype/InvokeInterfaceNegativeTest.java
index 31e6779..20b0d1f 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/callsites/dynamicupperboundtype/InvokeInterfaceNegativeTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/callsites/dynamicupperboundtype/InvokeInterfaceNegativeTest.java
@@ -12,30 +12,36 @@
 import com.android.tools.r8.NoVerticalClassMerging;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.optimize.info.CallSiteOptimizationInfo;
+import com.android.tools.r8.utils.BooleanUtils;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.android.tools.r8.utils.codeinspector.InstructionSubject;
 import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import java.util.List;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
 
 @RunWith(Parameterized.class)
 public class InvokeInterfaceNegativeTest extends TestBase {
   private static final Class<?> MAIN = Main.class;
 
-  @Parameterized.Parameters(name = "{0}")
-  public static TestParametersCollection data() {
-    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  @Parameters(name = "{1}, experimental: {0}")
+  public static List<Object[]> data() {
+    return buildParameters(
+        BooleanUtils.values(), getTestParameters().withAllRuntimesAndApiLevels().build());
   }
 
+  private final boolean enableExperimentalArgumentPropagation;
   private final TestParameters parameters;
 
-  public InvokeInterfaceNegativeTest(TestParameters parameters) {
+  public InvokeInterfaceNegativeTest(
+      boolean enableExperimentalArgumentPropagation, TestParameters parameters) {
+    this.enableExperimentalArgumentPropagation = enableExperimentalArgumentPropagation;
     this.parameters = parameters;
   }
 
@@ -53,6 +59,8 @@
               // target.
               o.enableDevirtualization = false;
               o.testing.callSiteOptimizationInfoInspector = this::callSiteOptimizationInfoInspect;
+              o.callSiteOptimizationOptions()
+                  .setEnableExperimentalArgumentPropagation(enableExperimentalArgumentPropagation);
             })
         .setMinApi(parameters.getApiLevel())
         .run(parameters.getRuntime(), MAIN)
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/callsites/dynamicupperboundtype/InvokeInterfacePositiveTest.java b/src/test/java/com/android/tools/r8/ir/optimize/callsites/dynamicupperboundtype/InvokeInterfacePositiveTest.java
index 5a06d91..b39540b 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/callsites/dynamicupperboundtype/InvokeInterfacePositiveTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/callsites/dynamicupperboundtype/InvokeInterfacePositiveTest.java
@@ -13,30 +13,36 @@
 import com.android.tools.r8.NoVerticalClassMerging;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.optimize.info.CallSiteOptimizationInfo;
+import com.android.tools.r8.utils.BooleanUtils;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.android.tools.r8.utils.codeinspector.InstructionSubject;
 import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import java.util.List;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
 
 @RunWith(Parameterized.class)
 public class InvokeInterfacePositiveTest extends TestBase {
   private static final Class<?> MAIN = Main.class;
 
-  @Parameterized.Parameters(name = "{0}")
-  public static TestParametersCollection data() {
-    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  @Parameters(name = "{1}, experimental: {0}")
+  public static List<Object[]> data() {
+    return buildParameters(
+        BooleanUtils.values(), getTestParameters().withAllRuntimesAndApiLevels().build());
   }
 
+  private final boolean enableExperimentalArgumentPropagation;
   private final TestParameters parameters;
 
-  public InvokeInterfacePositiveTest(TestParameters parameters) {
+  public InvokeInterfacePositiveTest(
+      boolean enableExperimentalArgumentPropagation, TestParameters parameters) {
+    this.enableExperimentalArgumentPropagation = enableExperimentalArgumentPropagation;
     this.parameters = parameters;
   }
 
@@ -55,6 +61,8 @@
               // target.
               o.enableDevirtualization = false;
               o.testing.callSiteOptimizationInfoInspector = this::callSiteOptimizationInfoInspect;
+              o.callSiteOptimizationOptions()
+                  .setEnableExperimentalArgumentPropagation(enableExperimentalArgumentPropagation);
             })
         .setMinApi(parameters.getApiLevel())
         .run(parameters.getRuntime(), MAIN)
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/callsites/dynamicupperboundtype/InvokeStaticNegativeTest.java b/src/test/java/com/android/tools/r8/ir/optimize/callsites/dynamicupperboundtype/InvokeStaticNegativeTest.java
index 3c94778..6ae9004 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/callsites/dynamicupperboundtype/InvokeStaticNegativeTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/callsites/dynamicupperboundtype/InvokeStaticNegativeTest.java
@@ -10,30 +10,36 @@
 import com.android.tools.r8.NeverInline;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.optimize.info.CallSiteOptimizationInfo;
+import com.android.tools.r8.utils.BooleanUtils;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.android.tools.r8.utils.codeinspector.InstructionSubject;
 import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import java.util.List;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
 
 @RunWith(Parameterized.class)
 public class InvokeStaticNegativeTest extends TestBase {
   private static final Class<?> MAIN = Main.class;
 
-  @Parameterized.Parameters(name = "{0}")
-  public static TestParametersCollection data() {
-    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  @Parameters(name = "{1}, experimental: {0}")
+  public static List<Object[]> data() {
+    return buildParameters(
+        BooleanUtils.values(), getTestParameters().withAllRuntimesAndApiLevels().build());
   }
 
+  private final boolean enableExperimentalArgumentPropagation;
   private final TestParameters parameters;
 
-  public InvokeStaticNegativeTest(TestParameters parameters) {
+  public InvokeStaticNegativeTest(
+      boolean enableExperimentalArgumentPropagation, TestParameters parameters) {
+    this.enableExperimentalArgumentPropagation = enableExperimentalArgumentPropagation;
     this.parameters = parameters;
   }
 
@@ -43,9 +49,12 @@
         .addInnerClasses(InvokeStaticNegativeTest.class)
         .addKeepMainRule(MAIN)
         .enableInliningAnnotations()
-        .addOptionsModification(o -> {
-          o.testing.callSiteOptimizationInfoInspector = this::callSiteOptimizationInfoInspect;
-        })
+        .addOptionsModification(
+            o -> {
+              o.testing.callSiteOptimizationInfoInspector = this::callSiteOptimizationInfoInspect;
+              o.callSiteOptimizationOptions()
+                  .setEnableExperimentalArgumentPropagation(enableExperimentalArgumentPropagation);
+            })
         .setMinApi(parameters.getApiLevel())
         .run(parameters.getRuntime(), MAIN)
         .assertSuccessWithOutputLines("Sub1", "Sub2")
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/callsites/dynamicupperboundtype/InvokeStaticPositiveTest.java b/src/test/java/com/android/tools/r8/ir/optimize/callsites/dynamicupperboundtype/InvokeStaticPositiveTest.java
index ff19f58..410d528 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/callsites/dynamicupperboundtype/InvokeStaticPositiveTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/callsites/dynamicupperboundtype/InvokeStaticPositiveTest.java
@@ -11,30 +11,36 @@
 import com.android.tools.r8.NoVerticalClassMerging;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.optimize.info.CallSiteOptimizationInfo;
+import com.android.tools.r8.utils.BooleanUtils;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.android.tools.r8.utils.codeinspector.InstructionSubject;
 import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import java.util.List;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
 
 @RunWith(Parameterized.class)
 public class InvokeStaticPositiveTest extends TestBase {
   private static final Class<?> MAIN = Main.class;
 
-  @Parameterized.Parameters(name = "{0}")
-  public static TestParametersCollection data() {
-    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  @Parameters(name = "{1}, experimental: {0}")
+  public static List<Object[]> data() {
+    return buildParameters(
+        BooleanUtils.values(), getTestParameters().withAllRuntimesAndApiLevels().build());
   }
 
+  private final boolean enableExperimentalArgumentPropagation;
   private final TestParameters parameters;
 
-  public InvokeStaticPositiveTest(TestParameters parameters) {
+  public InvokeStaticPositiveTest(
+      boolean enableExperimentalArgumentPropagation, TestParameters parameters) {
+    this.enableExperimentalArgumentPropagation = enableExperimentalArgumentPropagation;
     this.parameters = parameters;
   }
 
@@ -48,6 +54,8 @@
         .addOptionsModification(
             o -> {
               o.testing.callSiteOptimizationInfoInspector = this::callSiteOptimizationInfoInspect;
+              o.callSiteOptimizationOptions()
+                  .setEnableExperimentalArgumentPropagation(enableExperimentalArgumentPropagation);
             })
         .setMinApi(parameters.getApiLevel())
         .run(parameters.getRuntime(), MAIN)
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/callsites/dynamicupperboundtype/InvokeVirtualNegativeTest.java b/src/test/java/com/android/tools/r8/ir/optimize/callsites/dynamicupperboundtype/InvokeVirtualNegativeTest.java
index 39bfa4c..1db7baa 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/callsites/dynamicupperboundtype/InvokeVirtualNegativeTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/callsites/dynamicupperboundtype/InvokeVirtualNegativeTest.java
@@ -12,30 +12,36 @@
 import com.android.tools.r8.NoVerticalClassMerging;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.optimize.info.CallSiteOptimizationInfo;
+import com.android.tools.r8.utils.BooleanUtils;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.android.tools.r8.utils.codeinspector.InstructionSubject;
 import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import java.util.List;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
 
 @RunWith(Parameterized.class)
 public class InvokeVirtualNegativeTest extends TestBase {
   private static final Class<?> MAIN = Main.class;
 
-  @Parameterized.Parameters(name = "{0}")
-  public static TestParametersCollection data() {
-    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  @Parameters(name = "{1}, experimental: {0}")
+  public static List<Object[]> data() {
+    return buildParameters(
+        BooleanUtils.values(), getTestParameters().withAllRuntimesAndApiLevels().build());
   }
 
+  private final boolean enableExperimentalArgumentPropagation;
   private final TestParameters parameters;
 
-  public InvokeVirtualNegativeTest(TestParameters parameters) {
+  public InvokeVirtualNegativeTest(
+      boolean enableExperimentalArgumentPropagation, TestParameters parameters) {
+    this.enableExperimentalArgumentPropagation = enableExperimentalArgumentPropagation;
     this.parameters = parameters;
   }
 
@@ -50,6 +56,8 @@
         .addOptionsModification(
             o -> {
               o.testing.callSiteOptimizationInfoInspector = this::callSiteOptimizationInfoInspect;
+              o.callSiteOptimizationOptions()
+                  .setEnableExperimentalArgumentPropagation(enableExperimentalArgumentPropagation);
             })
         .setMinApi(parameters.getApiLevel())
         .run(parameters.getRuntime(), MAIN)
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/callsites/dynamicupperboundtype/InvokeVirtualPositiveTest.java b/src/test/java/com/android/tools/r8/ir/optimize/callsites/dynamicupperboundtype/InvokeVirtualPositiveTest.java
index 2595df5..adce8cc 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/callsites/dynamicupperboundtype/InvokeVirtualPositiveTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/callsites/dynamicupperboundtype/InvokeVirtualPositiveTest.java
@@ -12,30 +12,36 @@
 import com.android.tools.r8.NoVerticalClassMerging;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.optimize.info.CallSiteOptimizationInfo;
+import com.android.tools.r8.utils.BooleanUtils;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.android.tools.r8.utils.codeinspector.InstructionSubject;
 import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import java.util.List;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
 
 @RunWith(Parameterized.class)
 public class InvokeVirtualPositiveTest extends TestBase {
   private static final Class<?> MAIN = Main.class;
 
-  @Parameterized.Parameters(name = "{0}")
-  public static TestParametersCollection data() {
-    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  @Parameters(name = "{1}, experimental: {0}")
+  public static List<Object[]> data() {
+    return buildParameters(
+        BooleanUtils.values(), getTestParameters().withAllRuntimesAndApiLevels().build());
   }
 
+  private final boolean enableExperimentalArgumentPropagation;
   private final TestParameters parameters;
 
-  public InvokeVirtualPositiveTest(TestParameters parameters) {
+  public InvokeVirtualPositiveTest(
+      boolean enableExperimentalArgumentPropagation, TestParameters parameters) {
+    this.enableExperimentalArgumentPropagation = enableExperimentalArgumentPropagation;
     this.parameters = parameters;
   }
 
@@ -50,6 +56,8 @@
         .addOptionsModification(
             o -> {
               o.testing.callSiteOptimizationInfoInspector = this::callSiteOptimizationInfoInspect;
+              o.callSiteOptimizationOptions()
+                  .setEnableExperimentalArgumentPropagation(enableExperimentalArgumentPropagation);
             })
         .setMinApi(parameters.getApiLevel())
         .run(parameters.getRuntime(), MAIN)
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/callsites/nullability/InvokeDirectNegativeTest.java b/src/test/java/com/android/tools/r8/ir/optimize/callsites/nullability/InvokeDirectNegativeTest.java
index 96614da..05d1329 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/callsites/nullability/InvokeDirectNegativeTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/callsites/nullability/InvokeDirectNegativeTest.java
@@ -11,28 +11,34 @@
 import com.android.tools.r8.NeverInline;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.utils.BooleanUtils;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.android.tools.r8.utils.codeinspector.InstructionSubject;
 import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import java.util.List;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
 
 @RunWith(Parameterized.class)
 public class InvokeDirectNegativeTest extends TestBase {
   private static final Class<?> MAIN = Main.class;
 
-  @Parameterized.Parameters(name = "{0}")
-  public static TestParametersCollection data() {
-    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  @Parameters(name = "{1}, experimental: {0}")
+  public static List<Object[]> data() {
+    return buildParameters(
+        BooleanUtils.values(), getTestParameters().withAllRuntimesAndApiLevels().build());
   }
 
+  private final boolean enableExperimentalArgumentPropagation;
   private final TestParameters parameters;
 
-  public InvokeDirectNegativeTest(TestParameters parameters) {
+  public InvokeDirectNegativeTest(
+      boolean enableExperimentalArgumentPropagation, TestParameters parameters) {
+    this.enableExperimentalArgumentPropagation = enableExperimentalArgumentPropagation;
     this.parameters = parameters;
   }
 
@@ -46,6 +52,8 @@
         .addOptionsModification(
             o -> {
               o.testing.callSiteOptimizationInfoInspector = this::callSiteOptimizationInfoInspect;
+              o.callSiteOptimizationOptions()
+                  .setEnableExperimentalArgumentPropagation(enableExperimentalArgumentPropagation);
             })
         .setMinApi(parameters.getApiLevel())
         .run(parameters.getRuntime(), MAIN)
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/callsites/nullability/InvokeDirectPositiveTest.java b/src/test/java/com/android/tools/r8/ir/optimize/callsites/nullability/InvokeDirectPositiveTest.java
index 0b7ff7d..3a6fd02 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/callsites/nullability/InvokeDirectPositiveTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/callsites/nullability/InvokeDirectPositiveTest.java
@@ -11,29 +11,35 @@
 import com.android.tools.r8.NeverInline;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.optimize.info.CallSiteOptimizationInfo;
+import com.android.tools.r8.utils.BooleanUtils;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.android.tools.r8.utils.codeinspector.InstructionSubject;
 import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import java.util.List;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
 
 @RunWith(Parameterized.class)
 public class InvokeDirectPositiveTest extends TestBase {
   private static final Class<?> MAIN = Main.class;
 
-  @Parameterized.Parameters(name = "{0}")
-  public static TestParametersCollection data() {
-    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  @Parameters(name = "{1}, experimental: {0}")
+  public static List<Object[]> data() {
+    return buildParameters(
+        BooleanUtils.values(), getTestParameters().withAllRuntimesAndApiLevels().build());
   }
 
+  private final boolean enableExperimentalArgumentPropagation;
   private final TestParameters parameters;
 
-  public InvokeDirectPositiveTest(TestParameters parameters) {
+  public InvokeDirectPositiveTest(
+      boolean enableExperimentalArgumentPropagation, TestParameters parameters) {
+    this.enableExperimentalArgumentPropagation = enableExperimentalArgumentPropagation;
     this.parameters = parameters;
   }
 
@@ -47,6 +53,8 @@
         .addOptionsModification(
             o -> {
               o.testing.callSiteOptimizationInfoInspector = this::callSiteOptimizationInfoInspect;
+              o.callSiteOptimizationOptions()
+                  .setEnableExperimentalArgumentPropagation(enableExperimentalArgumentPropagation);
             })
         .setMinApi(parameters.getApiLevel())
         .run(parameters.getRuntime(), MAIN)
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/callsites/nullability/InvokeInterfaceNegativeTest.java b/src/test/java/com/android/tools/r8/ir/optimize/callsites/nullability/InvokeInterfaceNegativeTest.java
index bc9f10c..421ebf0 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/callsites/nullability/InvokeInterfaceNegativeTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/callsites/nullability/InvokeInterfaceNegativeTest.java
@@ -12,30 +12,36 @@
 import com.android.tools.r8.NoVerticalClassMerging;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.optimize.info.CallSiteOptimizationInfo;
+import com.android.tools.r8.utils.BooleanUtils;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.android.tools.r8.utils.codeinspector.InstructionSubject;
 import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import java.util.List;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
 
 @RunWith(Parameterized.class)
 public class InvokeInterfaceNegativeTest extends TestBase {
   private static final Class<?> MAIN = Main.class;
 
-  @Parameterized.Parameters(name = "{0}")
-  public static TestParametersCollection data() {
-    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  @Parameters(name = "{1}, experimental: {0}")
+  public static List<Object[]> data() {
+    return buildParameters(
+        BooleanUtils.values(), getTestParameters().withAllRuntimesAndApiLevels().build());
   }
 
+  private final boolean enableExperimentalArgumentPropagation;
   private final TestParameters parameters;
 
-  public InvokeInterfaceNegativeTest(TestParameters parameters) {
+  public InvokeInterfaceNegativeTest(
+      boolean enableExperimentalArgumentPropagation, TestParameters parameters) {
+    this.enableExperimentalArgumentPropagation = enableExperimentalArgumentPropagation;
     this.parameters = parameters;
   }
 
@@ -53,6 +59,8 @@
               // target.
               o.enableDevirtualization = false;
               o.testing.callSiteOptimizationInfoInspector = this::callSiteOptimizationInfoInspect;
+              o.callSiteOptimizationOptions()
+                  .setEnableExperimentalArgumentPropagation(enableExperimentalArgumentPropagation);
             })
         .setMinApi(parameters.getApiLevel())
         .run(parameters.getRuntime(), MAIN)
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/callsites/nullability/InvokeInterfacePositiveTest.java b/src/test/java/com/android/tools/r8/ir/optimize/callsites/nullability/InvokeInterfacePositiveTest.java
index 19fa2a1..6d0aab9 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/callsites/nullability/InvokeInterfacePositiveTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/callsites/nullability/InvokeInterfacePositiveTest.java
@@ -12,29 +12,35 @@
 import com.android.tools.r8.NoHorizontalClassMerging;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.optimize.info.CallSiteOptimizationInfo;
+import com.android.tools.r8.utils.BooleanUtils;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.android.tools.r8.utils.codeinspector.InstructionSubject;
 import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import java.util.List;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
 
 @RunWith(Parameterized.class)
 public class InvokeInterfacePositiveTest extends TestBase {
   private static final Class<?> MAIN = Main.class;
 
-  @Parameterized.Parameters(name = "{0}")
-  public static TestParametersCollection data() {
-    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  @Parameters(name = "{1}, experimental: {0}")
+  public static List<Object[]> data() {
+    return buildParameters(
+        BooleanUtils.values(), getTestParameters().withAllRuntimesAndApiLevels().build());
   }
 
+  private final boolean enableExperimentalArgumentPropagation;
   private final TestParameters parameters;
 
-  public InvokeInterfacePositiveTest(TestParameters parameters) {
+  public InvokeInterfacePositiveTest(
+      boolean enableExperimentalArgumentPropagation, TestParameters parameters) {
+    this.enableExperimentalArgumentPropagation = enableExperimentalArgumentPropagation;
     this.parameters = parameters;
   }
 
@@ -52,6 +58,8 @@
               // target.
               o.enableDevirtualization = false;
               o.testing.callSiteOptimizationInfoInspector = this::callSiteOptimizationInfoInspect;
+              o.callSiteOptimizationOptions()
+                  .setEnableExperimentalArgumentPropagation(enableExperimentalArgumentPropagation);
             })
         .setMinApi(parameters.getApiLevel())
         .run(parameters.getRuntime(), MAIN)
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/callsites/nullability/InvokeStaticNegativeTest.java b/src/test/java/com/android/tools/r8/ir/optimize/callsites/nullability/InvokeStaticNegativeTest.java
index 0a18b69..a7fff40 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/callsites/nullability/InvokeStaticNegativeTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/callsites/nullability/InvokeStaticNegativeTest.java
@@ -10,28 +10,34 @@
 import com.android.tools.r8.NeverInline;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.utils.BooleanUtils;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.android.tools.r8.utils.codeinspector.InstructionSubject;
 import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import java.util.List;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
 
 @RunWith(Parameterized.class)
 public class InvokeStaticNegativeTest extends TestBase {
   private static final Class<?> MAIN = Main.class;
 
-  @Parameterized.Parameters(name = "{0}")
-  public static TestParametersCollection data() {
-    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  @Parameters(name = "{1}, experimental: {0}")
+  public static List<Object[]> data() {
+    return buildParameters(
+        BooleanUtils.values(), getTestParameters().withAllRuntimesAndApiLevels().build());
   }
 
+  private final boolean enableExperimentalArgumentPropagation;
   private final TestParameters parameters;
 
-  public InvokeStaticNegativeTest(TestParameters parameters) {
+  public InvokeStaticNegativeTest(
+      boolean enableExperimentalArgumentPropagation, TestParameters parameters) {
+    this.enableExperimentalArgumentPropagation = enableExperimentalArgumentPropagation;
     this.parameters = parameters;
   }
 
@@ -41,9 +47,12 @@
         .addInnerClasses(InvokeStaticNegativeTest.class)
         .addKeepMainRule(MAIN)
         .enableInliningAnnotations()
-        .addOptionsModification(o -> {
-          o.testing.callSiteOptimizationInfoInspector = this::callSiteOptimizationInfoInspect;
-        })
+        .addOptionsModification(
+            o -> {
+              o.testing.callSiteOptimizationInfoInspector = this::callSiteOptimizationInfoInspect;
+              o.callSiteOptimizationOptions()
+                  .setEnableExperimentalArgumentPropagation(enableExperimentalArgumentPropagation);
+            })
         .setMinApi(parameters.getApiLevel())
         .run(parameters.getRuntime(), MAIN)
         .assertSuccessWithOutputLines("null", "non-null")
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/callsites/nullability/InvokeStaticPositiveTest.java b/src/test/java/com/android/tools/r8/ir/optimize/callsites/nullability/InvokeStaticPositiveTest.java
index 700ec34..0b27105 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/callsites/nullability/InvokeStaticPositiveTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/callsites/nullability/InvokeStaticPositiveTest.java
@@ -10,29 +10,35 @@
 import com.android.tools.r8.NeverInline;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.optimize.info.CallSiteOptimizationInfo;
+import com.android.tools.r8.utils.BooleanUtils;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.android.tools.r8.utils.codeinspector.InstructionSubject;
 import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import java.util.List;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
 
 @RunWith(Parameterized.class)
 public class InvokeStaticPositiveTest extends TestBase {
   private static final Class<?> MAIN = Main.class;
 
-  @Parameterized.Parameters(name = "{0}")
-  public static TestParametersCollection data() {
-    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  @Parameters(name = "{1}, experimental: {0}")
+  public static List<Object[]> data() {
+    return buildParameters(
+        BooleanUtils.values(), getTestParameters().withAllRuntimesAndApiLevels().build());
   }
 
+  private final boolean enableExperimentalArgumentPropagation;
   private final TestParameters parameters;
 
-  public InvokeStaticPositiveTest(TestParameters parameters) {
+  public InvokeStaticPositiveTest(
+      boolean enableExperimentalArgumentPropagation, TestParameters parameters) {
+    this.enableExperimentalArgumentPropagation = enableExperimentalArgumentPropagation;
     this.parameters = parameters;
   }
 
@@ -42,9 +48,12 @@
         .addInnerClasses(InvokeStaticPositiveTest.class)
         .addKeepMainRule(MAIN)
         .enableInliningAnnotations()
-        .addOptionsModification(o -> {
-          o.testing.callSiteOptimizationInfoInspector = this::callSiteOptimizationInfoInspect;
-        })
+        .addOptionsModification(
+            o -> {
+              o.testing.callSiteOptimizationInfoInspector = this::callSiteOptimizationInfoInspect;
+              o.callSiteOptimizationOptions()
+                  .setEnableExperimentalArgumentPropagation(enableExperimentalArgumentPropagation);
+            })
         .setMinApi(parameters.getApiLevel())
         .run(parameters.getRuntime(), MAIN)
         .assertSuccessWithOutputLines("non-null")
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/callsites/nullability/InvokeVirtualCascadeTest.java b/src/test/java/com/android/tools/r8/ir/optimize/callsites/nullability/InvokeVirtualCascadeTest.java
index f46869d..627171a 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/callsites/nullability/InvokeVirtualCascadeTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/callsites/nullability/InvokeVirtualCascadeTest.java
@@ -12,27 +12,33 @@
 import com.android.tools.r8.NoVerticalClassMerging;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.BooleanUtils;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.android.tools.r8.utils.codeinspector.InstructionSubject;
 import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import java.util.List;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
 
 @RunWith(Parameterized.class)
 public class InvokeVirtualCascadeTest extends TestBase {
   private static final Class<?> MAIN = Main.class;
 
-  @Parameterized.Parameters(name = "{0}")
-  public static TestParametersCollection data() {
-    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  @Parameters(name = "{1}, experimental: {0}")
+  public static List<Object[]> data() {
+    return buildParameters(
+        BooleanUtils.values(), getTestParameters().withAllRuntimesAndApiLevels().build());
   }
 
+  private final boolean enableExperimentalArgumentPropagation;
   private final TestParameters parameters;
 
-  public InvokeVirtualCascadeTest(TestParameters parameters) {
+  public InvokeVirtualCascadeTest(
+      boolean enableExperimentalArgumentPropagation, TestParameters parameters) {
+    this.enableExperimentalArgumentPropagation = enableExperimentalArgumentPropagation;
     this.parameters = parameters;
   }
 
@@ -41,6 +47,12 @@
     testForR8(parameters.getBackend())
         .addInnerClasses(InvokeVirtualCascadeTest.class)
         .addKeepMainRule(MAIN)
+        .addOptionsModification(
+            options ->
+                options
+                    .callSiteOptimizationOptions()
+                    .setEnableExperimentalArgumentPropagation(
+                        enableExperimentalArgumentPropagation))
         .enableNoVerticalClassMergingAnnotations()
         .enableNeverClassInliningAnnotations()
         .enableInliningAnnotations()
@@ -56,16 +68,24 @@
 
     MethodSubject a_m = a.uniqueMethodWithName("m");
     assertThat(a_m, isPresent());
-    // TODO(b/139246447): Can optimize branches since `arg` is definitely not null.
-    assertTrue(a_m.streamInstructions().anyMatch(InstructionSubject::isIf));
+    if (enableExperimentalArgumentPropagation) {
+      assertTrue(a_m.streamInstructions().noneMatch(InstructionSubject::isIf));
+    } else {
+      // TODO(b/139246447): Can optimize branches since `arg` is definitely not null.
+      assertTrue(a_m.streamInstructions().anyMatch(InstructionSubject::isIf));
+    }
 
     ClassSubject b = inspector.clazz(B.class);
     assertThat(b, isPresent());
 
     MethodSubject b_m = b.uniqueMethodWithName("m");
     assertThat(b_m, isPresent());
-    // TODO(b/139246447): Can optimize branches since `arg` is definitely not null.
-    assertTrue(b_m.streamInstructions().anyMatch(InstructionSubject::isIf));
+    if (enableExperimentalArgumentPropagation) {
+      assertTrue(b_m.streamInstructions().noneMatch(InstructionSubject::isIf));
+    } else {
+      // TODO(b/139246447): Can optimize branches since `arg` is definitely not null.
+      assertTrue(b_m.streamInstructions().anyMatch(InstructionSubject::isIf));
+    }
   }
 
   @NoVerticalClassMerging
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/callsites/nullability/InvokeVirtualNegativeTest.java b/src/test/java/com/android/tools/r8/ir/optimize/callsites/nullability/InvokeVirtualNegativeTest.java
index 544cf86..b9b1f2f 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/callsites/nullability/InvokeVirtualNegativeTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/callsites/nullability/InvokeVirtualNegativeTest.java
@@ -12,30 +12,36 @@
 import com.android.tools.r8.NoVerticalClassMerging;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.optimize.info.CallSiteOptimizationInfo;
+import com.android.tools.r8.utils.BooleanUtils;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.android.tools.r8.utils.codeinspector.InstructionSubject;
 import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import java.util.List;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
 
 @RunWith(Parameterized.class)
 public class InvokeVirtualNegativeTest extends TestBase {
   private static final Class<?> MAIN = Main.class;
 
-  @Parameterized.Parameters(name = "{0}")
-  public static TestParametersCollection data() {
-    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  @Parameters(name = "{1}, experimental: {0}")
+  public static List<Object[]> data() {
+    return buildParameters(
+        BooleanUtils.values(), getTestParameters().withAllRuntimesAndApiLevels().build());
   }
 
+  private final boolean enableExperimentalArgumentPropagation;
   private final TestParameters parameters;
 
-  public InvokeVirtualNegativeTest(TestParameters parameters) {
+  public InvokeVirtualNegativeTest(
+      boolean enableExperimentalArgumentPropagation, TestParameters parameters) {
+    this.enableExperimentalArgumentPropagation = enableExperimentalArgumentPropagation;
     this.parameters = parameters;
   }
 
@@ -50,6 +56,8 @@
         .addOptionsModification(
             o -> {
               o.testing.callSiteOptimizationInfoInspector = this::callSiteOptimizationInfoInspect;
+              o.callSiteOptimizationOptions()
+                  .setEnableExperimentalArgumentPropagation(enableExperimentalArgumentPropagation);
             })
         .setMinApi(parameters.getApiLevel())
         .run(parameters.getRuntime(), MAIN)
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/callsites/nullability/InvokeVirtualPositiveTest.java b/src/test/java/com/android/tools/r8/ir/optimize/callsites/nullability/InvokeVirtualPositiveTest.java
index 2cff135..8e1031a 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/callsites/nullability/InvokeVirtualPositiveTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/callsites/nullability/InvokeVirtualPositiveTest.java
@@ -12,30 +12,36 @@
 import com.android.tools.r8.NoVerticalClassMerging;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.optimize.info.CallSiteOptimizationInfo;
+import com.android.tools.r8.utils.BooleanUtils;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.android.tools.r8.utils.codeinspector.InstructionSubject;
 import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import java.util.List;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
 
 @RunWith(Parameterized.class)
 public class InvokeVirtualPositiveTest extends TestBase {
   private static final Class<?> MAIN = Main.class;
 
-  @Parameterized.Parameters(name = "{0}")
-  public static TestParametersCollection data() {
-    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  @Parameters(name = "{1}, experimental: {0}")
+  public static List<Object[]> data() {
+    return buildParameters(
+        BooleanUtils.values(), getTestParameters().withAllRuntimesAndApiLevels().build());
   }
 
+  private final boolean enableExperimentalArgumentPropagation;
   private final TestParameters parameters;
 
-  public InvokeVirtualPositiveTest(TestParameters parameters) {
+  public InvokeVirtualPositiveTest(
+      boolean enableExperimentalArgumentPropagation, TestParameters parameters) {
+    this.enableExperimentalArgumentPropagation = enableExperimentalArgumentPropagation;
     this.parameters = parameters;
   }
 
@@ -50,6 +56,8 @@
         .addOptionsModification(
             o -> {
               o.testing.callSiteOptimizationInfoInspector = this::callSiteOptimizationInfoInspect;
+              o.callSiteOptimizationOptions()
+                  .setEnableExperimentalArgumentPropagation(enableExperimentalArgumentPropagation);
             })
         .setMinApi(parameters.getApiLevel())
         .run(parameters.getRuntime(), MAIN)
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/inliner/InliningIntoVisibilityBridgeTest.java b/src/test/java/com/android/tools/r8/ir/optimize/inliner/InliningIntoVisibilityBridgeTest.java
index 46727f2..75679e5 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/inliner/InliningIntoVisibilityBridgeTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/inliner/InliningIntoVisibilityBridgeTest.java
@@ -9,16 +9,19 @@
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresentAndRenamed;
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
 
 import com.android.tools.r8.R8TestBuilder;
 import com.android.tools.r8.R8TestRunResult;
 import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.ir.optimize.inliner.testclasses.InliningIntoVisibilityBridgeTestClasses;
 import com.android.tools.r8.ir.optimize.inliner.testclasses.InliningIntoVisibilityBridgeTestClasses.InliningIntoVisibilityBridgeTestClassB;
 import com.android.tools.r8.utils.BooleanUtils;
 import com.android.tools.r8.utils.StringUtils;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import java.util.List;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
@@ -28,14 +31,17 @@
 @RunWith(Parameterized.class)
 public class InliningIntoVisibilityBridgeTest extends TestBase {
 
+  private final TestParameters parameters;
   private final boolean neverInline;
 
-  @Parameters(name = "Never inline: {0}")
-  public static Boolean[] data() {
-    return BooleanUtils.values();
+  @Parameters(name = "{0}, never inline: {1}")
+  public static List<Object[]> data() {
+    return buildParameters(
+        getTestParameters().withAllRuntimesAndApiLevels().build(), BooleanUtils.values());
   }
 
-  public InliningIntoVisibilityBridgeTest(boolean neverInline) {
+  public InliningIntoVisibilityBridgeTest(TestParameters parameters, boolean neverInline) {
+    this.parameters = parameters;
     this.neverInline = neverInline;
   }
 
@@ -44,7 +50,7 @@
     String expectedOutput = StringUtils.lines("Hello world", "Hello world", "Hello world");
 
     R8TestRunResult result =
-        testForR8(Backend.DEX)
+        testForR8(parameters.getBackend())
             .addInnerClasses(InliningIntoVisibilityBridgeTest.class)
             .addInnerClasses(InliningIntoVisibilityBridgeTestClasses.class)
             .addKeepMainRule(TestClass.class)
@@ -54,8 +60,9 @@
             .applyIf(!neverInline, R8TestBuilder::enableForceInliningAnnotations)
             .enableNoVerticalClassMergingAnnotations()
             .enableProguardTestOptions()
+            .setMinApi(parameters.getApiLevel())
             .compile()
-            .run(TestClass.class)
+            .run(parameters.getRuntime(), TestClass.class)
             .assertSuccessWithOutput(expectedOutput);
 
     // Verify that A.method() is only there if there is an explicit -neverinline rule.
@@ -75,9 +82,11 @@
       assertThat(classSubject, isPresent());
 
       MethodSubject methodSubject = classSubject.uniqueMethodWithName("method");
-      assertThat(methodSubject, isPresentAndRenamed());
-      assertEquals(neverInline, methodSubject.isBridge());
-      assertEquals(neverInline, methodSubject.isSynthetic());
+      if (!neverInline) {
+        assertThat(methodSubject, isPresentAndRenamed());
+        assertFalse(methodSubject.isBridge());
+        assertFalse(methodSubject.isSynthetic());
+      }
     }
   }
 
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/outliner/OutlineFromDefaultInterfaceMethodTest.java b/src/test/java/com/android/tools/r8/ir/optimize/outliner/OutlineFromDefaultInterfaceMethodTest.java
index 9b3dd42..baf0868 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/outliner/OutlineFromDefaultInterfaceMethodTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/outliner/OutlineFromDefaultInterfaceMethodTest.java
@@ -4,6 +4,7 @@
 package com.android.tools.r8.ir.optimize.outliner;
 
 import static com.android.tools.r8.references.Reference.methodFromMethod;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isAbsent;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.junit.Assert.assertEquals;
@@ -16,11 +17,9 @@
 import com.android.tools.r8.synthesis.SyntheticItemsTestUtils;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
-import com.android.tools.r8.utils.codeinspector.FoundMethodSubject;
 import com.android.tools.r8.utils.codeinspector.HorizontallyMergedClassesInspector;
 import com.android.tools.r8.utils.codeinspector.InstructionSubject;
 import com.android.tools.r8.utils.codeinspector.MethodSubject;
-import java.util.List;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
@@ -65,25 +64,22 @@
   }
 
   private void inspect(CodeInspector inspector) {
-    ClassSubject interfaceSubject;
-    MethodSubject greetMethodSubject;
-    if (parameters.isCfRuntime()
-        || parameters
-            .getApiLevel()
-            .isGreaterThanOrEqualTo(apiLevelWithDefaultInterfaceMethodsSupport())) {
-      interfaceSubject = inspector.clazz(I.class);
-      greetMethodSubject = interfaceSubject.uniqueMethodWithName("greet");
+    if (parameters.canUseDefaultAndStaticInterfaceMethods()) {
+      ClassSubject interfaceSubject = inspector.clazz(I.class);
+      MethodSubject greetMethodSubject = interfaceSubject.uniqueMethodWithName("greet");
+      assertThat(interfaceSubject, isPresent());
+      assertThat(greetMethodSubject, isPresent());
+      assertEquals(
+          1,
+          greetMethodSubject
+              .streamInstructions()
+              .filter(InstructionSubject::isInvokeStatic)
+              .count());
     } else {
-      interfaceSubject = inspector.clazz(SyntheticItemsTestUtils.syntheticCompanionClass(I.class));
-      List<FoundMethodSubject> companionMethods = interfaceSubject.allMethods();
-      assertEquals(1, companionMethods.size());
-      greetMethodSubject = companionMethods.get(0);
+      // The companion class method is inlined into main.
+      assertThat(
+          inspector.clazz(SyntheticItemsTestUtils.syntheticCompanionClass(I.class)), isAbsent());
     }
-    assertThat(interfaceSubject, isPresent());
-    assertThat(greetMethodSubject, isPresent());
-    assertEquals(
-        1,
-        greetMethodSubject.streamInstructions().filter(InstructionSubject::isInvokeStatic).count());
   }
 
   static class TestClass {
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/reflection/GetClassTest.java b/src/test/java/com/android/tools/r8/ir/optimize/reflection/GetClassTest.java
index d1d5f1f..000f5d4 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/reflection/GetClassTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/reflection/GetClassTest.java
@@ -155,7 +155,7 @@
     // In `getMainClass`, a call with `null`, which will throw NPE, is replaced with null throwing
     // code. Then, remaining call with non-null argument made getClass() replaceable.
     // Disable the propagation of call site information to separate the tests.
-    options.callSiteOptimizationOptions().disableTypePropagationForTesting();
+    options.callSiteOptimizationOptions().disableDynamicTypePropagationForTesting();
   }
 
   @Test
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/string/StringValueOfTest.java b/src/test/java/com/android/tools/r8/ir/optimize/string/StringValueOfTest.java
index 2e1709c..01ac9dc 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/string/StringValueOfTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/string/StringValueOfTest.java
@@ -58,7 +58,7 @@
     // Disable the propagation of call site information to test String#valueOf optimization with
     // nullable argument. Otherwise, e.g., we know that only `null` is used for `hideNPE`, and then
     // simplify everything in that method.
-    options.callSiteOptimizationOptions().disableTypePropagationForTesting();
+    options.callSiteOptimizationOptions().disableDynamicTypePropagationForTesting();
     options.testing.forceNameReflectionOptimization = true;
   }
 
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/unusedinterfaces/UnusedInterfaceWithDefaultMethodTest.java b/src/test/java/com/android/tools/r8/ir/optimize/unusedinterfaces/UnusedInterfaceWithDefaultMethodTest.java
index 6bc0862..c10d1db 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/unusedinterfaces/UnusedInterfaceWithDefaultMethodTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/unusedinterfaces/UnusedInterfaceWithDefaultMethodTest.java
@@ -4,6 +4,7 @@
 
 package com.android.tools.r8.ir.optimize.unusedinterfaces;
 
+import static com.android.tools.r8.utils.codeinspector.Matchers.isAbsent;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.junit.Assert.assertEquals;
@@ -57,7 +58,9 @@
     assertThat(iClassSubject, isPresent());
 
     ClassSubject jClassSubject = inspector.clazz(J.class);
-    assertThat(jClassSubject, isPresent());
+    assertThat(
+        jClassSubject,
+        parameters.canUseDefaultAndStaticInterfaceMethods() ? isPresent() : isAbsent());
 
     ClassSubject aClassSubject = inspector.clazz(A.class);
     assertThat(aClassSubject, isPresent());
@@ -66,7 +69,9 @@
     // m() that happens to be used.
     assertEquals(1, aClassSubject.getDexProgramClass().interfaces.size());
     assertEquals(
-        jClassSubject.getDexProgramClass().type,
+        parameters.canUseDefaultAndStaticInterfaceMethods()
+            ? jClassSubject.getDexProgramClass().type
+            : iClassSubject.getDexProgramClass().type,
         aClassSubject.getDexProgramClass().interfaces.values[0]);
   }
 
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewritePassThroughTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewritePassThroughTest.java
index 717bf12..b175132 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewritePassThroughTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewritePassThroughTest.java
@@ -75,9 +75,6 @@
     testForD8(Backend.DEX)
         .addProgramFiles(getKotlinStdlibJar(kotlinc), getKotlinAnnotationJar(kotlinc))
         .setMinApi(AndroidApiLevel.B)
-        // Enable record desugaring support to force a non-identity naming lens
-        .addOptionsModification(
-            options -> options.testing.enableExperimentalRecordDesugaring = true)
         .compile()
         .inspect(
             inspector ->
diff --git a/src/test/java/com/android/tools/r8/memberrebinding/MemberRebindingBridgeRemovalTest.java b/src/test/java/com/android/tools/r8/memberrebinding/MemberRebindingBridgeRemovalTest.java
index aa38f06..031440f 100644
--- a/src/test/java/com/android/tools/r8/memberrebinding/MemberRebindingBridgeRemovalTest.java
+++ b/src/test/java/com/android/tools/r8/memberrebinding/MemberRebindingBridgeRemovalTest.java
@@ -29,7 +29,7 @@
 
   @Parameterized.Parameters(name = "{0}")
   public static TestParametersCollection data() {
-    return getTestParameters().withAllRuntimes().build();
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
   }
 
   public MemberRebindingBridgeRemovalTest(TestParameters parameters) {
@@ -44,7 +44,7 @@
         .addKeepMainRule(TestClass.class)
         .enableInliningAnnotations()
         .enableNoVerticalClassMergingAnnotations()
-        .setMinApi(parameters.getRuntime())
+        .setMinApi(parameters.getApiLevel())
         .compile()
         .inspect(this::inspect)
         .run(parameters.getRuntime(), TestClass.class)
diff --git a/src/test/java/com/android/tools/r8/memberrebinding/MemberRebindingRemoveInterfaceBridgeTest.java b/src/test/java/com/android/tools/r8/memberrebinding/MemberRebindingRemoveInterfaceBridgeTest.java
new file mode 100644
index 0000000..d569899
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/memberrebinding/MemberRebindingRemoveInterfaceBridgeTest.java
@@ -0,0 +1,106 @@
+// Copyright (c) 2021, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.memberrebinding;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isSynthetic;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.NoHorizontalClassMerging;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.DescriptorUtils;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.FoundMethodSubject;
+import com.android.tools.r8.utils.codeinspector.HorizontallyMergedClassesInspector;
+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 MemberRebindingRemoveInterfaceBridgeTest extends TestBase {
+
+  private final TestParameters parameters;
+  private final String newMainDescriptor = "La/Main;";
+  private final String newMainTypeName = DescriptorUtils.descriptorToJavaType(newMainDescriptor);
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  public MemberRebindingRemoveInterfaceBridgeTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    testForR8(parameters.getBackend())
+        .addProgramClasses(A.class, B.class, I.class, J.class)
+        .addProgramClassFileData(
+            transformer(Main.class).setClassDescriptor(newMainDescriptor).transform())
+        .setMinApi(parameters.getApiLevel())
+        .addKeepMainRule(newMainTypeName)
+        .addKeepClassAndMembersRules(I.class)
+        .enableInliningAnnotations()
+        .enableNoHorizontalClassMergingAnnotations()
+        .addHorizontallyMergedClassesInspector(
+            HorizontallyMergedClassesInspector::assertNoClassesMerged)
+        .run(parameters.getRuntime(), newMainTypeName)
+        .assertSuccessWithOutputLines("A::foo")
+        .inspect(
+            inspector -> {
+              ClassSubject clazz = inspector.clazz(J.class);
+              assertThat(clazz, isPresent());
+              // TODO(b/197490164): We should remove the bridge inserted here.
+              assertEquals(1, clazz.allMethods().size());
+              FoundMethodSubject foundMethodSubject = clazz.allMethods().get(0);
+              assertThat(foundMethodSubject, isSynthetic());
+            });
+  }
+
+  interface I {
+
+    void foo();
+  }
+
+  public interface J extends I {}
+
+  @NoHorizontalClassMerging
+  public static class A implements J {
+
+    @Override
+    @NeverInline
+    public void foo() {
+      System.out.println("A::foo");
+    }
+  }
+
+  @NoHorizontalClassMerging
+  public static class B implements J {
+
+    @Override
+    @NeverInline
+    public void foo() {
+      System.out.println("B::foo");
+    }
+  }
+
+  public static class /* a.Main */ Main {
+
+    public static void main(String[] args) {
+      callJ(args.length == 0 ? new A() : new B());
+    }
+
+    @NeverInline
+    private static void callJ(J j) {
+      j.foo();
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/memberrebinding/MemberRebindingRemoveInterfaceBridgeWithSubTypeTest.java b/src/test/java/com/android/tools/r8/memberrebinding/MemberRebindingRemoveInterfaceBridgeWithSubTypeTest.java
new file mode 100644
index 0000000..f1fc86d
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/memberrebinding/MemberRebindingRemoveInterfaceBridgeWithSubTypeTest.java
@@ -0,0 +1,101 @@
+// 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.memberrebinding;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isSynthetic;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.NoHorizontalClassMerging;
+import com.android.tools.r8.NoVerticalClassMerging;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.DescriptorUtils;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.FoundMethodSubject;
+import com.android.tools.r8.utils.codeinspector.HorizontallyMergedClassesInspector;
+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 MemberRebindingRemoveInterfaceBridgeWithSubTypeTest extends TestBase {
+
+  private final TestParameters parameters;
+  private final String newMainDescriptor = "La/Main;";
+  private final String newMainTypeName = DescriptorUtils.descriptorToJavaType(newMainDescriptor);
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  public MemberRebindingRemoveInterfaceBridgeWithSubTypeTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    testForR8(parameters.getBackend())
+        .addProgramClasses(A.class, B.class, I.class)
+        .addProgramClassFileData(
+            transformer(Main.class).setClassDescriptor(newMainDescriptor).transform())
+        .setMinApi(parameters.getApiLevel())
+        .addKeepMainRule(newMainTypeName)
+        .addKeepClassAndMembersRules(I.class)
+        .enableInliningAnnotations()
+        .enableNoHorizontalClassMergingAnnotations()
+        .enableNoVerticalClassMergingAnnotations()
+        .addHorizontallyMergedClassesInspector(
+            HorizontallyMergedClassesInspector::assertNoClassesMerged)
+        .noMinification()
+        .run(parameters.getRuntime(), newMainTypeName)
+        .assertSuccessWithOutputLines("B::foo")
+        .inspect(
+            inspector -> {
+              ClassSubject clazz = inspector.clazz(A.class);
+              assertThat(clazz, isPresent());
+              // TODO(b/197490164): We should remove the bridge inserted here.
+              assertEquals(1, clazz.virtualMethods().size());
+              FoundMethodSubject foundMethodSubject = clazz.virtualMethods().get(0);
+              assertThat(foundMethodSubject, isSynthetic());
+            });
+  }
+
+  interface I {
+
+    void foo();
+  }
+
+  @NoHorizontalClassMerging
+  @NoVerticalClassMerging
+  public abstract static class A implements I {}
+
+  @NoHorizontalClassMerging
+  public static class B extends A {
+
+    @Override
+    @NeverInline
+    public void foo() {
+      System.out.println("B::foo");
+    }
+  }
+
+  public static class /* a.Main */ Main {
+
+    public static void main(String[] args) {
+      callJ(args.length == 0 ? new B() : null);
+    }
+
+    @NeverInline
+    private static void callJ(A a) {
+      a.foo();
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/memberrebinding/MemberRebindingRemoveInterfaceDefaultBridgeTest.java b/src/test/java/com/android/tools/r8/memberrebinding/MemberRebindingRemoveInterfaceDefaultBridgeTest.java
new file mode 100644
index 0000000..e11bdfe
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/memberrebinding/MemberRebindingRemoveInterfaceDefaultBridgeTest.java
@@ -0,0 +1,92 @@
+// Copyright (c) 2021, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.memberrebinding;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isAbsent;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.NoHorizontalClassMerging;
+import com.android.tools.r8.NoVerticalClassMerging;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.DescriptorUtils;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class MemberRebindingRemoveInterfaceDefaultBridgeTest extends TestBase {
+
+  private final TestParameters parameters;
+  private final String newMainDescriptor = "La/Main;";
+  private final String newMainTypeName = DescriptorUtils.descriptorToJavaType(newMainDescriptor);
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  public MemberRebindingRemoveInterfaceDefaultBridgeTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    testForR8(parameters.getBackend())
+        .addProgramClasses(A.class, I.class, J.class)
+        .addProgramClassFileData(
+            transformer(Main.class).setClassDescriptor(newMainDescriptor).transform())
+        .setMinApi(parameters.getApiLevel())
+        .addKeepMainRule(newMainTypeName)
+        .addKeepClassAndMembersRules(I.class)
+        .enableInliningAnnotations()
+        .enableNoHorizontalClassMergingAnnotations()
+        .enableNoVerticalClassMergingAnnotations()
+        .run(parameters.getRuntime(), newMainTypeName)
+        .assertSuccessWithOutputLines("I::foo")
+        .inspect(
+            inspector -> {
+              ClassSubject clazz = inspector.clazz(J.class);
+              assertThat(clazz, isPresent());
+              assertTrue(clazz.allMethods().isEmpty());
+              if (!parameters.canUseDefaultAndStaticInterfaceMethods()) {
+                ClassSubject classSubject = clazz.toCompanionClass();
+                assertThat(classSubject, isAbsent());
+              }
+            });
+  }
+
+  interface I {
+
+    @NeverInline
+    default void foo() {
+      System.out.println("I::foo");
+    }
+  }
+
+  @NoVerticalClassMerging
+  public interface J extends I {}
+
+  @NoHorizontalClassMerging
+  public static class A implements J {}
+
+  public static class /* a.Main */ Main {
+
+    public static void main(String[] args) {
+      callJ(args.length == 0 ? new A() : null);
+    }
+
+    @NeverInline
+    private static void callJ(J j) {
+      j.foo();
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/memberrebinding/MemberRebindingRemoveStaticBridgeTest.java b/src/test/java/com/android/tools/r8/memberrebinding/MemberRebindingRemoveStaticBridgeTest.java
new file mode 100644
index 0000000..8bdaa09
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/memberrebinding/MemberRebindingRemoveStaticBridgeTest.java
@@ -0,0 +1,79 @@
+// 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.memberrebinding;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.NoVerticalClassMerging;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.DescriptorUtils;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.HorizontallyMergedClassesInspector;
+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 MemberRebindingRemoveStaticBridgeTest extends TestBase {
+
+  private final TestParameters parameters;
+  private final String newMainDescriptor = "La/Main;";
+  private final String newMainTypeName = DescriptorUtils.descriptorToJavaType(newMainDescriptor);
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  public MemberRebindingRemoveStaticBridgeTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    testForR8(parameters.getBackend())
+        .addProgramClasses(A.class, B.class)
+        .addProgramClassFileData(
+            transformer(Main.class).setClassDescriptor(newMainDescriptor).transform())
+        .setMinApi(parameters.getApiLevel())
+        .addKeepMainRule(newMainTypeName)
+        .enableInliningAnnotations()
+        .enableNoVerticalClassMergingAnnotations()
+        .addHorizontallyMergedClassesInspector(
+            HorizontallyMergedClassesInspector::assertNoClassesMerged)
+        .run(parameters.getRuntime(), newMainTypeName)
+        .assertSuccessWithOutputLines("Hello World!")
+        .inspect(
+            inspector -> {
+              ClassSubject clazz = inspector.clazz(B.class);
+              assertThat(clazz, isPresent());
+              assertTrue(clazz.allMethods().isEmpty());
+            });
+  }
+
+  @NoVerticalClassMerging
+  static class A {
+
+    @NeverInline
+    public static void foo() {
+      System.out.println("Hello World!");
+    }
+  }
+
+  public static class B extends A {}
+
+  public static class /* a.Main */ Main {
+
+    public static void main(String[] args) {
+      B.foo();
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/memberrebinding/MemberRebindingRemoveVirtualBridgeDontShrinkTest.java b/src/test/java/com/android/tools/r8/memberrebinding/MemberRebindingRemoveVirtualBridgeDontShrinkTest.java
new file mode 100644
index 0000000..c23c295
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/memberrebinding/MemberRebindingRemoveVirtualBridgeDontShrinkTest.java
@@ -0,0 +1,80 @@
+// Copyright (c) 2021, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.memberrebinding;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertFalse;
+
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.DescriptorUtils;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.FoundMethodSubject;
+import com.android.tools.r8.utils.codeinspector.HorizontallyMergedClassesInspector;
+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 MemberRebindingRemoveVirtualBridgeDontShrinkTest extends TestBase {
+
+  private final TestParameters parameters;
+  private final String newMainDescriptor = "La/Main;";
+  private final String newMainTypeName = DescriptorUtils.descriptorToJavaType(newMainDescriptor);
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  public MemberRebindingRemoveVirtualBridgeDontShrinkTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    testForR8(parameters.getBackend())
+        .addProgramClasses(A.class, B.class)
+        .addProgramClassFileData(
+            transformer(Main.class).setClassDescriptor(newMainDescriptor).transform())
+        .setMinApi(parameters.getApiLevel())
+        .addKeepMainRule(newMainTypeName)
+        .enableInliningAnnotations()
+        .addHorizontallyMergedClassesInspector(
+            HorizontallyMergedClassesInspector::assertNoClassesMerged)
+        .addDontShrink()
+        .addDontOptimize()
+        .run(parameters.getRuntime(), newMainTypeName)
+        .assertSuccessWithOutputLines("Hello World!")
+        .inspect(
+            inspector -> {
+              ClassSubject clazz = inspector.clazz(B.class);
+              assertThat(clazz, isPresent());
+              // TODO(b/197491051): We should be able to remove bridges we have inserted.
+              assertFalse(clazz.allMethods(FoundMethodSubject::isBridge).isEmpty());
+            });
+  }
+
+  static class A {
+
+    @NeverInline
+    public void foo() {
+      System.out.println("Hello World!");
+    }
+  }
+
+  public static class B extends A {}
+
+  public static class /* a.Main */ Main {
+
+    public static void main(String[] args) {
+      new B().foo();
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/memberrebinding/MemberRebindingRemoveVirtualBridgeTest.java b/src/test/java/com/android/tools/r8/memberrebinding/MemberRebindingRemoveVirtualBridgeTest.java
new file mode 100644
index 0000000..f38e042
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/memberrebinding/MemberRebindingRemoveVirtualBridgeTest.java
@@ -0,0 +1,86 @@
+// Copyright (c) 2021, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.memberrebinding;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isInstanceInitializer;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.NoVerticalClassMerging;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.DescriptorUtils;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.FoundMethodSubject;
+import com.android.tools.r8.utils.codeinspector.HorizontallyMergedClassesInspector;
+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 MemberRebindingRemoveVirtualBridgeTest extends TestBase {
+
+  private final TestParameters parameters;
+  private final String newMainDescriptor = "La/Main;";
+  private final String newMainTypeName = DescriptorUtils.descriptorToJavaType(newMainDescriptor);
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  public MemberRebindingRemoveVirtualBridgeTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    testForR8(parameters.getBackend())
+        .addProgramClasses(A.class, B.class)
+        .addProgramClassFileData(
+            transformer(Main.class).setClassDescriptor(newMainDescriptor).transform())
+        .setMinApi(parameters.getApiLevel())
+        .addKeepMainRule(newMainTypeName)
+        .enableInliningAnnotations()
+        .enableNoVerticalClassMergingAnnotations()
+        .enableNeverClassInliningAnnotations()
+        .addHorizontallyMergedClassesInspector(
+            HorizontallyMergedClassesInspector::assertNoClassesMerged)
+        .run(parameters.getRuntime(), newMainTypeName)
+        .assertSuccessWithOutputLines("Hello World!")
+        .inspect(
+            inspector -> {
+              ClassSubject clazz = inspector.clazz(B.class);
+              assertThat(clazz, isPresent());
+              assertEquals(1, clazz.allMethods().size());
+              FoundMethodSubject foundMethodSubject = clazz.allMethods().get(0);
+              assertThat(foundMethodSubject, isInstanceInitializer());
+            });
+  }
+
+  @NoVerticalClassMerging
+  static class A {
+
+    @NeverInline
+    public void foo() {
+      System.out.println("Hello World!");
+    }
+  }
+
+  @NeverClassInline
+  public static class B extends A {}
+
+  public static class /* a.Main */ Main {
+
+    public static void main(String[] args) {
+      new B().foo();
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/naming/applymapping/desugar/DefaultInterfaceMethodTest.java b/src/test/java/com/android/tools/r8/naming/applymapping/desugar/DefaultInterfaceMethodTest.java
index 1868602..18f0c96 100644
--- a/src/test/java/com/android/tools/r8/naming/applymapping/desugar/DefaultInterfaceMethodTest.java
+++ b/src/test/java/com/android/tools/r8/naming/applymapping/desugar/DefaultInterfaceMethodTest.java
@@ -3,16 +3,15 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.naming.applymapping.desugar;
 
-import static com.android.tools.r8.references.Reference.classFromClass;
-import static org.junit.Assert.assertTrue;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertEquals;
 
 import com.android.tools.r8.R8TestCompileResult;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
-import com.android.tools.r8.ir.desugar.itf.InterfaceDesugaringForTesting;
-import com.android.tools.r8.references.Reference;
-import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.synthesis.SyntheticItemsTestUtils;
 import com.android.tools.r8.utils.StringUtils;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
@@ -73,26 +72,21 @@
 
   @Test
   public void testLibraryLinkedWithProgram() throws Throwable {
-    String ruleContent = "-keep class " + LibraryInterface.class.getTypeName() + " { *; }";
     R8TestCompileResult libraryResult =
         testForR8(parameters.getBackend())
             .addProgramClasses(LibraryInterface.class)
-            .addKeepRules(ruleContent)
+            .addKeepClassAndMembersRules(LibraryInterface.class)
             .setMinApi(parameters.getApiLevel())
             .compile();
     CodeInspector inspector = libraryResult.inspector();
-    assertTrue(inspector.clazz(LibraryInterface.class).isPresent());
-    assertTrue(inspector.method(LibraryInterface.class.getMethod("foo")).isPresent());
-    if (willDesugarDefaultInterfaceMethods(parameters.getApiLevel())) {
+    assertThat(inspector.clazz(LibraryInterface.class), isPresent());
+    assertThat(inspector.method(LibraryInterface.class.getMethod("foo")), isPresent());
+    if (!parameters.canUseDefaultAndStaticInterfaceMethods()) {
       ClassSubject companion =
-          inspector.clazz(
-              Reference.classFromDescriptor(
-                  InterfaceDesugaringForTesting.getCompanionClassDescriptor(
-                      classFromClass(LibraryInterface.class).getDescriptor())));
-      // Check that we included the companion class.
-      assertTrue(companion.isPresent());
-      // TODO(b/129223905): Check the method is also present on the companion class.
-      assertTrue(inspector.method(LibraryInterface.class.getMethod("foo")).isPresent());
+          inspector.clazz(SyntheticItemsTestUtils.syntheticCompanionClass(LibraryInterface.class));
+      // Check that we included the companion class and method.
+      assertThat(companion, isPresent());
+      assertEquals(1, companion.allMethods().size());
     }
 
     testForR8(parameters.getBackend())
@@ -107,8 +101,4 @@
         .run(parameters.getRuntime(), ProgramClass.class)
         .assertSuccessWithOutput(EXPECTED);
   }
-
-  private static boolean willDesugarDefaultInterfaceMethods(AndroidApiLevel apiLevel) {
-    return apiLevel != null && apiLevel.getLevel() < AndroidApiLevel.N.getLevel();
-  }
 }
diff --git a/src/test/java/com/android/tools/r8/naming/applymapping/desugar/StaticInterfaceMethodTest.java b/src/test/java/com/android/tools/r8/naming/applymapping/desugar/StaticInterfaceMethodTest.java
new file mode 100644
index 0000000..1dfd029
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/naming/applymapping/desugar/StaticInterfaceMethodTest.java
@@ -0,0 +1,110 @@
+// 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.naming.applymapping.desugar;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isAbsent;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.R8TestCompileResult;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.synthesis.SyntheticItemsTestUtils;
+import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import org.junit.Assume;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+// Reproduction for b/196345511.
+@RunWith(Parameterized.class)
+public class StaticInterfaceMethodTest extends TestBase {
+
+  public static final String OUTPUT = "Called LibraryInterface::foo";
+  public static final String EXPECTED = StringUtils.lines(OUTPUT);
+
+  public interface LibraryInterface {
+    static void foo() {
+      System.out.println(OUTPUT);
+    }
+  }
+
+  public static class ProgramClass implements LibraryInterface {
+
+    public static void main(String[] args) {
+      LibraryInterface.foo();
+    }
+  }
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection params() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  private final TestParameters parameters;
+
+  public StaticInterfaceMethodTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testJvm() throws Throwable {
+    Assume.assumeTrue(parameters.isCfRuntime());
+    testForJvm()
+        .addProgramClasses(LibraryInterface.class, ProgramClass.class)
+        .run(parameters.getRuntime(), ProgramClass.class)
+        .assertSuccessWithOutput(EXPECTED);
+  }
+
+  @Test
+  public void testFullProgram() throws Throwable {
+    testForR8(parameters.getBackend())
+        .addProgramClasses(LibraryInterface.class, ProgramClass.class)
+        .addKeepMainRule(ProgramClass.class)
+        .setMinApi(parameters.getApiLevel())
+        .run(parameters.getRuntime(), ProgramClass.class)
+        .assertSuccessWithOutput(EXPECTED);
+  }
+
+  @Test
+  public void testLibraryLinkedWithProgram() throws Throwable {
+    R8TestCompileResult libraryResult =
+        testForR8(parameters.getBackend())
+            .addProgramClasses(LibraryInterface.class)
+            .addKeepClassAndMembersRules(LibraryInterface.class)
+            .setMinApi(parameters.getApiLevel())
+            .compile();
+    CodeInspector inspector = libraryResult.inspector();
+    ClassSubject libraryInterface = inspector.clazz(LibraryInterface.class);
+    assertThat(libraryInterface, isPresent());
+    if (parameters.canUseDefaultAndStaticInterfaceMethods()) {
+      assertThat(libraryInterface.method(LibraryInterface.class.getMethod("foo")), isPresent());
+    } else {
+      // Desugaring must remove the static on the interface.
+      assertThat(libraryInterface.method(LibraryInterface.class.getMethod("foo")), isAbsent());
+      // Check that we included the companion class and method.
+      ClassSubject companion =
+          inspector.clazz(SyntheticItemsTestUtils.syntheticCompanionClass(LibraryInterface.class));
+      assertThat(companion, isPresent());
+      assertEquals(1, companion.allMethods().size());
+    }
+
+    testForR8(parameters.getBackend())
+        .noTreeShaking()
+        .addProgramClasses(ProgramClass.class)
+        .addClasspathClasses(LibraryInterface.class)
+        .addApplyMapping(libraryResult.getProguardMap())
+        .addKeepMainRule(ProgramClass.class)
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .addRunClasspathFiles(libraryResult.writeToZip())
+        .run(parameters.getRuntime(), ProgramClass.class)
+        .assertSuccessWithOutput(EXPECTED);
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/resolution/DefaultMethodShadowedByStaticTest.java b/src/test/java/com/android/tools/r8/resolution/DefaultMethodShadowedByStaticTest.java
index 19424d8..b974111 100644
--- a/src/test/java/com/android/tools/r8/resolution/DefaultMethodShadowedByStaticTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/DefaultMethodShadowedByStaticTest.java
@@ -6,7 +6,6 @@
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
-import com.android.tools.r8.TestRunResult;
 import com.android.tools.r8.ToolHelper.DexVm.Version;
 import com.android.tools.r8.resolution.shadowing1.AClassDump;
 import com.android.tools.r8.resolution.shadowing1.InterfaceDump;
@@ -39,22 +38,15 @@
 
   @Test
   public void testReference() throws Exception {
-    TestRunResult<?> result =
-        testForRuntime(parameters)
-            .addProgramClassFileData(CLASSES)
-            .run(parameters.getRuntime(), "Main");
-    if (parameters.isDexRuntime()
-        && (parameters.getApiLevel().isLessThan(apiLevelWithStaticInterfaceMethodsSupport())
-            || parameters.getDexRuntimeVersion().equals(Version.V7_0_0))) {
-      // TODO(b/167535447): Desugaring should preserve the error.
-      result.assertSuccessWithOutputLines("42");
-    } else if (parameters.isDexRuntime()
-        && parameters.getDexRuntimeVersion().equals(Version.V7_0_0)) {
-      // Note: VM 7.0.0 without desugaring of defaults will incorrectly allow the virtual dispatch.
-      result.assertSuccessWithOutputLines("42");
-    } else {
-      result.assertFailureWithErrorThatThrows(IncompatibleClassChangeError.class);
-    }
+    testForRuntime(parameters)
+        .addProgramClassFileData(CLASSES)
+        .run(parameters.getRuntime(), "Main")
+        .applyIf(
+            // When not desugaring interfaces, the v7 runtime fails to throw the correct error.
+            parameters.canUseDefaultAndStaticInterfaceMethods()
+                && parameters.isDexRuntimeVersion(Version.V7_0_0),
+            r -> r.assertSuccessWithOutputLines("42"),
+            r -> r.assertFailureWithErrorThatThrows(IncompatibleClassChangeError.class));
   }
 
   @Test
diff --git a/src/test/java/com/android/tools/r8/resolution/SingleTargetLookupTest.java b/src/test/java/com/android/tools/r8/resolution/SingleTargetLookupTest.java
index b176930..7d18aee 100644
--- a/src/test/java/com/android/tools/r8/resolution/SingleTargetLookupTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/SingleTargetLookupTest.java
@@ -8,6 +8,7 @@
 import static org.junit.Assert.assertTrue;
 
 import com.android.tools.r8.AsmTestBase;
+import com.android.tools.r8.TestAppViewBuilder;
 import com.android.tools.r8.graph.AppInfo;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexEncodedMethod;
@@ -89,12 +90,17 @@
   @BeforeClass
   public static void computeAppInfo() throws Exception {
     appView =
-        computeAppViewWithLiveness(
-            buildClassesWithTestingAnnotations(CLASSES)
-                .addClassProgramData(ASM_CLASSES)
-                .addLibraryFile(getMostRecentAndroidJar())
-                .build(),
-            Main.class);
+        TestAppViewBuilder.builder()
+            .addAndroidApp(
+                buildClassesWithTestingAnnotations(CLASSES)
+                    .addClassProgramData(ASM_CLASSES)
+                    .addLibraryFile(getMostRecentAndroidJar())
+                    .build())
+            .addKeepMainRule(Main.class)
+            // Some of these tests resolve default methods.
+            // If desugared they will hit the forward methods and not the expected defaults.
+            .setMinApi(apiLevelWithDefaultInterfaceMethodsSupport())
+            .buildWithLiveness();
     appInfo = appView.appInfo();
   }
 
diff --git a/src/test/java/com/android/tools/r8/resolution/VirtualOverrideOfStaticMethodWithVirtualParentInterfaceTest.java b/src/test/java/com/android/tools/r8/resolution/VirtualOverrideOfStaticMethodWithVirtualParentInterfaceTest.java
index cbad874..846c994 100644
--- a/src/test/java/com/android/tools/r8/resolution/VirtualOverrideOfStaticMethodWithVirtualParentInterfaceTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/VirtualOverrideOfStaticMethodWithVirtualParentInterfaceTest.java
@@ -4,22 +4,18 @@
 package com.android.tools.r8.resolution;
 
 import static com.android.tools.r8.ToolHelper.getMostRecentAndroidJar;
-import static org.hamcrest.CoreMatchers.containsString;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 
 import com.android.tools.r8.AsmTestBase;
-import com.android.tools.r8.R8TestRunResult;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
-import com.android.tools.r8.TestRunResult;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.MethodResolutionResult;
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
-import com.android.tools.r8.utils.AndroidApiLevel;
 import com.google.common.collect.ImmutableList;
 import java.util.List;
 import org.junit.Assert;
@@ -150,22 +146,11 @@
 
   @Test
   public void runJvmAndD8() throws Exception {
-    TestRunResult<?> runResult;
-    if (parameters.isCfRuntime()) {
-      runResult =
-          testForJvm()
-              .addProgramClasses(CLASSES)
-              .addProgramClassFileData(DUMP)
-              .run(parameters.getRuntime(), Main.class);
-    } else {
-      runResult =
-          testForD8()
-              .addProgramClasses(CLASSES)
-              .addProgramClassFileData(DUMP)
-              .setMinApi(parameters.getApiLevel())
-              .run(parameters.getRuntime(), Main.class);
-    }
-    checkResult(runResult);
+    testForRuntime(parameters)
+        .addProgramClasses(CLASSES)
+        .addProgramClassFileData(DUMP)
+        .run(parameters.getRuntime(), Main.class)
+        .assertFailureWithErrorThatThrows(IncompatibleClassChangeError.class);
   }
 
   @Test
@@ -179,33 +164,13 @@
   }
 
   public void runR8(boolean enableVerticalClassMerging) throws Exception {
-    R8TestRunResult runResult =
-        testForR8(parameters.getBackend())
-            .addProgramClasses(CLASSES)
-            .addProgramClassFileData(DUMP)
-            .addKeepMainRule(Main.class)
-            .setMinApi(parameters.getApiLevel())
-            .addOptionsModification(o -> o.enableVerticalClassMerging = enableVerticalClassMerging)
-            .run(parameters.getRuntime(), Main.class);
-    if (enableVerticalClassMerging) {
-      // Vertical class merging will merge B and C and change the instruction to invoke-virtual
-      // causing the legacy ART runtime behavior to match the expected error.
-      runResult.assertFailureWithErrorThatMatches(containsString("IncompatibleClassChangeError"));
-    } else {
-      checkResult(runResult);
-    }
-  }
-
-  private void checkResult(TestRunResult<?> runResult) {
-    runResult.assertFailureWithErrorThatMatches(containsString(expectedRuntimeError()));
-  }
-
-  private String expectedRuntimeError() {
-    if (parameters.isDexRuntime()
-        && parameters.getApiLevel().getLevel() < AndroidApiLevel.N.getLevel()) {
-      // When desugaring default interface methods the error will be NoSuchMethodError.
-      return "NoSuchMethodError";
-    }
-    return "IncompatibleClassChangeError";
+    testForR8(parameters.getBackend())
+        .addProgramClasses(CLASSES)
+        .addProgramClassFileData(DUMP)
+        .addKeepMainRule(Main.class)
+        .setMinApi(parameters.getApiLevel())
+        .addOptionsModification(o -> o.enableVerticalClassMerging = enableVerticalClassMerging)
+        .run(parameters.getRuntime(), Main.class)
+        .assertFailureWithErrorThatThrows(IncompatibleClassChangeError.class);
   }
 }
diff --git a/src/test/java/com/android/tools/r8/resolution/VirtualOverrideOfStaticMethodWithVirtualParentTest.java b/src/test/java/com/android/tools/r8/resolution/VirtualOverrideOfStaticMethodWithVirtualParentTest.java
index c6e9206..ca29588 100644
--- a/src/test/java/com/android/tools/r8/resolution/VirtualOverrideOfStaticMethodWithVirtualParentTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/VirtualOverrideOfStaticMethodWithVirtualParentTest.java
@@ -4,7 +4,6 @@
 package com.android.tools.r8.resolution;
 
 import static com.android.tools.r8.ToolHelper.getMostRecentAndroidJar;
-import static org.hamcrest.CoreMatchers.containsString;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 
@@ -13,8 +12,7 @@
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.TestRunResult;
-import com.android.tools.r8.TestRuntime;
-import com.android.tools.r8.ToolHelper.DexVm;
+import com.android.tools.r8.ToolHelper.DexVm.Version;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexProgramClass;
@@ -226,23 +224,18 @@
   }
 
   private void checkResult(TestRunResult<?> runResult, boolean isCorrectedByR8) {
-    if (expectedToIncorrectlyRun(parameters.getRuntime(), isCorrectedByR8)) {
-      // Do to incorrect resolution, some Art VMs will resolve to Base.f (ignoring A.f) and thus
+    if (expectedToIncorrectlyRun(isCorrectedByR8)) {
+      // Do to incorrect resolution, some Art 7 will resolve to Base.f (ignoring A.f) and thus
       // virtual dispatch to C.f. See b/140013075.
       runResult.assertSuccessWithOutputLines("Called C.f");
     } else {
-      runResult.assertFailureWithErrorThatMatches(containsString(expectedRuntimeError()));
+      runResult.assertFailureWithErrorThatThrows(IncompatibleClassChangeError.class);
     }
   }
 
-  private boolean expectedToIncorrectlyRun(TestRuntime runtime, boolean isCorrectedByR8) {
+  private boolean expectedToIncorrectlyRun(boolean isCorrectedByR8) {
     return !isCorrectedByR8
-        && runtime.isDex()
-        && runtime.asDex().getVm().isNewerThan(DexVm.ART_4_4_4_HOST)
-        && runtime.asDex().getVm().isOlderThanOrEqual(DexVm.ART_7_0_0_HOST);
-  }
-
-  private static String expectedRuntimeError() {
-    return "IncompatibleClassChangeError";
+        && parameters.canUseDefaultAndStaticInterfaceMethods()
+        && parameters.isDexRuntimeVersion(Version.V7_0_0);
   }
 }
diff --git a/src/test/java/com/android/tools/r8/resolution/access/NestInvokeSpecialInterfaceMethodAccessWithIntermediateTest.java b/src/test/java/com/android/tools/r8/resolution/access/NestInvokeSpecialInterfaceMethodAccessWithIntermediateTest.java
index 4787b97..82c6b63 100644
--- a/src/test/java/com/android/tools/r8/resolution/access/NestInvokeSpecialInterfaceMethodAccessWithIntermediateTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/access/NestInvokeSpecialInterfaceMethodAccessWithIntermediateTest.java
@@ -10,6 +10,7 @@
 import static org.junit.Assume.assumeFalse;
 import static org.junit.Assume.assumeTrue;
 
+import com.android.tools.r8.TestAppViewBuilder;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestRunResult;
@@ -20,6 +21,7 @@
 import com.android.tools.r8.graph.MethodResolutionResult.NoSuchMethodResult;
 import com.android.tools.r8.references.Reference;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.synthesis.SyntheticItemsTestUtils;
 import com.android.tools.r8.transformers.ClassFileTransformer;
 import com.android.tools.r8.utils.BooleanUtils;
 import com.android.tools.r8.utils.DescriptorUtils;
@@ -52,6 +54,7 @@
             .withCfRuntimesStartingFromIncluding(JDK11)
             .withDexRuntimes()
             .withAllApiLevels()
+            .enableApiLevelsForCf()
             .build(),
         BooleanUtils.values(),
         BooleanUtils.values());
@@ -132,7 +135,16 @@
     MethodSubject foo = inspector.clazz(callerClass).uniqueMethodWithName("foo");
     assertTrue(
         foo.streamInstructions()
-            .anyMatch(i -> i.asCfInstruction().isInvokeSpecial() && i.getMethod() == method));
+            .anyMatch(
+                i -> {
+                  if (parameters.canUseDefaultAndStaticInterfaceMethodsWhenDesugaring()) {
+                    return i.asCfInstruction().isInvokeSpecial() && i.getMethod() == method;
+                  } else {
+                    return i.isInvokeStatic()
+                        && SyntheticItemsTestUtils.isInternalThrowNSME(
+                            i.getMethod().asMethodReference());
+                  }
+                }));
   }
 
   private DexMethod getTargetMethodSignature(Class<?> declaredClass, AppInfoWithLiveness appInfo) {
@@ -148,12 +160,13 @@
   }
 
   private AppView<AppInfoWithLiveness> getAppView() throws Exception {
-    return computeAppViewWithLiveness(
-        buildClasses(getClasses())
-            .addClassProgramData(getTransformedClasses())
-            .addLibraryFile(TestBase.runtimeJar(parameters.getBackend()))
-            .build(),
-        Main.class);
+    return TestAppViewBuilder.builder()
+        .addProgramClasses(getClasses())
+        .addProgramClassFileData(getTransformedClasses())
+        .addLibraryFiles(parameters.getDefaultRuntimeLibrary())
+        .addKeepMainRule(Main.class)
+        .setMinApi(parameters.getApiLevel())
+        .buildWithLiveness();
   }
 
   @Test
diff --git a/src/test/java/com/android/tools/r8/resolution/interfacediamonds/DefaultLeftAbstractRightTest.java b/src/test/java/com/android/tools/r8/resolution/interfacediamonds/DefaultLeftAbstractRightTest.java
index d6edf4f..47d664f 100644
--- a/src/test/java/com/android/tools/r8/resolution/interfacediamonds/DefaultLeftAbstractRightTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/interfacediamonds/DefaultLeftAbstractRightTest.java
@@ -6,6 +6,7 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assume.assumeTrue;
 
+import com.android.tools.r8.TestAppViewBuilder;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
@@ -15,7 +16,6 @@
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.google.common.collect.ImmutableList;
 import java.util.Collection;
-import java.util.Collections;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
@@ -42,12 +42,13 @@
     // The resolution is runtime independent, so just run it on the default CF VM.
     assumeTrue(parameters.useRuntimeAsNoneRuntime());
     AppInfoWithLiveness appInfo =
-        computeAppViewWithLiveness(
-                buildClasses(CLASSES)
-                    .addClassProgramData(Collections.singletonList(transformB()))
-                    .addLibraryFile(parameters.getDefaultRuntimeLibrary())
-                    .build(),
-                Main.class)
+        TestAppViewBuilder.builder()
+            .addProgramClasses(CLASSES)
+            .addProgramClassFileData(transformB())
+            .addLibraryFiles(parameters.getDefaultRuntimeLibrary())
+            .addKeepMainRule(Main.class)
+            .setMinApi(apiLevelWithDefaultInterfaceMethodsSupport())
+            .buildWithLiveness()
             .appInfo();
     DexMethod method = buildNullaryVoidMethod(B.class, "f", appInfo.dexItemFactory());
     MethodResolutionResult resolutionResult = appInfo.resolveMethodOnClass(method);
diff --git a/src/test/java/com/android/tools/r8/resolution/interfacediamonds/DefaultRightAbstractLeftTest.java b/src/test/java/com/android/tools/r8/resolution/interfacediamonds/DefaultRightAbstractLeftTest.java
index 77fad9a..f6b2df5 100644
--- a/src/test/java/com/android/tools/r8/resolution/interfacediamonds/DefaultRightAbstractLeftTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/interfacediamonds/DefaultRightAbstractLeftTest.java
@@ -6,6 +6,7 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assume.assumeTrue;
 
+import com.android.tools.r8.TestAppViewBuilder;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
@@ -15,7 +16,6 @@
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.google.common.collect.ImmutableList;
 import java.util.Collection;
-import java.util.Collections;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
@@ -42,12 +42,13 @@
     // The resolution is runtime independent, so just run it on the default CF VM.
     assumeTrue(parameters.useRuntimeAsNoneRuntime());
     AppInfoWithLiveness appInfo =
-        computeAppViewWithLiveness(
-                buildClasses(CLASSES)
-                    .addClassProgramData(Collections.singletonList(transformB()))
-                    .addLibraryFile(parameters.getDefaultRuntimeLibrary())
-                    .build(),
-                Main.class)
+        TestAppViewBuilder.builder()
+            .addProgramClasses(CLASSES)
+            .addProgramClassFileData(transformB())
+            .addLibraryFiles(parameters.getDefaultRuntimeLibrary())
+            .addKeepMainRule(Main.class)
+            .setMinApi(apiLevelWithDefaultInterfaceMethodsSupport())
+            .buildWithLiveness()
             .appInfo();
     DexMethod method = buildNullaryVoidMethod(B.class, "f", appInfo.dexItemFactory());
     MethodResolutionResult resolutionResult = appInfo.resolveMethodOnClass(method);
diff --git a/src/test/java/com/android/tools/r8/resolution/interfacediamonds/DefaultTopAndBothTest.java b/src/test/java/com/android/tools/r8/resolution/interfacediamonds/DefaultTopAndBothTest.java
index 41307e8..af70288 100644
--- a/src/test/java/com/android/tools/r8/resolution/interfacediamonds/DefaultTopAndBothTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/interfacediamonds/DefaultTopAndBothTest.java
@@ -7,6 +7,7 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assume.assumeTrue;
 
+import com.android.tools.r8.TestAppViewBuilder;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
@@ -15,7 +16,6 @@
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableSet;
-import java.util.Collections;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Set;
@@ -44,12 +44,13 @@
     // The resolution is runtime independent, so just run it on the default CF VM.
     assumeTrue(parameters.useRuntimeAsNoneRuntime());
     AppInfoWithLiveness appInfo =
-        computeAppViewWithLiveness(
-                buildClasses(CLASSES)
-                    .addClassProgramData(Collections.singletonList(transformB()))
-                    .addLibraryFile(parameters.getDefaultRuntimeLibrary())
-                    .build(),
-                Main.class)
+        TestAppViewBuilder.builder()
+            .addProgramClasses(CLASSES)
+            .addProgramClassFileData(transformB())
+            .addLibraryFiles(parameters.getDefaultRuntimeLibrary())
+            .addKeepMainRule(Main.class)
+            .setMinApi(apiLevelWithDefaultInterfaceMethodsSupport())
+            .buildWithLiveness()
             .appInfo();
     DexMethod method = buildNullaryVoidMethod(B.class, "f", appInfo.dexItemFactory());
     MethodResolutionResult resolutionResult = appInfo.resolveMethodOnClass(method);
diff --git a/src/test/java/com/android/tools/r8/resolution/interfacediamonds/DefaultTopAndLeftTest.java b/src/test/java/com/android/tools/r8/resolution/interfacediamonds/DefaultTopAndLeftTest.java
index 9b9a45d..d2d08a5 100644
--- a/src/test/java/com/android/tools/r8/resolution/interfacediamonds/DefaultTopAndLeftTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/interfacediamonds/DefaultTopAndLeftTest.java
@@ -6,6 +6,7 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assume.assumeTrue;
 
+import com.android.tools.r8.TestAppViewBuilder;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
@@ -41,9 +42,12 @@
     // The resolution is runtime independent, so just run it on the default CF VM.
     assumeTrue(parameters.useRuntimeAsNoneRuntime());
     AppInfoWithLiveness appInfo =
-        computeAppViewWithLiveness(
-                buildClasses(CLASSES).addLibraryFile(parameters.getDefaultRuntimeLibrary()).build(),
-                Main.class)
+        TestAppViewBuilder.builder()
+            .addProgramClasses(CLASSES)
+            .addLibraryFiles(parameters.getDefaultRuntimeLibrary())
+            .addKeepMainRule(Main.class)
+            .setMinApi(apiLevelWithDefaultInterfaceMethodsSupport())
+            .buildWithLiveness()
             .appInfo();
     DexMethod method = buildNullaryVoidMethod(B.class, "f", appInfo.dexItemFactory());
     MethodResolutionResult resolutionResult = appInfo.resolveMethodOnClass(method);
diff --git a/src/test/java/com/android/tools/r8/resolution/interfacediamonds/DefaultTopAndRightTest.java b/src/test/java/com/android/tools/r8/resolution/interfacediamonds/DefaultTopAndRightTest.java
index 50dc8d8..1b900b4 100644
--- a/src/test/java/com/android/tools/r8/resolution/interfacediamonds/DefaultTopAndRightTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/interfacediamonds/DefaultTopAndRightTest.java
@@ -6,6 +6,7 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assume.assumeTrue;
 
+import com.android.tools.r8.TestAppViewBuilder;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
@@ -41,9 +42,12 @@
     // The resolution is runtime independent, so just run it on the default CF VM.
     assumeTrue(parameters.useRuntimeAsNoneRuntime());
     AppInfoWithLiveness appInfo =
-        computeAppViewWithLiveness(
-                buildClasses(CLASSES).addLibraryFile(parameters.getDefaultRuntimeLibrary()).build(),
-                Main.class)
+        TestAppViewBuilder.builder()
+            .addProgramClasses(CLASSES)
+            .addLibraryFiles(parameters.getDefaultRuntimeLibrary())
+            .addKeepMainRule(Main.class)
+            .setMinApi(apiLevelWithDefaultInterfaceMethodsSupport())
+            .buildWithLiveness()
             .appInfo();
     DexMethod method = buildNullaryVoidMethod(B.class, "f", appInfo.dexItemFactory());
     MethodResolutionResult resolutionResult = appInfo.resolveMethodOnClass(method);
diff --git a/src/test/java/com/android/tools/r8/resolution/interfacediamonds/TwoDefaultMethodsWithoutTopTest.java b/src/test/java/com/android/tools/r8/resolution/interfacediamonds/TwoDefaultMethodsWithoutTopTest.java
index 0139049..8901542 100644
--- a/src/test/java/com/android/tools/r8/resolution/interfacediamonds/TwoDefaultMethodsWithoutTopTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/interfacediamonds/TwoDefaultMethodsWithoutTopTest.java
@@ -5,17 +5,19 @@
 
 import static org.hamcrest.CoreMatchers.containsString;
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
 import static org.junit.Assume.assumeTrue;
 
+import com.android.tools.r8.TestAppViewBuilder;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.MethodResolutionResult;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.utils.AndroidApiLevel;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableSet;
-import java.util.Collections;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Set;
@@ -44,21 +46,45 @@
   public void testResolution() throws Exception {
     // The resolution is runtime independent, so just run it on the default CF VM.
     assumeTrue(parameters.useRuntimeAsNoneRuntime());
-    AppInfoWithLiveness appInfo =
-        computeAppViewWithLiveness(
-                buildClasses(CLASSES)
-                    .addClassProgramData(Collections.singletonList(transformB()))
-                    .addLibraryFile(parameters.getDefaultRuntimeLibrary())
-                    .build(),
-                Main.class)
-            .appInfo();
-    DexMethod method = buildNullaryVoidMethod(B.class, "f", appInfo.dexItemFactory());
-    MethodResolutionResult resolutionResult = appInfo.resolveMethodOnClass(method);
-    Set<String> holders = new HashSet<>();
-    resolutionResult
-        .asFailedResolution()
-        .forEachFailureDependency(m -> holders.add(m.getHolderType().toSourceString()));
-    assertEquals(ImmutableSet.of(I.class.getTypeName(), J.class.getTypeName()), holders);
+    for (AndroidApiLevel minApi :
+        ImmutableList.of(AndroidApiLevel.B, apiLevelWithDefaultInterfaceMethodsSupport())) {
+      AppInfoWithLiveness appInfo =
+          TestAppViewBuilder.builder()
+              .addProgramClasses(CLASSES)
+              .addProgramClassFileData(transformB())
+              .addLibraryFiles(parameters.getDefaultRuntimeLibrary())
+              .addKeepMainRule(Main.class)
+              .setMinApi(minApi)
+              .buildWithLiveness()
+              .appInfo();
+      DexMethod method = buildNullaryVoidMethod(B.class, "f", appInfo.dexItemFactory());
+      MethodResolutionResult resolutionResult = appInfo.resolveMethodOnClass(method);
+      if (minApi.isLessThan(apiLevelWithDefaultInterfaceMethodsSupport())) {
+        // When desugaring a forwarding method throwing ICCE is inserted.
+        // Check that the resolved method throws such an exception.
+        assertTrue(
+            resolutionResult
+                .asSingleResolution()
+                .getResolvedMethod()
+                .getCode()
+                .asCfCode()
+                .getInstructions()
+                .stream()
+                .anyMatch(
+                    i ->
+                        i.isTypeInstruction()
+                            && i.asTypeInstruction().getType()
+                                == appInfo.dexItemFactory().icceType));
+      } else {
+        // When not desugaring resolution should fail. Check the failure dependencies are the two
+        // default methods in conflict.
+        Set<String> holders = new HashSet<>();
+        resolutionResult
+            .asFailedResolution()
+            .forEachFailureDependency(m -> holders.add(m.getHolderType().toSourceString()));
+        assertEquals(ImmutableSet.of(I.class.getTypeName(), J.class.getTypeName()), holders);
+      }
+    }
   }
 
   @Test
diff --git a/src/test/java/com/android/tools/r8/resolution/interfacetargets/DefaultMethodAsOverrideWithLambdaTest.java b/src/test/java/com/android/tools/r8/resolution/interfacetargets/DefaultMethodAsOverrideWithLambdaTest.java
index f8a46ca..b3c88c7 100644
--- a/src/test/java/com/android/tools/r8/resolution/interfacetargets/DefaultMethodAsOverrideWithLambdaTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/interfacetargets/DefaultMethodAsOverrideWithLambdaTest.java
@@ -13,6 +13,7 @@
 import com.android.tools.r8.NeverClassInline;
 import com.android.tools.r8.NeverInline;
 import com.android.tools.r8.NoVerticalClassMerging;
+import com.android.tools.r8.TestAppViewBuilder;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
@@ -53,11 +54,13 @@
   public void testResolution() throws Exception {
     assumeTrue(parameters.useRuntimeAsNoneRuntime());
     AppView<AppInfoWithLiveness> appView =
-        computeAppViewWithLiveness(
-            buildClassesWithTestingAnnotations(I.class, J.class, A.class, Main.class)
-                .addLibraryFile(parameters.getDefaultRuntimeLibrary())
-                .build(),
-            Main.class);
+        TestAppViewBuilder.builder()
+            .addProgramClasses(I.class, J.class, A.class, Main.class)
+            .addTestingAnnotations()
+            .addLibraryFiles(parameters.getDefaultRuntimeLibrary())
+            .addKeepMainRule(Main.class)
+            .setMinApi(apiLevelWithDefaultInterfaceMethodsSupport())
+            .buildWithLiveness();
     AppInfoWithLiveness appInfo = appView.appInfo();
     DexMethod method = buildNullaryVoidMethod(I.class, "bar", appInfo.dexItemFactory());
     MethodResolutionResult resolutionResult =
diff --git a/src/test/java/com/android/tools/r8/resolution/interfacetargets/DefaultMethodLambdaTest.java b/src/test/java/com/android/tools/r8/resolution/interfacetargets/DefaultMethodLambdaTest.java
index 4449400..1b3d3d9 100644
--- a/src/test/java/com/android/tools/r8/resolution/interfacetargets/DefaultMethodLambdaTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/interfacetargets/DefaultMethodLambdaTest.java
@@ -13,6 +13,7 @@
 import com.android.tools.r8.NeverClassInline;
 import com.android.tools.r8.NeverInline;
 import com.android.tools.r8.NoVerticalClassMerging;
+import com.android.tools.r8.TestAppViewBuilder;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
@@ -52,11 +53,13 @@
   public void testResolution() throws Exception {
     assumeTrue(parameters.useRuntimeAsNoneRuntime());
     AppView<AppInfoWithLiveness> appView =
-        computeAppViewWithLiveness(
-            buildClassesWithTestingAnnotations(I.class, A.class, Main.class)
-                .addLibraryFile(parameters.getDefaultRuntimeLibrary())
-                .build(),
-            Main.class);
+        TestAppViewBuilder.builder()
+            .addProgramClasses(I.class, A.class, Main.class)
+            .addTestingAnnotations()
+            .addLibraryFiles(parameters.getDefaultRuntimeLibrary())
+            .addKeepMainRule(Main.class)
+            .setMinApi(apiLevelWithDefaultInterfaceMethodsSupport())
+            .buildWithLiveness();
     AppInfoWithLiveness appInfo = appView.appInfo();
     DexMethod method = buildNullaryVoidMethod(I.class, "bar", appInfo.dexItemFactory());
     MethodResolutionResult resolutionResult =
diff --git a/src/test/java/com/android/tools/r8/resolution/interfacetargets/DefaultWithoutTopTest.java b/src/test/java/com/android/tools/r8/resolution/interfacetargets/DefaultWithoutTopTest.java
index 6701adc..467ca50 100644
--- a/src/test/java/com/android/tools/r8/resolution/interfacetargets/DefaultWithoutTopTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/interfacetargets/DefaultWithoutTopTest.java
@@ -13,6 +13,7 @@
 import com.android.tools.r8.NeverClassInline;
 import com.android.tools.r8.NeverInline;
 import com.android.tools.r8.NoVerticalClassMerging;
+import com.android.tools.r8.TestAppViewBuilder;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
@@ -52,12 +53,14 @@
   public void testDynamicLookupTargets() throws Exception {
     assumeTrue(parameters.useRuntimeAsNoneRuntime());
     AppView<AppInfoWithLiveness> appView =
-        computeAppViewWithLiveness(
-            buildClassesWithTestingAnnotations(I.class, J.class, Main.class)
-                .addClassProgramData(setAImplementsIAndJ())
-                .addLibraryFile(parameters.getDefaultRuntimeLibrary())
-                .build(),
-            Main.class);
+        TestAppViewBuilder.builder()
+            .addProgramClasses(I.class, J.class, Main.class)
+            .addTestingAnnotations()
+            .addProgramClassFileData(setAImplementsIAndJ())
+            .addLibraryFiles(parameters.getDefaultRuntimeLibrary())
+            .addKeepMainRule(Main.class)
+            .setMinApi(apiLevelWithDefaultInterfaceMethodsSupport())
+            .buildWithLiveness();
     AppInfoWithLiveness appInfo = appView.appInfo();
     DexMethod method = buildNullaryVoidMethod(I.class, "foo", appInfo.dexItemFactory());
     MethodResolutionResult resolutionResult = appInfo.resolveMethodOnInterface(method);
@@ -99,12 +102,14 @@
   public void testDynamicLookupTargetsWithIndirectDefault() throws Exception {
     assumeTrue(parameters.useRuntimeAsNoneRuntime());
     AppView<AppInfoWithLiveness> appView =
-        computeAppViewWithLiveness(
-            buildClassesWithTestingAnnotations(I.class, J.class, K.class, Main.class)
-                .addClassProgramData(setAimplementsIandK())
-                .addLibraryFile(parameters.getDefaultRuntimeLibrary())
-                .build(),
-            Main.class);
+        TestAppViewBuilder.builder()
+            .addTestingAnnotations()
+            .addProgramClasses(I.class, J.class, K.class, Main.class)
+            .addProgramClassFileData(setAimplementsIandK())
+            .addLibraryFiles(parameters.getDefaultRuntimeLibrary())
+            .addKeepMainRule(Main.class)
+            .setMinApi(apiLevelWithDefaultInterfaceMethodsSupport())
+            .buildWithLiveness();
     AppInfoWithLiveness appInfo = appView.appInfo();
     DexMethod method = buildNullaryVoidMethod(I.class, "foo", appInfo.dexItemFactory());
     MethodResolutionResult resolutionResult = appInfo.resolveMethodOnInterface(method);
diff --git a/src/test/java/com/android/tools/r8/resolution/interfacetargets/DuplicateImportsTest.java b/src/test/java/com/android/tools/r8/resolution/interfacetargets/DuplicateImportsTest.java
index ae340f7..af2dde1 100644
--- a/src/test/java/com/android/tools/r8/resolution/interfacetargets/DuplicateImportsTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/interfacetargets/DuplicateImportsTest.java
@@ -13,6 +13,7 @@
 import com.android.tools.r8.NeverClassInline;
 import com.android.tools.r8.NeverInline;
 import com.android.tools.r8.NoVerticalClassMerging;
+import com.android.tools.r8.TestAppViewBuilder;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
@@ -53,11 +54,13 @@
   public void testResolution() throws Exception {
     assumeTrue(parameters.useRuntimeAsNoneRuntime());
     AppView<AppInfoWithLiveness> appView =
-        computeAppViewWithLiveness(
-            buildClassesWithTestingAnnotations(I.class, J.class, A.class, Main.class)
-                .addLibraryFile(parameters.getDefaultRuntimeLibrary())
-                .build(),
-            Main.class);
+        TestAppViewBuilder.builder()
+            .addTestingAnnotations()
+            .addProgramClasses(I.class, J.class, A.class, Main.class)
+            .addLibraryFiles(parameters.getDefaultRuntimeLibrary())
+            .addKeepMainRule(Main.class)
+            .setMinApi(apiLevelWithDefaultInterfaceMethodsSupport())
+            .buildWithLiveness();
     AppInfoWithLiveness appInfo = appView.appInfo();
     DexMethod method = buildNullaryVoidMethod(I.class, "foo", appInfo.dexItemFactory());
     MethodResolutionResult resolutionResult = appInfo.resolveMethodOnInterface(method);
diff --git a/src/test/java/com/android/tools/r8/resolution/interfacetargets/InvokeInterfaceWithStaticTargetTest.java b/src/test/java/com/android/tools/r8/resolution/interfacetargets/InvokeInterfaceWithStaticTargetTest.java
index 0ec1ce8..4dcead3 100644
--- a/src/test/java/com/android/tools/r8/resolution/interfacetargets/InvokeInterfaceWithStaticTargetTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/interfacetargets/InvokeInterfaceWithStaticTargetTest.java
@@ -4,7 +4,6 @@
 
 package com.android.tools.r8.resolution.interfacetargets;
 
-import static org.hamcrest.CoreMatchers.containsString;
 import static org.junit.Assume.assumeTrue;
 import static org.objectweb.asm.Opcodes.INVOKEINTERFACE;
 
@@ -17,7 +16,6 @@
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
-import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.DescriptorUtils;
 import java.io.IOException;
 import java.util.concurrent.ExecutionException;
@@ -70,7 +68,7 @@
         .addProgramClasses(A.class, I.class)
         .addProgramClassFileData(transformMain())
         .run(parameters.getRuntime(), Main.class)
-        .assertFailureWithErrorThatMatches(containsString(getExpected()));
+        .assertFailureWithErrorThatThrows(IncompatibleClassChangeError.class);
   }
 
   @Test
@@ -81,14 +79,7 @@
         .addKeepMainRule(Main.class)
         .setMinApi(parameters.getApiLevel())
         .run(parameters.getRuntime(), Main.class)
-        .assertFailureWithErrorThatMatches(containsString(getExpected()));
-  }
-
-  private String getExpected() {
-    return parameters.isCfRuntime()
-            || parameters.getApiLevel().isGreaterThanOrEqualTo(AndroidApiLevel.N)
-        ? "IncompatibleClassChangeError"
-        : "NoSuchMethodError";
+        .assertFailureWithErrorThatThrows(IncompatibleClassChangeError.class);
   }
 
   private byte[] transformMain() throws IOException {
diff --git a/src/test/java/com/android/tools/r8/resolution/interfacetargets/LambdaMultipleInterfacesTest.java b/src/test/java/com/android/tools/r8/resolution/interfacetargets/LambdaMultipleInterfacesTest.java
index 9af861c..0819b97 100644
--- a/src/test/java/com/android/tools/r8/resolution/interfacetargets/LambdaMultipleInterfacesTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/interfacetargets/LambdaMultipleInterfacesTest.java
@@ -13,6 +13,7 @@
 import com.android.tools.r8.NeverClassInline;
 import com.android.tools.r8.NeverInline;
 import com.android.tools.r8.NoVerticalClassMerging;
+import com.android.tools.r8.TestAppViewBuilder;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
@@ -52,11 +53,13 @@
   public void testResolution() throws Exception {
     assumeTrue(parameters.useRuntimeAsNoneRuntime());
     AppView<AppInfoWithLiveness> appView =
-        computeAppViewWithLiveness(
-            buildClassesWithTestingAnnotations(I.class, J.class, A.class, Main.class)
-                .addLibraryFile(parameters.getDefaultRuntimeLibrary())
-                .build(),
-            Main.class);
+        TestAppViewBuilder.builder()
+            .addTestingAnnotations()
+            .addProgramClasses(I.class, J.class, A.class, Main.class)
+            .addLibraryFiles(parameters.getDefaultRuntimeLibrary())
+            .addKeepMainRule(Main.class)
+            .setMinApi(apiLevelWithDefaultInterfaceMethodsSupport())
+            .buildWithLiveness();
     AppInfoWithLiveness appInfo = appView.appInfo();
     DexMethod method = buildNullaryVoidMethod(J.class, "bar", appInfo.dexItemFactory());
     MethodResolutionResult resolutionResult =
diff --git a/src/test/java/com/android/tools/r8/resolution/interfacetargets/SubInterfaceOverridesTest.java b/src/test/java/com/android/tools/r8/resolution/interfacetargets/SubInterfaceOverridesTest.java
index 512e775..4d85bfb 100644
--- a/src/test/java/com/android/tools/r8/resolution/interfacetargets/SubInterfaceOverridesTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/interfacetargets/SubInterfaceOverridesTest.java
@@ -13,6 +13,7 @@
 import com.android.tools.r8.CompilationFailedException;
 import com.android.tools.r8.NeverClassInline;
 import com.android.tools.r8.NoVerticalClassMerging;
+import com.android.tools.r8.TestAppViewBuilder;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
@@ -52,12 +53,13 @@
   public void testResolution() throws Exception {
     assumeTrue(parameters.useRuntimeAsNoneRuntime());
     AppView<AppInfoWithLiveness> appView =
-        computeAppViewWithLiveness(
-            buildClassesWithTestingAnnotations(
-                    I.class, J.class, A.class, B.class, C.class, Main.class)
-                .addLibraryFile(parameters.getDefaultRuntimeLibrary())
-                .build(),
-            Main.class);
+        TestAppViewBuilder.builder()
+            .addTestingAnnotations()
+            .addProgramClasses(I.class, J.class, A.class, B.class, C.class, Main.class)
+            .addLibraryFiles(parameters.getDefaultRuntimeLibrary())
+            .addKeepMainRule(Main.class)
+            .setMinApi(apiLevelWithDefaultInterfaceMethodsSupport())
+            .buildWithLiveness();
     AppInfoWithLiveness appInfo = appView.appInfo();
     DexMethod method = buildNullaryVoidMethod(I.class, "foo", appInfo.dexItemFactory());
     MethodResolutionResult resolutionResult =
diff --git a/src/test/java/com/android/tools/r8/resolution/packageprivate/PackagePrivateClasspathWidenTest.java b/src/test/java/com/android/tools/r8/resolution/packageprivate/PackagePrivateClasspathWidenTest.java
index fe3a74c..e59ea7a 100644
--- a/src/test/java/com/android/tools/r8/resolution/packageprivate/PackagePrivateClasspathWidenTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/packageprivate/PackagePrivateClasspathWidenTest.java
@@ -10,6 +10,7 @@
 import static org.junit.Assume.assumeTrue;
 
 import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.TestAppViewBuilder;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
@@ -64,12 +65,13 @@
   public void testResolution() throws Exception {
     assumeTrue(parameters.useRuntimeAsNoneRuntime());
     AppView<AppInfoWithLiveness> appView =
-        computeAppViewWithLiveness(
-            buildClasses(C.class, Main.class)
-                .addClasspathFiles(classPathJar)
-                .addLibraryFile(parameters.getDefaultRuntimeLibrary())
-                .build(),
-            Main.class);
+        TestAppViewBuilder.builder()
+            .addProgramClasses(C.class, Main.class)
+            .addClasspathFiles(classPathJar)
+            .addLibraryFiles(parameters.getDefaultRuntimeLibrary())
+            .addKeepMainRule(Main.class)
+            .setMinApi(apiLevelWithDefaultInterfaceMethodsSupport())
+            .buildWithLiveness();
     AppInfoWithLiveness appInfo = appView.appInfo();
     DexMethod method = buildNullaryVoidMethod(Abstract.class, "foo", appInfo.dexItemFactory());
     MethodResolutionResult resolutionResult = appInfo.resolveMethodOnClass(method);
diff --git a/src/test/java/com/android/tools/r8/resolution/virtualtargets/DefaultInterfaceMethodInSubInterfaceSubTypeTest.java b/src/test/java/com/android/tools/r8/resolution/virtualtargets/DefaultInterfaceMethodInSubInterfaceSubTypeTest.java
index f6368cb..d91d19f 100644
--- a/src/test/java/com/android/tools/r8/resolution/virtualtargets/DefaultInterfaceMethodInSubInterfaceSubTypeTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/virtualtargets/DefaultInterfaceMethodInSubInterfaceSubTypeTest.java
@@ -13,6 +13,7 @@
 import com.android.tools.r8.NeverClassInline;
 import com.android.tools.r8.NeverInline;
 import com.android.tools.r8.NoVerticalClassMerging;
+import com.android.tools.r8.TestAppViewBuilder;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
@@ -52,11 +53,13 @@
   public void testResolution() throws Exception {
     assumeTrue(parameters.useRuntimeAsNoneRuntime());
     AppView<AppInfoWithLiveness> appView =
-        computeAppViewWithLiveness(
-            buildClassesWithTestingAnnotations(I.class, J.class, A.class, B.class, Main.class)
-                .addLibraryFile(parameters.getDefaultRuntimeLibrary())
-                .build(),
-            Main.class);
+        TestAppViewBuilder.builder()
+            .addTestingAnnotations()
+            .addProgramClasses(I.class, J.class, A.class, B.class, Main.class)
+            .addLibraryFiles(parameters.getDefaultRuntimeLibrary())
+            .addKeepMainRule(Main.class)
+            .setMinApi(apiLevelWithDefaultInterfaceMethodsSupport())
+            .buildWithLiveness();
     AppInfoWithLiveness appInfo = appView.appInfo();
     DexMethod method = buildNullaryVoidMethod(A.class, "foo", appInfo.dexItemFactory());
     MethodResolutionResult resolutionResult = appInfo.resolveMethodOnClass(method);
diff --git a/src/test/java/com/android/tools/r8/resolution/virtualtargets/DefaultInterfaceMethodInSubInterfaceTest.java b/src/test/java/com/android/tools/r8/resolution/virtualtargets/DefaultInterfaceMethodInSubInterfaceTest.java
index 4673e7c..69c8c7a 100644
--- a/src/test/java/com/android/tools/r8/resolution/virtualtargets/DefaultInterfaceMethodInSubInterfaceTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/virtualtargets/DefaultInterfaceMethodInSubInterfaceTest.java
@@ -13,6 +13,7 @@
 import com.android.tools.r8.NeverClassInline;
 import com.android.tools.r8.NeverInline;
 import com.android.tools.r8.NoVerticalClassMerging;
+import com.android.tools.r8.TestAppViewBuilder;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
@@ -52,11 +53,13 @@
   public void testResolution() throws Exception {
     assumeTrue(parameters.useRuntimeAsNoneRuntime());
     AppView<AppInfoWithLiveness> appView =
-        computeAppViewWithLiveness(
-            buildClassesWithTestingAnnotations(I.class, J.class, A.class, Main.class)
-                .addLibraryFile(parameters.getDefaultRuntimeLibrary())
-                .build(),
-            Main.class);
+        TestAppViewBuilder.builder()
+            .addTestingAnnotations()
+            .addProgramClasses(I.class, J.class, A.class, Main.class)
+            .addLibraryFiles(parameters.getDefaultRuntimeLibrary())
+            .addKeepMainRule(Main.class)
+            .setMinApi(apiLevelWithDefaultInterfaceMethodsSupport())
+            .buildWithLiveness();
     AppInfoWithLiveness appInfo = appView.appInfo();
     DexMethod method = buildNullaryVoidMethod(A.class, "foo", appInfo.dexItemFactory());
     MethodResolutionResult resolutionResult = appInfo.resolveMethodOnClass(method);
diff --git a/src/test/java/com/android/tools/r8/resolution/virtualtargets/DefaultWithoutTopTest.java b/src/test/java/com/android/tools/r8/resolution/virtualtargets/DefaultWithoutTopTest.java
index d6d5dc8..65d8316 100644
--- a/src/test/java/com/android/tools/r8/resolution/virtualtargets/DefaultWithoutTopTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/virtualtargets/DefaultWithoutTopTest.java
@@ -13,6 +13,7 @@
 import com.android.tools.r8.NeverClassInline;
 import com.android.tools.r8.NeverInline;
 import com.android.tools.r8.NoVerticalClassMerging;
+import com.android.tools.r8.TestAppViewBuilder;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
@@ -52,12 +53,14 @@
   public void testDynamicLookupTargets() throws Exception {
     assumeTrue(parameters.useRuntimeAsNoneRuntime());
     AppView<AppInfoWithLiveness> appView =
-        computeAppViewWithLiveness(
-            buildClassesWithTestingAnnotations(I.class, J.class, Main.class)
-                .addClassProgramData(setAImplementsIAndJ())
-                .addLibraryFile(parameters.getDefaultRuntimeLibrary())
-                .build(),
-            Main.class);
+        TestAppViewBuilder.builder()
+            .addTestingAnnotations()
+            .addProgramClasses(I.class, J.class, Main.class)
+            .addProgramClassFileData(setAImplementsIAndJ())
+            .addLibraryFiles(parameters.getDefaultRuntimeLibrary())
+            .addKeepMainRule(Main.class)
+            .setMinApi(apiLevelWithDefaultInterfaceMethodsSupport())
+            .buildWithLiveness();
     AppInfoWithLiveness appInfo = appView.appInfo();
     DexMethod method = buildNullaryVoidMethod(A.class, "foo", appInfo.dexItemFactory());
     MethodResolutionResult resolutionResult = appInfo.resolveMethodOnClass(method);
diff --git a/src/test/java/com/android/tools/r8/resolution/virtualtargets/InvokeVirtualToInterfaceDefinitionTest.java b/src/test/java/com/android/tools/r8/resolution/virtualtargets/InvokeVirtualToInterfaceDefinitionTest.java
index e6e6f6c..14f9d2a 100644
--- a/src/test/java/com/android/tools/r8/resolution/virtualtargets/InvokeVirtualToInterfaceDefinitionTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/virtualtargets/InvokeVirtualToInterfaceDefinitionTest.java
@@ -13,6 +13,7 @@
 import com.android.tools.r8.NeverClassInline;
 import com.android.tools.r8.NeverInline;
 import com.android.tools.r8.NoVerticalClassMerging;
+import com.android.tools.r8.TestAppViewBuilder;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
@@ -52,11 +53,13 @@
   public void testResolution() throws Exception {
     assumeTrue(parameters.useRuntimeAsNoneRuntime());
     AppView<AppInfoWithLiveness> appView =
-        computeAppViewWithLiveness(
-            buildClassesWithTestingAnnotations(A.class, I.class, Main.class)
-                .addLibraryFile(parameters.getDefaultRuntimeLibrary())
-                .build(),
-            Main.class);
+        TestAppViewBuilder.builder()
+            .addTestingAnnotations()
+            .addProgramClasses(A.class, I.class, Main.class)
+            .addLibraryFiles(parameters.getDefaultRuntimeLibrary())
+            .addKeepMainRule(Main.class)
+            .setMinApi(apiLevelWithDefaultInterfaceMethodsSupport())
+            .buildWithLiveness();
     AppInfoWithLiveness appInfo = appView.appInfo();
     DexMethod method = buildNullaryVoidMethod(A.class, "foo", appInfo.dexItemFactory());
     MethodResolutionResult resolutionResult = appInfo.resolveMethodOnClass(method);
diff --git a/src/test/java/com/android/tools/r8/resolution/virtualtargets/TargetInDefaultMethodTest.java b/src/test/java/com/android/tools/r8/resolution/virtualtargets/TargetInDefaultMethodTest.java
index 536f420..162d3c4 100644
--- a/src/test/java/com/android/tools/r8/resolution/virtualtargets/TargetInDefaultMethodTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/virtualtargets/TargetInDefaultMethodTest.java
@@ -14,6 +14,7 @@
 import com.android.tools.r8.NeverClassInline;
 import com.android.tools.r8.NeverInline;
 import com.android.tools.r8.NoVerticalClassMerging;
+import com.android.tools.r8.TestAppViewBuilder;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
@@ -53,11 +54,13 @@
   public void testResolution() throws Exception {
     assumeTrue(parameters.useRuntimeAsNoneRuntime());
     AppView<AppInfoWithLiveness> appView =
-        computeAppViewWithLiveness(
-            buildClassesWithTestingAnnotations(I.class, A.class, B.class, C.class, Main.class)
-                .addLibraryFile(parameters.getDefaultRuntimeLibrary())
-                .build(),
-            Main.class);
+        TestAppViewBuilder.builder()
+            .addTestingAnnotations()
+            .addProgramClasses(I.class, A.class, B.class, C.class, Main.class)
+            .addLibraryFiles(parameters.getDefaultRuntimeLibrary())
+            .addKeepMainRule(Main.class)
+            .setMinApi(apiLevelWithDefaultInterfaceMethodsSupport())
+            .buildWithLiveness();
     AppInfoWithLiveness appInfo = appView.appInfo();
     DexMethod method = buildNullaryVoidMethod(I.class, "foo", appInfo.dexItemFactory());
     MethodResolutionResult resolutionResult = appInfo.resolveMethodOnInterface(method);
diff --git a/src/test/java/com/android/tools/r8/retrace/api/RetraceApiBinaryCompatibilityTest.java b/src/test/java/com/android/tools/r8/retrace/api/RetraceApiBinaryCompatibilityTest.java
new file mode 100644
index 0000000..df7c599
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/retrace/api/RetraceApiBinaryCompatibilityTest.java
@@ -0,0 +1,95 @@
+// Copyright (c) 2021, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.retrace.api;
+
+import static java.nio.file.StandardCopyOption.REPLACE_EXISTING;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.TestRuntime.CfRuntime;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.utils.ZipUtils;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.List;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class RetraceApiBinaryCompatibilityTest extends TestBase {
+
+  private final TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withNoneRuntime().build();
+  }
+
+  public RetraceApiBinaryCompatibilityTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  private static final Path BINARY_COMPATIBILITY_JAR =
+      Paths.get(ToolHelper.THIRD_PARTY_DIR, "retrace", "binary_compatibility", "tests.jar");
+
+  private Path generateJar() throws Exception {
+    return RetraceApiTestHelper.generateJarForRetraceBinaryTests(
+        temp, RetraceApiTestHelper.getBinaryCompatibilityTests());
+  }
+
+  @Test
+  public void testBinaryJarIsUpToDate() throws Exception {
+    Path binaryContents = temp.newFolder().toPath();
+    Path generatedContents = temp.newFolder().toPath();
+    ZipUtils.unzip(BINARY_COMPATIBILITY_JAR, binaryContents);
+    ZipUtils.unzip(generateJar(), generatedContents);
+    try (Stream<Path> existingPaths = Files.walk(binaryContents);
+        Stream<Path> generatedPaths = Files.walk(generatedContents)) {
+      List<Path> existing = existingPaths.filter(this::isClassFile).collect(Collectors.toList());
+      List<Path> generated = generatedPaths.filter(this::isClassFile).collect(Collectors.toList());
+      assertEquals(existing.size(), generated.size());
+      assertNotEquals(0, existing.size());
+      for (Path classFile : generated) {
+        Path otherClassFile = binaryContents.resolve(generatedContents.relativize(classFile));
+        assertTrue(Files.exists(otherClassFile));
+        assertTrue(TestBase.filesAreEqual(classFile, otherClassFile));
+      }
+    }
+  }
+
+  private boolean isClassFile(Path path) {
+    return path.toString().endsWith(".class");
+  }
+
+  @Test
+  public void runCheckedInBinaryJar() throws Exception {
+    for (CfRuntime cfRuntime : CfRuntime.getCheckedInCfRuntimes()) {
+      RetraceApiTestHelper.runJunitOnTests(
+          cfRuntime,
+          ToolHelper.R8_RETRACE_JAR,
+          BINARY_COMPATIBILITY_JAR,
+          RetraceApiTestHelper.getBinaryCompatibilityTests());
+    }
+  }
+
+  public static void main(String[] args) throws Exception {
+    TemporaryFolder temp = new TemporaryFolder();
+    temp.create();
+    Path generatedJar =
+        RetraceApiTestHelper.generateJarForRetraceBinaryTests(
+            temp, RetraceApiTestHelper.getBinaryCompatibilityTests());
+    Files.move(generatedJar, BINARY_COMPATIBILITY_JAR, REPLACE_EXISTING);
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/retrace/api/RetraceApiBinaryTest.java b/src/test/java/com/android/tools/r8/retrace/api/RetraceApiBinaryTest.java
new file mode 100644
index 0000000..eac71c5
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/retrace/api/RetraceApiBinaryTest.java
@@ -0,0 +1,7 @@
+// Copyright (c) 2021, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.retrace.api;
+
+public interface RetraceApiBinaryTest {}
diff --git a/src/test/java/com/android/tools/r8/retrace/api/RetraceApiEmptyTest.java b/src/test/java/com/android/tools/r8/retrace/api/RetraceApiEmptyTest.java
new file mode 100644
index 0000000..45df95c
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/retrace/api/RetraceApiEmptyTest.java
@@ -0,0 +1,51 @@
+// Copyright (c) 2021, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.retrace.api;
+
+import static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.DiagnosticsHandler;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.references.Reference;
+import com.android.tools.r8.retrace.ProguardMapProducer;
+import com.android.tools.r8.retrace.RetracedClassReference;
+import com.android.tools.r8.retrace.Retracer;
+import java.util.ArrayList;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class RetraceApiEmptyTest extends RetraceApiTestBase {
+
+  public RetraceApiEmptyTest(TestParameters parameters) {
+    super(parameters);
+  }
+
+  @Override
+  protected Class<? extends RetraceApiBinaryTest> binaryTestClass() {
+    return RetraceTest.class;
+  }
+
+  public static class RetraceTest implements RetraceApiBinaryTest {
+
+    @Test
+    public void testNone() throws Exception {
+      String expected = "hello.World";
+      List<RetracedClassReference> retracedClasses = new ArrayList<>();
+      Retracer.createDefault(ProguardMapProducer.fromString(""), new DiagnosticsHandler() {})
+          .retraceClass(Reference.classFromTypeName(expected))
+          .stream()
+          .forEach(
+              result -> {
+                retracedClasses.add(result.getRetracedClass());
+              });
+      assertEquals(1, retracedClasses.size());
+      RetracedClassReference retracedClass = retracedClasses.get(0);
+      assertEquals(retracedClass.getTypeName(), expected);
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/retrace/api/RetraceApiTestBase.java b/src/test/java/com/android/tools/r8/retrace/api/RetraceApiTestBase.java
new file mode 100644
index 0000000..a58102a
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/retrace/api/RetraceApiTestBase.java
@@ -0,0 +1,53 @@
+// Copyright (c) 2021, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.retrace.api;
+
+import static com.android.tools.r8.retrace.api.RetraceApiTestHelper.runJunitOnTests;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersBuilder;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.ToolHelper;
+import java.nio.file.Files;
+import org.junit.Assume;
+import org.junit.Test;
+import org.junit.runner.JUnitCore;
+import org.junit.runner.Result;
+import org.junit.runner.notification.Failure;
+import org.junit.runners.Parameterized.Parameters;
+
+public abstract class RetraceApiTestBase extends TestBase {
+
+  private final TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return TestParametersBuilder.builder().withSystemRuntime().build();
+  }
+
+  public RetraceApiTestBase(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  protected abstract Class<? extends RetraceApiBinaryTest> binaryTestClass();
+
+  @Test
+  public void testDirect() {
+    Result result = JUnitCore.runClasses(binaryTestClass());
+    for (Failure failure : result.getFailures()) {
+      System.out.println(failure.toString());
+    }
+    assertTrue(result.wasSuccessful());
+  }
+
+  @Test
+  public void testRetraceLib() throws Exception {
+    Assume.assumeTrue(Files.exists(ToolHelper.R8_RETRACE_JAR));
+    runJunitOnTests(
+        parameters.getRuntime().asCf(), ToolHelper.R8_RETRACE_JAR, binaryTestClass(), temp);
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/retrace/api/RetraceApiTestHelper.java b/src/test/java/com/android/tools/r8/retrace/api/RetraceApiTestHelper.java
new file mode 100644
index 0000000..9b2f67d
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/retrace/api/RetraceApiTestHelper.java
@@ -0,0 +1,117 @@
+// Copyright (c) 2021, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.retrace.api;
+
+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 com.android.tools.r8.Collectors;
+import com.android.tools.r8.TestRuntime.CfRuntime;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.ToolHelper.ProcessResult;
+import com.android.tools.r8.transformers.ClassFileTransformer;
+import com.android.tools.r8.transformers.ClassFileTransformer.InnerClassPredicate;
+import com.android.tools.r8.utils.DescriptorUtils;
+import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.ZipUtils;
+import com.android.tools.r8.utils.ZipUtils.ZipBuilder;
+import com.google.common.collect.ImmutableList;
+import java.io.File;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.List;
+import org.junit.rules.TemporaryFolder;
+
+public class RetraceApiTestHelper {
+
+  private static final String JUNIT_JAR = "junit-4.13-beta-2.jar";
+  private static final String HAMCREST = "hamcrest-core-1.3.jar";
+
+  public static List<Class<? extends RetraceApiBinaryTest>> CLASSES_FOR_BINARY_COMPATIBILITY =
+      ImmutableList.of(RetraceApiEmptyTest.RetraceTest.class);
+  public static List<Class<? extends RetraceApiBinaryTest>> CLASSES_PENDING_BINARY_COMPATIBILITY =
+      ImmutableList.of();
+
+  public static void runJunitOnTests(
+      CfRuntime runtime,
+      Path r8Jar,
+      Class<? extends RetraceApiBinaryTest> clazz,
+      TemporaryFolder temp)
+      throws Exception {
+    assertTrue(testIsSpecifiedAsBinaryOrPending(clazz));
+    List<Class<? extends RetraceApiBinaryTest>> testClasses = ImmutableList.of(clazz);
+    runJunitOnTests(
+        runtime, r8Jar, generateJarForRetraceBinaryTests(temp, testClasses), testClasses);
+  }
+
+  public static void runJunitOnTests(
+      CfRuntime runtime,
+      Path r8Jar,
+      Path testJar,
+      Collection<Class<? extends RetraceApiBinaryTest>> tests)
+      throws Exception {
+    List<Path> classPaths = ImmutableList.of(getJunitDependency(), getHamcrest(), r8Jar, testJar);
+    ProcessResult processResult =
+        ToolHelper.runJava(
+            runtime,
+            classPaths,
+            "org.junit.runner.JUnitCore",
+            StringUtils.join(" ", tests, Class::getTypeName));
+    assertEquals(0, processResult.exitCode);
+    assertThat(processResult.stdout, containsString("OK (" + tests.size() + " test"));
+  }
+
+  private static Path getJunitDependency() {
+    String junitPath =
+        Arrays.stream(System.getProperty("java.class.path").split(":"))
+            .filter(cp -> cp.endsWith(JUNIT_JAR))
+            .collect(Collectors.toSingle());
+    return Paths.get(junitPath);
+  }
+
+  private static Path getHamcrest() {
+    String junitPath =
+        Arrays.stream(System.getProperty("java.class.path").split(":"))
+            .filter(cp -> cp.endsWith(HAMCREST))
+            .collect(Collectors.toSingle());
+    return Paths.get(junitPath);
+  }
+
+  public static Path generateJarForRetraceBinaryTests(
+      TemporaryFolder temp, Collection<Class<? extends RetraceApiBinaryTest>> classes)
+      throws Exception {
+    Path jar = File.createTempFile("retrace_api_tests", ".jar", temp.getRoot()).toPath();
+    ZipBuilder zipBuilder = ZipBuilder.builder(jar);
+    for (Class<? extends RetraceApiBinaryTest> retraceApiTest : classes) {
+      zipBuilder.addFilesRelative(
+          ToolHelper.getClassPathForTests(),
+          ToolHelper.getClassFilesForInnerClasses(retraceApiTest));
+      zipBuilder.addBytes(
+          ZipUtils.zipEntryNameForClass(retraceApiTest),
+          ClassFileTransformer.create(retraceApiTest)
+              .removeInnerClasses(
+                  InnerClassPredicate.onName(
+                      DescriptorUtils.getBinaryNameFromJavaType(retraceApiTest.getTypeName())))
+              .transform());
+    }
+    zipBuilder.addFilesRelative(
+        ToolHelper.getClassPathForTests(),
+        ToolHelper.getClassFileForTestClass(RetraceApiBinaryTest.class));
+    return zipBuilder.build();
+  }
+
+  public static Collection<Class<? extends RetraceApiBinaryTest>> getBinaryCompatibilityTests() {
+    return CLASSES_FOR_BINARY_COMPATIBILITY;
+  }
+
+  private static boolean testIsSpecifiedAsBinaryOrPending(Class<?> clazz) {
+    return CLASSES_FOR_BINARY_COMPATIBILITY.contains(clazz)
+        || CLASSES_PENDING_BINARY_COMPATIBILITY.contains(clazz);
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/NonVirtualOverrideTest.java b/src/test/java/com/android/tools/r8/shaking/NonVirtualOverrideTest.java
index 4a8306a..801d678 100644
--- a/src/test/java/com/android/tools/r8/shaking/NonVirtualOverrideTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/NonVirtualOverrideTest.java
@@ -4,9 +4,9 @@
 
 package com.android.tools.r8.shaking;
 
+import static com.android.tools.r8.utils.codeinspector.Matchers.isAbsent;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresentAndRenamed;
-import static org.hamcrest.CoreMatchers.not;
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.junit.Assert.assertEquals;
 
@@ -105,9 +105,9 @@
           "In C.m3()",
           "In A.m4()",
           "In A.m1()", // With Java: Caught IllegalAccessError when calling B.m1()
-          "In A.m3()", // With Java: Caught IncompatibleClassChangeError when calling B.m3()
+          "Caught IncompatibleClassChangeError when calling B.m3()",
           "In C.m1()", // With Java: Caught IllegalAccessError when calling B.m1()
-          "In C.m3()", // With Java: Caught IncompatibleClassChangeError when calling B.m3()
+          "Caught IncompatibleClassChangeError when calling B.m3()",
           "In C.m1()",
           "In C.m3()",
           "");
@@ -178,9 +178,11 @@
       ClassSubject classSubject = inspector.clazz(B.class.getName());
       assertThat(classSubject, isPresentAndRenamed());
       assertThat(classSubject.method("void", "m1", ImmutableList.of()), isPresent());
-      assertThat(classSubject.method("void", "m2", ImmutableList.of()), not(isPresent()));
-      assertThat(classSubject.method("void", "m3", ImmutableList.of()), isPresent());
-      assertThat(classSubject.method("void", "m4", ImmutableList.of()), not(isPresent()));
+      assertThat(classSubject.method("void", "m2", ImmutableList.of()), isAbsent());
+      assertThat(
+          classSubject.method("void", "m3", ImmutableList.of()),
+          parameters.isCfRuntime() ? isPresent() : isAbsent());
+      assertThat(classSubject.method("void", "m4", ImmutableList.of()), isAbsent());
     }
   }
 
@@ -263,7 +265,8 @@
       System.out.println("In B.m2()");
     }
 
-    // Made static in the dump below. This method is targeted and can therefore not be removed.
+    // Made static in the dump below. Ends up dead as the targeting call is replaced by throw ICCE.
+    // Except in non-desugaring CF the method will remain instead of inserting a stub.
     @Override
     public void m3() {
       System.out.println("In B.m3()");
diff --git a/src/test/java/com/android/tools/r8/shaking/ReturnTypeTest.java b/src/test/java/com/android/tools/r8/shaking/ReturnTypeTest.java
index 98b7f80..82e9d69 100644
--- a/src/test/java/com/android/tools/r8/shaking/ReturnTypeTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/ReturnTypeTest.java
@@ -85,7 +85,7 @@
               // is
               // always null, and replace the last call with null-throwing instruction.
               // However, we want to test return type and parameter type are kept in this scenario.
-              o.callSiteOptimizationOptions().disableTypePropagationForTesting();
+              o.callSiteOptimizationOptions().disableDynamicTypePropagationForTesting();
               o.enableInlining = false;
             })
         .run(parameters.getRuntime(), MAIN)
diff --git a/src/test/java/com/android/tools/r8/shaking/clinit/ClassInitializationTriggersIndirectInterfaceInitializationTest.java b/src/test/java/com/android/tools/r8/shaking/clinit/ClassInitializationTriggersIndirectInterfaceInitializationTest.java
index b427736..29960f9 100644
--- a/src/test/java/com/android/tools/r8/shaking/clinit/ClassInitializationTriggersIndirectInterfaceInitializationTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/clinit/ClassInitializationTriggersIndirectInterfaceInitializationTest.java
@@ -4,8 +4,8 @@
 
 package com.android.tools.r8.shaking.clinit;
 
+import static com.android.tools.r8.utils.codeinspector.Matchers.isAbsent;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
-import static com.android.tools.r8.utils.codeinspector.Matchers.onlyIf;
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
@@ -51,22 +51,27 @@
         .compile()
         .inspect(
             inspector -> {
-              // Verify that I's class initializer is still present.
               ClassSubject iClassSubject = inspector.clazz(I.class);
-              assertThat(iClassSubject, isPresent());
-              assertThat(
-                  iClassSubject.clinit(),
-                  onlyIf(hasDefaultInterfaceMethodsSupport(parameters), isPresent()));
-
-              // Verify that J is still there.
               ClassSubject jClassSubject = inspector.clazz(J.class);
-              assertThat(jClassSubject, isPresent());
-
-              // Verify that A still implements J.
               ClassSubject aClassSubject = inspector.clazz(A.class);
-              assertThat(aClassSubject, isPresent());
-              assertEquals(1, aClassSubject.getDexProgramClass().getInterfaces().size());
-              assertTrue(aClassSubject.isImplementing(jClassSubject));
+              if (hasDefaultInterfaceMethodsSupport(parameters)) {
+                // Verify that I's class initializer is still present.
+                assertThat(iClassSubject, isPresent());
+                assertThat(iClassSubject.clinit(), isPresent());
+
+                // Verify that J is still there.
+                assertThat(jClassSubject, isPresent());
+
+                // Verify that A still implements J.
+                assertThat(aClassSubject, isPresent());
+                assertEquals(1, aClassSubject.getDexProgramClass().getInterfaces().size());
+                assertTrue(aClassSubject.isImplementing(jClassSubject));
+              } else {
+                // All interfaces are gone and the default methods companion call is inlined.
+                assertThat(iClassSubject, isAbsent());
+                assertThat(jClassSubject, isAbsent());
+                assertThat(aClassSubject, isAbsent());
+              }
             })
         .run(parameters.getRuntime(), Main.class)
         .assertSuccessWithOutputLinesIf(
diff --git a/src/test/java/com/android/tools/r8/shaking/desugar/KeepRuleWarningTest.java b/src/test/java/com/android/tools/r8/shaking/desugar/KeepRuleWarningTest.java
index c2be7a8..02dfda4 100644
--- a/src/test/java/com/android/tools/r8/shaking/desugar/KeepRuleWarningTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/desugar/KeepRuleWarningTest.java
@@ -3,8 +3,6 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.shaking.desugar;
 
-import static org.hamcrest.CoreMatchers.containsString;
-
 import com.android.tools.r8.NoVerticalClassMerging;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestDiagnosticMessages;
@@ -106,14 +104,8 @@
         .enableNoVerticalClassMergingAnnotations()
         .addKeepMainRule(MAIN)
         .addKeepRules("-keep interface **.I { static void foo(); }")
-        .allowDiagnosticWarningMessages()
         .compile()
-        .inspectDiagnosticMessages(
-            m ->
-                m.assertWarningsCount(1)
-                    .assertWarningMessageThatMatches(containsString("static void foo()"))
-                    .assertWarningMessageThatMatches(containsString("is ignored"))
-                    .assertWarningMessageThatMatches(containsString("will be desugared")))
+        .inspectDiagnosticMessages(TestDiagnosticMessages::assertNoMessages)
         .run(parameters.getRuntime(), MAIN)
         .assertSuccessWithOutput(EXPECTED_OUTPUT);
   }
diff --git a/src/test/java/com/android/tools/r8/shaking/ifrule/interfacemethoddesugaring/IfRuleWithInterfaceMethodDesugaringTest.java b/src/test/java/com/android/tools/r8/shaking/ifrule/interfacemethoddesugaring/IfRuleWithInterfaceMethodDesugaringTest.java
index 19b87e4..01d977a 100644
--- a/src/test/java/com/android/tools/r8/shaking/ifrule/interfacemethoddesugaring/IfRuleWithInterfaceMethodDesugaringTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/ifrule/interfacemethoddesugaring/IfRuleWithInterfaceMethodDesugaringTest.java
@@ -4,7 +4,6 @@
 
 package com.android.tools.r8.shaking.ifrule.interfacemethoddesugaring;
 
-import static com.android.tools.r8.ir.desugar.itf.InterfaceDesugaringForTesting.getCompanionClassNameSuffix;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPublic;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isStatic;
@@ -12,6 +11,8 @@
 import static org.hamcrest.CoreMatchers.not;
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeTrue;
 
 import com.android.tools.r8.NeverClassInline;
 import com.android.tools.r8.NeverInline;
@@ -34,9 +35,17 @@
 
   private final TestParameters parameters;
 
+  private static final String STATIC_STR = "In Interface.staticMethod()";
+  private static final String VIRTUAL_STR = "In Interface.virtualMethod()";
+  private static final String EXPECTED_OUTPUT = StringUtils.lines(STATIC_STR, VIRTUAL_STR);
+
   @Parameters(name = "{0}")
   public static TestParametersCollection data() {
-    return getTestParameters().withDexRuntimes().withApiLevel(AndroidApiLevel.M).build();
+    return getTestParameters()
+        .withCfRuntimes()
+        .withDexRuntimes()
+        .withApiLevel(AndroidApiLevel.M)
+        .build();
   }
 
   public IfRuleWithInterfaceMethodDesugaringTest(TestParameters parameters) {
@@ -44,50 +53,64 @@
   }
 
   @Test
+  public void testReference() throws Exception {
+    assumeTrue(parameters.isCfRuntime());
+    testForJvm()
+        .addTestClasspath()
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutput(EXPECTED_OUTPUT);
+  }
+
+  @Test
   public void test() throws Exception {
-    String expectedOutput =
-        StringUtils.lines("In Interface.staticMethod()", "In Interface.virtualMethod()");
-
-    testForJvm().addTestClasspath().run(TestClass.class).assertSuccessWithOutput(expectedOutput);
-
     CodeInspector inspector =
         testForR8(parameters.getBackend())
             .addInnerClasses(IfRuleWithInterfaceMethodDesugaringTest.class)
             .addKeepMainRule(TestClass.class)
             .addKeepRules(
                 "-if class " + Interface.class.getTypeName() + " {",
-                "  !public static void staticMethod();",
+                "  static void staticMethod();",
                 "}",
                 "-keep class " + Unused1.class.getTypeName(),
                 "-if class " + Interface.class.getTypeName() + " {",
-                "  !public !static void virtualMethod();",
+                "  !static void virtualMethod();",
                 "}",
                 "-keep class " + Unused2.class.getTypeName())
-            .allowUnusedProguardConfigurationRules()
+            .allowUnusedProguardConfigurationRules(parameters.isDexRuntime())
             .enableInliningAnnotations()
             .enableNeverClassInliningAnnotations()
             .enableNoVerticalClassMergingAnnotations()
             .setMinApi(parameters.getApiLevel())
-            .compile()
             .run(parameters.getRuntime(), TestClass.class)
-            .assertSuccessWithOutput(expectedOutput)
+            .assertSuccessWithOutput(EXPECTED_OUTPUT)
             .inspector();
 
-    ClassSubject classSubject =
-        inspector.clazz(Interface.class.getTypeName() + getCompanionClassNameSuffix());
-    assertThat(classSubject, isPresent());
-    assertEquals(2, classSubject.allMethods().size());
+    if (parameters.isCfRuntime()) {
+      ClassSubject itfClass = inspector.clazz(Interface.class.getTypeName());
+      assertThat(itfClass, isPresent());
+      assertThat(itfClass.uniqueMethodWithName("staticMethod"), isPresent());
+      assertThat(itfClass.uniqueMethodWithName("virtualMethod"), isPresent());
+      assertThat(inspector.clazz(Unused1.class), isPresent());
+      assertThat(inspector.clazz(Unused2.class), isPresent());
+      return;
+    }
 
+    ClassSubject classSubject = inspector.clazz(Interface.class.getTypeName()).toCompanionClass();
+    assertThat(classSubject, isPresent());
+
+    // NeverInline is only applicable to the static method at this point (could change).
+    assertEquals(1, classSubject.allMethods().size());
     MethodSubject staticMethodSubject = classSubject.uniqueMethodWithName("staticMethod");
     assertThat(staticMethodSubject, allOf(isPresent(), isPublic(), isStatic()));
+    assertTrue(staticMethodSubject.streamInstructions().anyMatch(i -> i.isConstString(STATIC_STR)));
 
-    // TODO(b/120764902): MethodSubject.getOriginalName() not working in presence of desugaring.
-    MethodSubject virtualMethodSubject =
-        classSubject.allMethods().stream()
-            .filter(subject -> subject != staticMethodSubject)
-            .findFirst()
-            .get();
-    assertThat(virtualMethodSubject, allOf(isPresent(), isPublic(), isStatic()));
+    // The virtual method is inlined as @NeverInline does not apply at this point (could change).
+    assertTrue(
+        inspector
+            .clazz(TestClass.class)
+            .mainMethod()
+            .streamInstructions()
+            .anyMatch(i -> i.isConstString(VIRTUAL_STR)));
 
     // TODO(b/122875545): The Unused class should be present due to the -if rule.
     assertThat(inspector.clazz(Unused1.class), not(isPresent()));
diff --git a/src/test/java/com/android/tools/r8/shaking/staticinterfacemethods/defaultmethods/StaticInterfaceMethodsTest.java b/src/test/java/com/android/tools/r8/shaking/staticinterfacemethods/defaultmethods/StaticInterfaceMethodsTest.java
index ca0aaa5..e9af866 100644
--- a/src/test/java/com/android/tools/r8/shaking/staticinterfacemethods/defaultmethods/StaticInterfaceMethodsTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/staticinterfacemethods/defaultmethods/StaticInterfaceMethodsTest.java
@@ -107,7 +107,7 @@
     assertFalse(clazz.method("int", "method", ImmutableList.of()).isPresent());
   }
 
-  private void staticMethodKeptB159987443(CodeInspector inspector) {
+  private void staticMethodKept(CodeInspector inspector) {
     ClassSubject clazz = inspector.clazz(InterfaceWithStaticMethods.class);
     assertThat(clazz, isPresent());
     MethodSubject method = clazz.method("int", "method", ImmutableList.of());
@@ -117,13 +117,12 @@
       assertThat(companionClass, not(isPresent()));
     } else {
       assertThat(method, not(isPresent()));
-      // TODO(159987443): The companion class should be present.
-      assertThat(companionClass, not(isPresent()));
-      // Also check that method exists on companion class.
+      assertThat(companionClass, isPresent());
+      assertThat(companionClass.uniqueMethodWithName("method"), isPresent());
     }
   }
 
-  private void staticMethodKept(CodeInspector inspector) {
+  private void staticMethodKeptB160142903(CodeInspector inspector) {
     ClassSubject clazz = inspector.clazz(InterfaceWithStaticMethods.class);
     ClassSubject companionClass = clazz.toCompanionClass();
     MethodSubject method = clazz.method("int", "method", ImmutableList.of());
@@ -136,7 +135,7 @@
       // after desugaring, only the companion class is left.
       assertThat(clazz, not(isPresent()));
       assertThat(method, not(isPresent()));
-      // TODO(159987443): The companion class should be present.
+      // TODO(160142903): The companion class should be present.
       assertThat(companionClass, not(isPresent()));
       // Also check that method exists on companion class.
     }
@@ -168,7 +167,7 @@
             "-keep interface " + InterfaceWithStaticMethods.class.getTypeName() + "{",
             "  <methods>;",
             "}"),
-        this::staticMethodKeptB159987443);
+        this::staticMethodKept);
   }
 
   @Test
@@ -196,7 +195,7 @@
             "}");
       }
     }
-    runTest(builder.build(), this::staticMethodKept);
+    runTest(builder.build(), this::staticMethodKeptB160142903);
   }
 
   public static class TestClass {
diff --git a/src/test/java/com/android/tools/r8/synthesis/SyntheticItemsTestUtils.java b/src/test/java/com/android/tools/r8/synthesis/SyntheticItemsTestUtils.java
index 6ffbce9..92774e1 100644
--- a/src/test/java/com/android/tools/r8/synthesis/SyntheticItemsTestUtils.java
+++ b/src/test/java/com/android/tools/r8/synthesis/SyntheticItemsTestUtils.java
@@ -21,9 +21,12 @@
   }
 
   public static ClassReference syntheticCompanionClass(Class<?> clazz) {
+    return syntheticCompanionClass(Reference.classFromClass(clazz));
+  }
+
+  public static ClassReference syntheticCompanionClass(ClassReference clazz) {
     return Reference.classFromDescriptor(
-        InterfaceDesugaringForTesting.getCompanionClassDescriptor(
-            Reference.classFromClass(clazz).getDescriptor()));
+        InterfaceDesugaringForTesting.getCompanionClassDescriptor(clazz.getDescriptor()));
   }
 
   private static ClassReference syntheticClass(Class<?> clazz, SyntheticKind kind, int id) {
@@ -72,7 +75,8 @@
 
   public static boolean isExternalSynthetic(ClassReference reference) {
     for (SyntheticKind kind : SyntheticKind.values()) {
-      if (kind == SyntheticKind.RECORD_TAG) {
+      if (kind == SyntheticKind.RECORD_TAG
+          || kind == SyntheticKind.EMULATED_INTERFACE_MARKER_CLASS) {
         continue;
       }
       if (kind.isFixedSuffixSynthetic) {
@@ -134,4 +138,9 @@
   public static Matcher<String> containsExternalSyntheticReference() {
     return containsString(SyntheticNaming.getPhaseSeparator(Phase.EXTERNAL));
   }
+
+  public static boolean isInternalThrowNSME(MethodReference method) {
+    return SyntheticNaming.isSynthetic(
+        method.getHolderClass(), Phase.INTERNAL, SyntheticKind.THROW_NSME);
+  }
 }
diff --git a/src/test/java/com/android/tools/r8/transformers/ClassFileTransformer.java b/src/test/java/com/android/tools/r8/transformers/ClassFileTransformer.java
index 2bbcf4a..bc72103 100644
--- a/src/test/java/com/android/tools/r8/transformers/ClassFileTransformer.java
+++ b/src/test/java/com/android/tools/r8/transformers/ClassFileTransformer.java
@@ -4,6 +4,7 @@
 package com.android.tools.r8.transformers;
 
 import static com.android.tools.r8.references.Reference.classFromTypeName;
+import static com.android.tools.r8.transformers.ClassFileTransformer.InnerClassPredicate.always;
 import static com.android.tools.r8.utils.DescriptorUtils.getBinaryNameFromDescriptor;
 import static com.android.tools.r8.utils.StringUtils.replaceAll;
 import static org.objectweb.asm.Opcodes.ASM7;
@@ -31,6 +32,7 @@
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
+import java.util.Objects;
 import java.util.function.Consumer;
 import java.util.function.Function;
 import java.util.stream.Collectors;
@@ -556,12 +558,31 @@
     boolean test(String name, String typeDescriptor);
   }
 
+  @FunctionalInterface
+  public interface InnerClassPredicate {
+    boolean test(String name, String outerName, String innerName, int access);
+
+    static InnerClassPredicate always() {
+      return (name, outerName, innerName, access) -> true;
+    }
+
+    static InnerClassPredicate onName(String name) {
+      return (otherName, outerName, innerName, access) -> Objects.equals(name, otherName);
+    }
+  }
+
   public ClassFileTransformer removeInnerClasses() {
+    return removeInnerClasses(always());
+  }
+
+  public ClassFileTransformer removeInnerClasses(InnerClassPredicate predicate) {
     return addClassTransformer(
         new ClassTransformer() {
           @Override
           public void visitInnerClass(String name, String outerName, String innerName, int access) {
-            // Intentionally empty.
+            if (!predicate.test(name, outerName, innerName, access)) {
+              super.visitInnerClass(name, outerName, innerName, access);
+            }
           }
         });
   }
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/ClassOrMemberSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/ClassOrMemberSubject.java
index bca691f..2dc0774 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/ClassOrMemberSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/ClassOrMemberSubject.java
@@ -43,6 +43,7 @@
     return getAccessFlags().isStatic();
   }
 
+  @Override
   public final boolean isSynthetic() {
     return getAccessFlags().isSynthetic();
   }
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/ClassSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/ClassSubject.java
index f552a25..b100332 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/ClassSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/ClassSubject.java
@@ -4,8 +4,6 @@
 
 package com.android.tools.r8.utils.codeinspector;
 
-import static com.android.tools.r8.ir.desugar.itf.InterfaceDesugaringForTesting.getCompanionClassNameSuffix;
-
 import com.android.tools.r8.graph.ClassAccessFlags;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexProgramClass;
@@ -13,11 +11,11 @@
 import com.android.tools.r8.naming.MemberNaming.MethodSignature;
 import com.android.tools.r8.references.ClassReference;
 import com.android.tools.r8.references.MethodReference;
-import com.android.tools.r8.references.Reference;
 import com.android.tools.r8.references.TypeReference;
 import com.android.tools.r8.retrace.RetraceClassElement;
 import com.android.tools.r8.retrace.RetraceClassResult;
 import com.android.tools.r8.smali.SmaliBuilder;
+import com.android.tools.r8.synthesis.SyntheticItemsTestUtils;
 import com.android.tools.r8.utils.ListUtils;
 import com.google.common.base.Predicates;
 import com.google.common.collect.ImmutableList;
@@ -231,12 +229,7 @@
   public abstract KotlinClassMetadata getKotlinClassMetadata();
 
   public ClassSubject toCompanionClass() {
-    String descriptor = reference.getDescriptor();
-    return codeInspector.clazz(
-        Reference.classFromDescriptor(
-            descriptor.substring(0, descriptor.length() - 1)
-                + getCompanionClassNameSuffix()
-                + ";"));
+    return codeInspector.clazz(SyntheticItemsTestUtils.syntheticCompanionClass(reference));
   }
 
   public abstract RetraceClassResult retrace();
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/CodeInspector.java b/src/test/java/com/android/tools/r8/utils/codeinspector/CodeInspector.java
index 77c68b3..76d76e5 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/CodeInspector.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/CodeInspector.java
@@ -3,8 +3,6 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.utils.codeinspector;
 
-import static com.android.tools.r8.ir.desugar.itf.InterfaceDesugaringForTesting.getCompanionClassNameSuffix;
-
 import com.android.tools.r8.DexIndexedConsumer;
 import com.android.tools.r8.StringResource;
 import com.android.tools.r8.TestDiagnosticMessagesImpl;
@@ -40,6 +38,7 @@
 import com.android.tools.r8.retrace.Retracer;
 import com.android.tools.r8.retrace.internal.DirectClassNameMapperProguardMapProducer;
 import com.android.tools.r8.retrace.internal.RetracerImpl;
+import com.android.tools.r8.synthesis.SyntheticItemsTestUtils;
 import com.android.tools.r8.utils.AndroidApp;
 import com.android.tools.r8.utils.BiMapContainer;
 import com.android.tools.r8.utils.DescriptorUtils;
@@ -328,7 +327,7 @@
   }
 
   public ClassSubject companionClassFor(Class<?> clazz) {
-    return clazz(Reference.classFromTypeName(clazz.getTypeName() + getCompanionClassNameSuffix()));
+    return clazz(SyntheticItemsTestUtils.syntheticCompanionClass(clazz));
   }
 
   public void forAllClasses(Consumer<FoundClassSubject> inspection) {
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 cbbbb14..edeed5a 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
@@ -25,6 +25,7 @@
 import com.android.tools.r8.utils.Visibility;
 import com.google.common.collect.ImmutableList;
 import java.util.List;
+import java.util.function.Predicate;
 import org.hamcrest.Description;
 import org.hamcrest.Matcher;
 import org.hamcrest.TypeSafeMatcher;
@@ -84,15 +85,24 @@
   }
 
   public static Matcher<MethodSubject> isBridge() {
+    return isMethodSatisfying("bridge", FoundMethodSubject::isBridge);
+  }
+
+  public static Matcher<MethodSubject> isInstanceInitializer() {
+    return isMethodSatisfying("instance initializer", FoundMethodSubject::isInstanceInitializer);
+  }
+
+  private static Matcher<MethodSubject> isMethodSatisfying(
+      String helperText, Predicate<FoundMethodSubject> predicate) {
     return new TypeSafeMatcher<MethodSubject>() {
       @Override
       protected boolean matchesSafely(MethodSubject subject) {
-        return subject.isPresent() && subject.isBridge();
+        return subject.isPresent() && predicate.test(subject.asFoundMethodSubject());
       }
 
       @Override
       public void describeTo(Description description) {
-        description.appendText(" bridge");
+        description.appendText(" " + helperText);
       }
 
       @Override
diff --git a/third_party/retrace/binary_compatibility.tar.gz.sha1 b/third_party/retrace/binary_compatibility.tar.gz.sha1
new file mode 100644
index 0000000..3825d93
--- /dev/null
+++ b/third_party/retrace/binary_compatibility.tar.gz.sha1
@@ -0,0 +1 @@
+63dd09b086d0d134219a379720c6b68a94afdf48
\ No newline at end of file