Merge commit '9066ac43ffdd344123a2f1e13979e9df3232b42b' into dev-release
diff --git a/.gitignore b/.gitignore
index 45852a0..2e7f798 100644
--- a/.gitignore
+++ b/.gitignore
@@ -225,6 +225,8 @@
 third_party/r8.tar.gz
 third_party/r8-releases/2.0.74
 third_party/r8-releases/2.0.74.tar.gz
+third_party/r8-releases/3.2.54
+third_party/r8-releases/3.2.54.tar.gz
 third_party/r8mappings
 third_party/r8mappings.tar.gz
 third_party/remapper
diff --git a/build.gradle b/build.gradle
index 67074c7..e5467f0 100644
--- a/build.gradle
+++ b/build.gradle
@@ -354,6 +354,7 @@
                 "retrace/binary_compatibility",
                 "r8",
                 "r8-releases/2.0.74",
+                "r8-releases/3.2.54",
                 "r8mappings",
                 "tachiyomi"
         ],
diff --git a/buildSrc/src/main/java/dx/DexMergerTask.java b/buildSrc/src/main/java/dx/DexMergerTask.java
index 735afa7..5dea52b 100644
--- a/buildSrc/src/main/java/dx/DexMergerTask.java
+++ b/buildSrc/src/main/java/dx/DexMergerTask.java
@@ -10,17 +10,13 @@
 import java.util.List;
 import java.util.Set;
 import javax.inject.Inject;
-import org.gradle.api.Action;
 import org.gradle.api.DefaultTask;
-import org.gradle.api.UncheckedIOException;
 import org.gradle.api.file.FileCollection;
 import org.gradle.api.tasks.InputFile;
 import org.gradle.api.tasks.InputFiles;
 import org.gradle.api.tasks.OutputFile;
 import org.gradle.api.tasks.TaskAction;
-import org.gradle.process.ExecSpec;
 import org.gradle.workers.IsolationMode;
-import org.gradle.workers.WorkerConfiguration;
 import org.gradle.workers.WorkerExecutor;
 import utils.Utils;
 
@@ -67,19 +63,30 @@
 
   @TaskAction
   void exec() {
-    workerExecutor.submit(RunDexMerger.class, config -> {
-      config.setIsolationMode(IsolationMode.NONE);
-      config.params(source.getFiles(), destination, dexMergerExecutable);
-    });
+    workerExecutor.submit(
+        RunDexMerger.class,
+        config -> {
+          File executable =
+              dexMergerExecutable.isPresent()
+                  ? dexMergerExecutable.get()
+                  : config
+                      .getForkOptions()
+                      .getWorkingDir()
+                      .toPath()
+                      .resolve(Utils.dexMergerExecutable())
+                      .toFile();
+          config.setIsolationMode(IsolationMode.NONE);
+          config.params(source.getFiles(), destination, executable);
+        });
   }
 
   public static class RunDexMerger implements Runnable {
     private final Set<File> sources;
     private final File destination;
-    private final Optional<File> dexMergerExecutable;
+    private final File dexMergerExecutable;
 
     @Inject
-    public RunDexMerger(Set<File> sources, File destination, Optional<File> dexMergerExecutable) {
+    public RunDexMerger(Set<File> sources, File destination, File dexMergerExecutable) {
       this.sources = sources;
       this.destination = destination;
       this.dexMergerExecutable = dexMergerExecutable;
@@ -89,7 +96,7 @@
     public void run() {
       try {
         List<String> command = new ArrayList<>();
-        command.add(dexMergerExecutable.or(Utils::dexMergerExecutable).getCanonicalPath());
+        command.add(dexMergerExecutable.getCanonicalPath());
         command.add(destination.getCanonicalPath());
         for (File source : sources) {
           command.add(source.getCanonicalPath());
diff --git a/buildSrc/src/main/java/dx/DxTask.java b/buildSrc/src/main/java/dx/DxTask.java
index d8975dc..e05e8fb 100644
--- a/buildSrc/src/main/java/dx/DxTask.java
+++ b/buildSrc/src/main/java/dx/DxTask.java
@@ -75,20 +75,31 @@
 
   @TaskAction
   void exec() {
-    workerExecutor.submit(RunDx.class, config -> {
-      config.setIsolationMode(IsolationMode.NONE);
-      config.params(source.getFiles(), destination, dxExecutable, debug);
-    });
+    workerExecutor.submit(
+        RunDx.class,
+        config -> {
+          File executable =
+              dxExecutable.isPresent()
+                  ? dxExecutable.get()
+                  : config
+                      .getForkOptions()
+                      .getWorkingDir()
+                      .toPath()
+                      .resolve(Utils.dxExecutable())
+                      .toFile();
+          config.setIsolationMode(IsolationMode.NONE);
+          config.params(source.getFiles(), destination, executable, debug);
+        });
   }
 
   public static class RunDx implements Runnable {
     private final Set<File> sources;
     private final File destination;
-    private final Optional<File> dxExecutable;
+    private final File dxExecutable;
     private final boolean debug;
 
     @Inject
-    public RunDx(Set<File> sources, File destination, Optional<File> dxExecutable, boolean debug) {
+    public RunDx(Set<File> sources, File destination, File dxExecutable, boolean debug) {
       this.sources = sources;
       this.destination = destination;
       this.dxExecutable = dxExecutable;
@@ -99,7 +110,7 @@
     public void run() {
       try {
         List<String> command = new ArrayList<>();
-        command.add(dxExecutable.or(Utils::dxExecutable).getCanonicalPath());
+        command.add(dxExecutable.getCanonicalPath());
         command.add("--dex");
         command.add("--output");
         command.add(destination.getCanonicalPath());
diff --git a/buildSrc/src/main/java/utils/Utils.java b/buildSrc/src/main/java/utils/Utils.java
index 7843edd..7f09b81 100644
--- a/buildSrc/src/main/java/utils/Utils.java
+++ b/buildSrc/src/main/java/utils/Utils.java
@@ -3,7 +3,8 @@
 // BSD-style license that can be found in the LICENSE file.
 package utils;
 
-import java.io.File;
+import java.nio.file.Path;
+import java.nio.file.Paths;
 
 public class Utils {
   public static String toolsDir() {
@@ -17,13 +18,17 @@
     }
   }
 
-  public static File dxExecutable() {
-    String dxExecutableName = Utils.toolsDir().equals("windows") ? "dx.bat" : "dx";
-    return new File("tools/" + Utils.toolsDir() + "/dx/bin/" + dxExecutableName);
+  public static boolean isWindows() {
+    return toolsDir().equals("windows");
   }
 
-  public static File dexMergerExecutable() {
-    String executableName = Utils.toolsDir().equals("windows") ? "dexmerger.bat" : "dexmerger";
-    return new File("tools/" + Utils.toolsDir() + "/dx/bin/" + executableName);
+  public static Path dxExecutable() {
+    String dxExecutableName = isWindows() ? "dx.bat" : "dx";
+    return Paths.get("tools", toolsDir(), "dx", "bin", dxExecutableName);
+  }
+
+  public static Path dexMergerExecutable() {
+    String executableName = isWindows() ? "dexmerger.bat" : "dexmerger";
+    return Paths.get("tools", toolsDir(), "dx", "bin", executableName);
   }
 }
diff --git a/src/library_desugar/jdk11/desugar_jdk_libs_alternative_3.json b/src/library_desugar/jdk11/desugar_jdk_libs_alternative_3.json
index de5d525..57f7c3e 100644
--- a/src/library_desugar/jdk11/desugar_jdk_libs_alternative_3.json
+++ b/src/library_desugar/jdk11/desugar_jdk_libs_alternative_3.json
@@ -55,7 +55,6 @@
       "api_level_below_or_equal": 23,
       "rewrite_prefix": {
         "java.io.DesugarBufferedReader": "j$.io.DesugarBufferedReader",
-        "java.io.UncheckedIOException": "j$.io.UncheckedIOException",
         "java.util.DoubleSummaryStatistics": "j$.util.DoubleSummaryStatistics",
         "java.util.IntSummaryStatistics": "j$.util.IntSummaryStatistics",
         "java.util.LongSummaryStatistics": "j$.util.LongSummaryStatistics",
@@ -70,6 +69,9 @@
         "java.util.function.": "j$.util.function.",
         "java.util.stream.": "j$.util.stream."
       },
+      "maintain_prefix": [
+        "java.io.UncheckedIOException"
+      ],
       "emulate_interface": {
         "java.lang.Iterable": "j$.lang.Iterable",
         "java.util.Collection": "j$.util.Collection",
diff --git a/src/main/java/com/android/tools/r8/CompatProguardCommandBuilder.java b/src/main/java/com/android/tools/r8/CompatProguardCommandBuilder.java
index 90544cc..b638235 100644
--- a/src/main/java/com/android/tools/r8/CompatProguardCommandBuilder.java
+++ b/src/main/java/com/android/tools/r8/CompatProguardCommandBuilder.java
@@ -14,9 +14,7 @@
   public CompatProguardCommandBuilder(
       boolean forceProguardCompatibility, DiagnosticsHandler diagnosticsHandler) {
     super(diagnosticsHandler);
-    if (forceProguardCompatibility) {
-      internalForceProguardCompatibility();
-    }
+    setProguardCompatibility(forceProguardCompatibility);
     setIgnoreDexInArchive(true);
   }
 
@@ -26,9 +24,7 @@
 
   public CompatProguardCommandBuilder(
       boolean forceProguardCompatibility, boolean disableVerticalClassMerging) {
-    if (forceProguardCompatibility) {
-      internalForceProguardCompatibility();
-    }
+    setProguardCompatibility(forceProguardCompatibility);
     setDisableVerticalClassMerging(disableVerticalClassMerging);
     setIgnoreDexInArchive(true);
   }
diff --git a/src/main/java/com/android/tools/r8/D8Command.java b/src/main/java/com/android/tools/r8/D8Command.java
index 7f74668..c953853 100644
--- a/src/main/java/com/android/tools/r8/D8Command.java
+++ b/src/main/java/com/android/tools/r8/D8Command.java
@@ -539,7 +539,6 @@
     assert !internal.isMinifying();
     assert !internal.passthroughDexCode;
     internal.passthroughDexCode = true;
-    assert internal.neverMergePrefixes.contains("j$.");
 
     // Assert some of R8 optimizations are disabled.
     assert !internal.inlinerOptions().enableInlining;
@@ -580,7 +579,9 @@
     HorizontalClassMergerOptions horizontalClassMergerOptions =
         internal.horizontalClassMergerOptions();
     if (internal.isGeneratingDex()) {
-      horizontalClassMergerOptions.setRestrictToSynthetics();
+      // TODO(b/227791663): Disable until fixed.
+      horizontalClassMergerOptions.disable();
+      // horizontalClassMergerOptions.setRestrictToSynthetics();
     } else {
       assert internal.isGeneratingClassFiles();
       horizontalClassMergerOptions.disable();
diff --git a/src/main/java/com/android/tools/r8/R8Command.java b/src/main/java/com/android/tools/r8/R8Command.java
index 05f03d2..f259f26 100644
--- a/src/main/java/com/android/tools/r8/R8Command.java
+++ b/src/main/java/com/android/tools/r8/R8Command.java
@@ -138,10 +138,6 @@
 
     // Internal
 
-    void internalForceProguardCompatibility() {
-      this.forceProguardCompatibility = true;
-    }
-
     void setDisableVerticalClassMerging(boolean disableVerticalClassMerging) {
       this.disableVerticalClassMerging = disableVerticalClassMerging;
     }
@@ -203,6 +199,23 @@
       return self();
     }
 
+    /**
+     * Set Proguard compatibility mode.
+     *
+     * <p>If true, R8 will attempt to retain more compatibility with Proguard. Most notably, R8 will
+     * introduce rules for keeping more default constructors as well as various attributes. Note
+     * that setting R8 in compatibility mode will result in larger residual programs.
+     */
+    public Builder setProguardCompatibility(boolean value) {
+      this.forceProguardCompatibility = value;
+      return self();
+    }
+
+    /** Get the current value of Proguard compatibility mode. */
+    public boolean getProguardCompatibility() {
+      return forceProguardCompatibility;
+    }
+
     /** Add proguard configuration-file resources. */
     public Builder addProguardConfigurationFiles(Path... paths) {
       guard(() -> {
@@ -851,6 +864,11 @@
     return enableMinification;
   }
 
+  /** Get the Proguard compatibility state. */
+  public boolean getProguardCompatibility() {
+    return forceProguardCompatibility;
+  }
+
   @Override
   InternalOptions getInternalOptions() {
     InternalOptions internal = new InternalOptions(getMode(), proguardConfiguration, getReporter());
diff --git a/src/main/java/com/android/tools/r8/R8CommandParser.java b/src/main/java/com/android/tools/r8/R8CommandParser.java
index 5e7cf59..56e2b97 100644
--- a/src/main/java/com/android/tools/r8/R8CommandParser.java
+++ b/src/main/java/com/android/tools/r8/R8CommandParser.java
@@ -83,6 +83,7 @@
                       + "# Minimum Android API level compatibility, default: "
                       + AndroidApiLevel.getDefault().getLevel()
                       + ".",
+                  "  --pg-compat             # Compile with R8 in Proguard compatibility mode.",
                   "  --pg-conf <file>        # Proguard configuration <file>.",
                   "  --pg-map-output <file>  # Output the resulting name and line mapping to"
                       + " <file>.",
@@ -195,6 +196,8 @@
                   "Cannot compile in both --debug and --release mode.", argsOrigin));
         }
         state.mode = CompilationMode.RELEASE;
+      } else if (arg.equals("--pg-compat")) {
+        builder.setProguardCompatibility(true);
       } else if (arg.equals("--dex")) {
         if (state.outputMode == OutputMode.ClassFile) {
           builder.error(
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfFieldInstruction.java b/src/main/java/com/android/tools/r8/cf/code/CfFieldInstruction.java
index 29dc73f..ce94d5d 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfFieldInstruction.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfFieldInstruction.java
@@ -93,6 +93,12 @@
     return opcode == Opcodes.GETSTATIC;
   }
 
+  public boolean isStaticFieldPut() {
+    return opcode == Opcodes.PUTSTATIC;
+  }
+
+  public abstract CfFieldInstruction createWithField(DexField field);
+
   @Override
   public CfFieldInstruction asFieldInstruction() {
     return this;
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfInstanceFieldRead.java b/src/main/java/com/android/tools/r8/cf/code/CfInstanceFieldRead.java
index 145bc94..2684c0d 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfInstanceFieldRead.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfInstanceFieldRead.java
@@ -22,6 +22,11 @@
   }
 
   @Override
+  public CfFieldInstruction createWithField(DexField otherField) {
+    return new CfInstanceFieldRead(otherField);
+  }
+
+  @Override
   void internalRegisterUse(
       UseRegistry<?> registry, DexClassAndMethod context, ListIterator<CfInstruction> iterator) {
     registry.registerInstanceFieldReadInstruction(this);
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfInstanceFieldWrite.java b/src/main/java/com/android/tools/r8/cf/code/CfInstanceFieldWrite.java
index 6d01f49..c9138bb 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfInstanceFieldWrite.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfInstanceFieldWrite.java
@@ -21,6 +21,11 @@
   }
 
   @Override
+  public CfFieldInstruction createWithField(DexField otherField) {
+    return new CfInstanceFieldWrite(otherField);
+  }
+
+  @Override
   void internalRegisterUse(
       UseRegistry<?> registry, DexClassAndMethod context, ListIterator<CfInstruction> iterator) {
     registry.registerInstanceFieldWrite(getField());
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfStaticFieldRead.java b/src/main/java/com/android/tools/r8/cf/code/CfStaticFieldRead.java
index 66fc48e..30715e1 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfStaticFieldRead.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfStaticFieldRead.java
@@ -22,6 +22,11 @@
   }
 
   @Override
+  public CfFieldInstruction createWithField(DexField otherField) {
+    return new CfStaticFieldRead(otherField);
+  }
+
+  @Override
   void internalRegisterUse(
       UseRegistry<?> registry, DexClassAndMethod context, ListIterator<CfInstruction> iterator) {
     registry.registerStaticFieldReadInstruction(this);
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfStaticFieldWrite.java b/src/main/java/com/android/tools/r8/cf/code/CfStaticFieldWrite.java
index 2cd0f26..24b5e6d 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfStaticFieldWrite.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfStaticFieldWrite.java
@@ -21,6 +21,11 @@
   }
 
   @Override
+  public CfFieldInstruction createWithField(DexField otherField) {
+    return new CfStaticFieldWrite(otherField);
+  }
+
+  @Override
   void internalRegisterUse(
       UseRegistry<?> registry, DexClassAndMethod context, ListIterator<CfInstruction> iterator) {
     registry.registerStaticFieldWrite(getField());
diff --git a/src/main/java/com/android/tools/r8/dex/CodeToKeep.java b/src/main/java/com/android/tools/r8/dex/CodeToKeep.java
index 3e33ca8..7bfd947 100644
--- a/src/main/java/com/android/tools/r8/dex/CodeToKeep.java
+++ b/src/main/java/com/android/tools/r8/dex/CodeToKeep.java
@@ -25,6 +25,7 @@
 
   static CodeToKeep createCodeToKeep(InternalOptions options, NamingLens namingLens) {
     if ((!namingLens.hasPrefixRewritingLogic()
+            && options.machineDesugaredLibrarySpecification.getMaintainType().isEmpty()
             && !options.machineDesugaredLibrarySpecification.hasEmulatedInterfaces())
         || options.isDesugaredLibraryCompilation()
         || options.testing.enableExperimentalDesugaredLibraryKeepRuleGenerator) {
@@ -67,6 +68,7 @@
 
     private boolean shouldKeep(DexType type) {
       return namingLens.prefixRewrittenType(type) != null
+          || options.machineDesugaredLibrarySpecification.getMaintainType().contains(type)
           || options.machineDesugaredLibrarySpecification.isCustomConversionRewrittenType(type)
           || options.machineDesugaredLibrarySpecification.isEmulatedInterfaceRewrittenType(type)
           // TODO(b/158632510): This should prefix match on DexString.
diff --git a/src/main/java/com/android/tools/r8/graph/DexAnnotation.java b/src/main/java/com/android/tools/r8/graph/DexAnnotation.java
index 5a2bffc..d26278f 100644
--- a/src/main/java/com/android/tools/r8/graph/DexAnnotation.java
+++ b/src/main/java/com/android/tools/r8/graph/DexAnnotation.java
@@ -390,9 +390,13 @@
 
   public static DexAnnotation createAnnotationSynthesizedClass(
       SyntheticKind kind, DexItemFactory dexItemFactory) {
+    DexString versionHash =
+        dexItemFactory.createString(dexItemFactory.getSyntheticNaming().getVersionHash());
     DexAnnotationElement kindElement =
         new DexAnnotationElement(dexItemFactory.kindString, DexValueInt.create(kind.getId()));
-    DexAnnotationElement[] elements = new DexAnnotationElement[] {kindElement};
+    DexAnnotationElement versionHashElement =
+        new DexAnnotationElement(dexItemFactory.versionHashString, new DexValueString(versionHash));
+    DexAnnotationElement[] elements = new DexAnnotationElement[] {kindElement, versionHashElement};
     return new DexAnnotation(
         VISIBILITY_BUILD,
         new DexEncodedAnnotation(dexItemFactory.annotationSynthesizedClass, elements));
@@ -413,17 +417,29 @@
       return null;
     }
     int length = annotation.annotation.elements.length;
-    if (length != 1) {
+    if (length != 2) {
       return null;
     }
-    assert factory.kindString.isLessThan(factory.valueString);
+    assert factory.kindString.isLessThan(factory.versionHashString);
     DexAnnotationElement kindElement = annotation.annotation.elements[0];
+    DexAnnotationElement versionHashElement = annotation.annotation.elements[1];
     if (kindElement.name != factory.kindString) {
       return null;
     }
     if (!kindElement.value.isDexValueInt()) {
       return null;
     }
+    if (versionHashElement.name != factory.versionHashString) {
+      return null;
+    }
+    if (!versionHashElement.value.isDexValueString()) {
+      return null;
+    }
+    String currentVersionHash = synthetics.getNaming().getVersionHash();
+    String syntheticVersionHash = versionHashElement.value.asDexValueString().getValue().toString();
+    if (!currentVersionHash.equals(syntheticVersionHash)) {
+      return null;
+    }
     SyntheticKind kind =
         synthetics.getNaming().fromId(kindElement.value.asDexValueInt().getValue());
     return kind;
diff --git a/src/main/java/com/android/tools/r8/graph/DexClassAndMethod.java b/src/main/java/com/android/tools/r8/graph/DexClassAndMethod.java
index 765fef3..aeab5fe 100644
--- a/src/main/java/com/android/tools/r8/graph/DexClassAndMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/DexClassAndMethod.java
@@ -6,10 +6,9 @@
 
 import com.android.tools.r8.ir.optimize.info.MethodOptimizationInfo;
 import com.android.tools.r8.references.MethodReference;
-import java.util.function.Consumer;
 
 public abstract class DexClassAndMethod extends DexClassAndMember<DexEncodedMethod, DexMethod>
-    implements LookupTarget {
+    implements LookupMethodTarget {
 
   DexClassAndMethod(DexClass holder, DexEncodedMethod method) {
     super(holder, method);
@@ -88,16 +87,6 @@
   }
 
   @Override
-  public boolean isMethodTarget() {
-    return true;
-  }
-
-  @Override
-  public DexClassAndMethod asMethodTarget() {
-    return this;
-  }
-
-  @Override
   public boolean isMethod() {
     return true;
   }
@@ -113,8 +102,7 @@
   }
 
   @Override
-  public void accept(
-      Consumer<DexClassAndMethod> methodConsumer, Consumer<LookupLambdaTarget> lambdaConsumer) {
-    methodConsumer.accept(this);
+  public DexClassAndMethod getTarget() {
+    return this;
   }
 }
diff --git a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
index 3e5150c..98dac6d 100644
--- a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
+++ b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
@@ -338,6 +338,7 @@
 
   public final DexString valueString = createString("value");
   public final DexString kindString = createString("kind");
+  public final DexString versionHashString = createString("versionHash");
 
   // Prefix for runtime affecting yet potential class-retained annotations.
   public final DexString dalvikAnnotationOptimizationPrefix =
@@ -643,7 +644,7 @@
       createStaticallyKnownType("Ldalvik/annotation/SourceDebugExtension;");
   public final DexType annotationThrows = createStaticallyKnownType("Ldalvik/annotation/Throws;");
   public final DexType annotationSynthesizedClass =
-      createStaticallyKnownType("Lcom/android/tools/r8/annotations/SynthesizedClass;");
+      createStaticallyKnownType("Lcom/android/tools/r8/annotations/SynthesizedClassV2;");
   public final DexType annotationCovariantReturnType =
       createStaticallyKnownType("Ldalvik/annotation/codegen/CovariantReturnType;");
   public final DexType annotationCovariantReturnTypes =
diff --git a/src/main/java/com/android/tools/r8/graph/DexType.java b/src/main/java/com/android/tools/r8/graph/DexType.java
index 810874b..2e84b7a 100644
--- a/src/main/java/com/android/tools/r8/graph/DexType.java
+++ b/src/main/java/com/android/tools/r8/graph/DexType.java
@@ -157,7 +157,8 @@
     if (clazz == null) {
       return false;
     }
-    if (clazz.isInterface() && appView.getOpenClosedInterfacesCollection().isMaybeOpen(clazz)) {
+    // TODO(b/214496607): Allow uninstantiated reasoning for closed interfaces.
+    if (clazz.isInterface()) {
       return false;
     }
     return !appView.appInfo().isInstantiatedDirectlyOrIndirectly(clazz);
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 8e5ec1f..4354f06 100644
--- a/src/main/java/com/android/tools/r8/graph/JarClassFileReader.java
+++ b/src/main/java/com/android/tools/r8/graph/JarClassFileReader.java
@@ -360,9 +360,6 @@
       if (InternalOptions.SUPPORTED_CF_VERSION.isLessThan(version)) {
         throw new CompilationError("Unsupported class file version: " + version, origin);
       }
-      if (version.isGreaterThanOrEqualTo(InternalOptions.EXPERIMENTAL_CF_VERSION)) {
-        application.options.warningExperimentalClassFileVersion(origin);
-      }
       this.deprecated = AsmUtils.isDeprecated(access);
       accessFlags = ClassAccessFlags.fromCfAccessFlags(cleanAccessFlags(access));
       type = application.getTypeFromName(name);
diff --git a/src/main/java/com/android/tools/r8/graph/LookupCompletenessHelper.java b/src/main/java/com/android/tools/r8/graph/LookupCompletenessHelper.java
index 4a99071..504ca65 100644
--- a/src/main/java/com/android/tools/r8/graph/LookupCompletenessHelper.java
+++ b/src/main/java/com/android/tools/r8/graph/LookupCompletenessHelper.java
@@ -38,9 +38,9 @@
     }
   }
 
-  void checkDexClassAndMethod(DexClassAndMethod classAndMethod) {
-    checkClass(classAndMethod.getHolder());
-    checkMethod(classAndMethod.getDefinition());
+  void checkDexClassAndMethod(LookupMethodTarget methodTarget) {
+    checkClass(methodTarget.getHolder());
+    checkMethod(methodTarget.getDefinition());
   }
 
   LookupResultCollectionState computeCollectionState(
diff --git a/src/main/java/com/android/tools/r8/graph/LookupLambdaTarget.java b/src/main/java/com/android/tools/r8/graph/LookupLambdaTarget.java
index 12eeddc..e1209e9 100644
--- a/src/main/java/com/android/tools/r8/graph/LookupLambdaTarget.java
+++ b/src/main/java/com/android/tools/r8/graph/LookupLambdaTarget.java
@@ -30,7 +30,7 @@
 
   @Override
   public void accept(
-      Consumer<DexClassAndMethod> methodConsumer, Consumer<LookupLambdaTarget> lambdaConsumer) {
+      Consumer<LookupMethodTarget> methodConsumer, Consumer<LookupLambdaTarget> lambdaConsumer) {
     lambdaConsumer.accept(this);
   }
 
diff --git a/src/main/java/com/android/tools/r8/graph/LookupMethodTarget.java b/src/main/java/com/android/tools/r8/graph/LookupMethodTarget.java
new file mode 100644
index 0000000..b7a6aa0
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/graph/LookupMethodTarget.java
@@ -0,0 +1,33 @@
+// Copyright (c) 2022, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.graph;
+
+import java.util.function.Consumer;
+
+public interface LookupMethodTarget extends LookupTarget {
+
+  @Override
+  default boolean isMethodTarget() {
+    return true;
+  }
+
+  @Override
+  default LookupMethodTarget asMethodTarget() {
+    return this;
+  }
+
+  @Override
+  default void accept(
+      Consumer<LookupMethodTarget> methodConsumer, Consumer<LookupLambdaTarget> lambdaConsumer) {
+    methodConsumer.accept(this);
+  }
+
+  DexClass getHolder();
+
+  DexMethod getReference();
+
+  DexEncodedMethod getDefinition();
+
+  DexClassAndMethod getTarget();
+}
diff --git a/src/main/java/com/android/tools/r8/graph/LookupMethodTargetWithAccessOverride.java b/src/main/java/com/android/tools/r8/graph/LookupMethodTargetWithAccessOverride.java
new file mode 100644
index 0000000..d69c5b3
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/graph/LookupMethodTargetWithAccessOverride.java
@@ -0,0 +1,41 @@
+// Copyright (c) 2022, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.graph;
+
+public class LookupMethodTargetWithAccessOverride implements LookupMethodTarget {
+
+  private final DexClassAndMethod target;
+  private final DexClassAndMethod accessOverride;
+
+  public LookupMethodTargetWithAccessOverride(
+      DexClassAndMethod target, DexClassAndMethod accessOverride) {
+    this.target = target;
+    this.accessOverride = accessOverride;
+  }
+
+  @Override
+  public DexClassAndMethod getAccessOverride() {
+    return accessOverride;
+  }
+
+  @Override
+  public DexClass getHolder() {
+    return target.getHolder();
+  }
+
+  @Override
+  public DexMethod getReference() {
+    return target.getReference();
+  }
+
+  @Override
+  public DexEncodedMethod getDefinition() {
+    return target.getDefinition();
+  }
+
+  @Override
+  public DexClassAndMethod getTarget() {
+    return target;
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/graph/LookupResult.java b/src/main/java/com/android/tools/r8/graph/LookupResult.java
index bc85814..b8bc3c8 100644
--- a/src/main/java/com/android/tools/r8/graph/LookupResult.java
+++ b/src/main/java/com/android/tools/r8/graph/LookupResult.java
@@ -5,10 +5,11 @@
 package com.android.tools.r8.graph;
 
 import com.android.tools.r8.graph.LookupResult.LookupResultSuccess.LookupResultCollectionState;
-import com.android.tools.r8.utils.collections.DexClassAndMethodSet;
 import java.util.ArrayList;
 import java.util.Collections;
+import java.util.IdentityHashMap;
 import java.util.List;
+import java.util.Map;
 import java.util.function.Consumer;
 
 public abstract class LookupResult {
@@ -34,14 +35,14 @@
   }
 
   public abstract void forEach(
-      Consumer<? super DexClassAndMethod> onMethodTarget,
+      Consumer<? super LookupMethodTarget> onMethodTarget,
       Consumer<? super LookupLambdaTarget> onLambdaTarget);
 
   public abstract void forEachFailureDependency(
       Consumer<? super DexEncodedMethod> methodCausingFailureConsumer);
 
   public static LookupResultSuccess createResult(
-      DexClassAndMethodSet methodTargets,
+      Map<DexMethod, LookupMethodTarget> methodTargets,
       List<LookupLambdaTarget> lambdaTargets,
       List<DexEncodedMethod> methodsCausingFailure,
       LookupResultCollectionState state) {
@@ -60,18 +61,18 @@
 
     private static final LookupResultSuccess EMPTY_INSTANCE =
         new LookupResultSuccess(
-            DexClassAndMethodSet.empty(),
+            new IdentityHashMap<>(),
             Collections.emptyList(),
             Collections.emptyList(),
             LookupResultCollectionState.Incomplete);
 
-    private final DexClassAndMethodSet methodTargets;
+    private final Map<DexMethod, LookupMethodTarget> methodTargets;
     private final List<LookupLambdaTarget> lambdaTargets;
     private final List<DexEncodedMethod> methodsCausingFailure;
     private LookupResultCollectionState state;
 
     private LookupResultSuccess(
-        DexClassAndMethodSet methodTargets,
+        Map<DexMethod, LookupMethodTarget> methodTargets,
         List<LookupLambdaTarget> lambdaTargets,
         List<DexEncodedMethod> methodsCausingFailure,
         LookupResultCollectionState state) {
@@ -99,9 +100,9 @@
 
     @Override
     public void forEach(
-        Consumer<? super DexClassAndMethod> onMethodTarget,
+        Consumer<? super LookupMethodTarget> onMethodTarget,
         Consumer<? super LookupLambdaTarget> onLambdaTarget) {
-      methodTargets.forEach(onMethodTarget);
+      methodTargets.forEach((key, value) -> onMethodTarget.accept(value));
       lambdaTargets.forEach(onLambdaTarget);
     }
 
@@ -113,7 +114,7 @@
 
     public boolean contains(DexEncodedMethod method) {
       // Containment of a method in the lookup results only pertains to the method targets.
-      return methodTargets.contains(method);
+      return methodTargets.containsKey(method.getReference());
     }
 
     @Override
@@ -145,7 +146,7 @@
       }
       // TODO(b/150932978): Check lambda targets implementation methods.
       if (methodTargets.size() == 1) {
-        return methodTargets.iterator().next();
+        return methodTargets.values().iterator().next();
       } else if (lambdaTargets.size() == 1) {
         return lambdaTargets.get(0);
       }
@@ -159,13 +160,14 @@
 
     public static class Builder {
 
-      private final DexClassAndMethodSet methodTargets = DexClassAndMethodSet.create();
+      private final Map<DexMethod, LookupMethodTarget> methodTargets = new IdentityHashMap<>();
       private final List<LookupLambdaTarget> lambdaTargets = new ArrayList<>();
       private final List<DexEncodedMethod> methodsCausingFailure = new ArrayList<>();
       private LookupResultCollectionState state;
 
-      public Builder addMethodTarget(DexClassAndMethod methodTarget) {
-        methodTargets.add(methodTarget);
+      public Builder addMethodTarget(LookupMethodTarget methodTarget) {
+        assert methodTarget.isMethodTarget();
+        methodTargets.putIfAbsent(methodTarget.asMethodTarget().getReference(), methodTarget);
         return this;
       }
 
@@ -210,7 +212,7 @@
 
     @Override
     public void forEach(
-        Consumer<? super DexClassAndMethod> onMethodTarget,
+        Consumer<? super LookupMethodTarget> onMethodTarget,
         Consumer<? super LookupLambdaTarget> onLambdaTarget) {
       // Nothing to iterate for a failed lookup.
     }
diff --git a/src/main/java/com/android/tools/r8/graph/LookupTarget.java b/src/main/java/com/android/tools/r8/graph/LookupTarget.java
index cc0d574..4530c0f 100644
--- a/src/main/java/com/android/tools/r8/graph/LookupTarget.java
+++ b/src/main/java/com/android/tools/r8/graph/LookupTarget.java
@@ -14,7 +14,7 @@
     return false;
   }
 
-  default DexClassAndMethod asMethodTarget() {
+  default LookupMethodTarget asMethodTarget() {
     return null;
   }
 
@@ -22,6 +22,10 @@
     return null;
   }
 
+  default DexClassAndMethod getAccessOverride() {
+    return null;
+  }
+
   void accept(
-      Consumer<DexClassAndMethod> methodConsumer, Consumer<LookupLambdaTarget> lambdaConsumer);
+      Consumer<LookupMethodTarget> methodConsumer, Consumer<LookupLambdaTarget> lambdaConsumer);
 }
diff --git a/src/main/java/com/android/tools/r8/graph/MethodResolutionResult.java b/src/main/java/com/android/tools/r8/graph/MethodResolutionResult.java
index 113c31c..49860a7 100644
--- a/src/main/java/com/android/tools/r8/graph/MethodResolutionResult.java
+++ b/src/main/java/com/android/tools/r8/graph/MethodResolutionResult.java
@@ -13,7 +13,6 @@
 import com.android.tools.r8.utils.BooleanBox;
 import com.android.tools.r8.utils.Box;
 import com.android.tools.r8.utils.OptionalBool;
-import com.android.tools.r8.utils.collections.DexClassAndMethodSet;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.function.BiPredicate;
@@ -152,7 +151,7 @@
   public abstract LookupTarget lookupVirtualDispatchTarget(
       InstantiatedObject instance, AppInfoWithClassHierarchy appInfo);
 
-  public abstract DexClassAndMethod lookupVirtualDispatchTarget(
+  public abstract LookupMethodTarget lookupVirtualDispatchTarget(
       DexClass dynamicInstance, AppInfoWithClassHierarchy appInfo);
 
   public abstract LookupTarget lookupVirtualDispatchTarget(
@@ -451,8 +450,9 @@
         // Only include if the target has code or is native.
         boolean isIncomplete =
             pinnedPredicate.isPinned(resolvedHolder) && pinnedPredicate.isPinned(resolvedMethod);
+        DexClassAndMethod resolutionPair = getResolutionPair();
         return LookupResult.createResult(
-            DexClassAndMethodSet.create(getResolutionPair()),
+            Collections.singletonMap(resolutionPair.getReference(), resolutionPair),
             Collections.emptyList(),
             Collections.emptyList(),
             isIncomplete
@@ -466,13 +466,12 @@
           initialResolutionHolder.type,
           subClass -> {
             incompleteness.checkClass(subClass);
-            DexClassAndMethod dexClassAndMethod =
+            LookupMethodTarget lookupTarget =
                 lookupVirtualDispatchTarget(
                     subClass, appInfo, resolvedHolder.type, resultBuilder::addMethodCausingFailure);
-            if (dexClassAndMethod != null) {
-              incompleteness.checkDexClassAndMethod(dexClassAndMethod);
-              addVirtualDispatchTarget(
-                  dexClassAndMethod, resolvedHolder.isInterface(), resultBuilder);
+            if (lookupTarget != null) {
+              incompleteness.checkDexClassAndMethod(lookupTarget);
+              addVirtualDispatchTarget(lookupTarget, resolvedHolder.isInterface(), resultBuilder);
             }
           },
           lambda -> {
@@ -552,10 +551,11 @@
     }
 
     private static void addVirtualDispatchTarget(
-        DexClassAndMethod target,
+        LookupMethodTarget target,
         boolean holderIsInterface,
         LookupResultSuccess.Builder resultBuilder) {
-      DexEncodedMethod targetMethod = target.getDefinition();
+      assert target.isMethodTarget();
+      DexEncodedMethod targetMethod = target.asMethodTarget().getDefinition();
       assert !targetMethod.isPrivateMethod();
       if (holderIsInterface) {
         // Add default interface methods to the list of targets.
@@ -609,7 +609,7 @@
     }
 
     @Override
-    public DexClassAndMethod lookupVirtualDispatchTarget(
+    public LookupMethodTarget lookupVirtualDispatchTarget(
         DexClass dynamicInstance, AppInfoWithClassHierarchy appInfo) {
       return lookupVirtualDispatchTarget(
           dynamicInstance, appInfo, initialResolutionHolder.type, emptyConsumer());
@@ -634,7 +634,7 @@
           lambdaInstance, appInfo, methodCausingFailureConsumer);
     }
 
-    private DexClassAndMethod lookupVirtualDispatchTarget(
+    private LookupMethodTarget lookupVirtualDispatchTarget(
         DexClass dynamicInstance,
         AppInfoWithClassHierarchy appInfo,
         DexType resolutionHolder,
@@ -644,18 +644,20 @@
       // TODO(b/148591377): Enable this assertion.
       // The dynamic type cannot be an interface.
       // assert !dynamicInstance.isInterface();
+      DexClassAndMethod initialResolutionPair = getResolutionPair();
       if (resolvedMethod.isPrivateMethod()) {
         // If the resolved reference is private there is no dispatch.
         // This is assuming that the method is accessible, which implies self/nest access.
-        return getResolutionPair();
+        return initialResolutionPair;
       }
       boolean allowPackageBlocked = resolvedMethod.accessFlags.isPackagePrivate();
       DexClass current = dynamicInstance;
-      DexEncodedMethod overrideTarget = resolvedMethod;
+      DexClassAndMethod overrideTarget = initialResolutionPair;
       while (current != null) {
-        DexEncodedMethod candidate = lookupOverrideCandidate(overrideTarget, current);
+        DexEncodedMethod candidate =
+            lookupOverrideCandidate(overrideTarget.getDefinition(), current);
         if (candidate == DexEncodedMethod.SENTINEL && allowPackageBlocked) {
-          overrideTarget = findWideningOverride(resolvedMethod, current, appInfo);
+          overrideTarget = findWideningOverride(initialResolutionPair, current, appInfo);
           allowPackageBlocked = false;
           continue;
         }
@@ -667,7 +669,10 @@
           current = current.superType == null ? null : appInfo.definitionFor(current.superType);
           continue;
         }
-        return DexClassAndMethod.create(current, candidate);
+        DexClassAndMethod target = DexClassAndMethod.create(current, candidate);
+        return overrideTarget != initialResolutionPair
+            ? new LookupMethodTargetWithAccessOverride(target, overrideTarget)
+            : target;
       }
       // If we have not found a candidate and the holder is not an interface it must be because the
       // class is missing.
@@ -732,10 +737,10 @@
       return null;
     }
 
-    private static DexEncodedMethod findWideningOverride(
-        DexEncodedMethod resolvedMethod, DexClass clazz, AppInfoWithClassHierarchy appView) {
+    private static DexClassAndMethod findWideningOverride(
+        DexClassAndMethod resolvedMethod, DexClass clazz, AppInfoWithClassHierarchy appView) {
       // Otherwise, lookup to first override that is distinct from resolvedMethod.
-      assert resolvedMethod.accessFlags.isPackagePrivate();
+      assert resolvedMethod.getDefinition().accessFlags.isPackagePrivate();
       while (clazz.superType != null) {
         clazz = appView.definitionFor(clazz.superType);
         if (clazz == null) {
@@ -743,10 +748,10 @@
         }
         DexEncodedMethod otherOverride = clazz.lookupVirtualMethod(resolvedMethod.getReference());
         if (otherOverride != null
-            && isOverriding(resolvedMethod, otherOverride)
+            && isOverriding(resolvedMethod.getDefinition(), otherOverride)
             && (otherOverride.accessFlags.isPublic() || otherOverride.accessFlags.isProtected())) {
-          assert resolvedMethod != otherOverride;
-          return otherOverride;
+          assert resolvedMethod.getDefinition() != otherOverride;
+          return DexClassAndMethod.create(clazz, otherOverride);
         }
       }
       return resolvedMethod;
@@ -818,19 +823,19 @@
     }
 
     @Override
-    public DexClassAndMethod lookupVirtualDispatchTarget(
+    public LookupTarget lookupVirtualDispatchTarget(
         InstantiatedObject instance, AppInfoWithClassHierarchy appInfo) {
       return null;
     }
 
     @Override
-    public DexClassAndMethod lookupVirtualDispatchTarget(
+    public LookupMethodTarget lookupVirtualDispatchTarget(
         DexClass dynamicInstance, AppInfoWithClassHierarchy appInfo) {
       return null;
     }
 
     @Override
-    public DexClassAndMethod lookupVirtualDispatchTarget(
+    public LookupTarget lookupVirtualDispatchTarget(
         LambdaDescriptor lambdaInstance,
         AppInfoWithClassHierarchy appInfo,
         Consumer<? super DexEncodedMethod> methodCausingFailureConsumer) {
@@ -1032,4 +1037,5 @@
       return invalidSymbolicReference.get();
     }
   }
+
 }
diff --git a/src/main/java/com/android/tools/r8/graph/analysis/ApiModelAnalysis.java b/src/main/java/com/android/tools/r8/graph/analysis/ApiModelAnalysis.java
index 1321169..aebbc12 100644
--- a/src/main/java/com/android/tools/r8/graph/analysis/ApiModelAnalysis.java
+++ b/src/main/java/com/android/tools/r8/graph/analysis/ApiModelAnalysis.java
@@ -73,7 +73,7 @@
   public void notifyMarkVirtualDispatchTargetAsLive(
       LookupTarget target, EnqueuerWorklist worklist) {
     target.accept(
-        this::computeAndSetApiLevelForDefinition,
+        lookupMethodTarget -> computeAndSetApiLevelForDefinition(lookupMethodTarget.getTarget()),
         lookupLambdaTarget -> {
           // The implementation method will be assigned an api level when visited.
         });
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokeMethod.java b/src/main/java/com/android/tools/r8/ir/code/InvokeMethod.java
index 1d259f6..4e77680 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InvokeMethod.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InvokeMethod.java
@@ -170,7 +170,8 @@
     }
     ProgramMethodSet result = ProgramMethodSet.create();
     lookupResult.forEach(
-        methodTarget -> {
+        target -> {
+          DexClassAndMethod methodTarget = target.getTarget();
           if (methodTarget.isProgramMethod()) {
             result.add(methodTarget.asProgramMethod());
           }
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 acadf11..9ccaec9 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
@@ -83,7 +83,6 @@
 import com.android.tools.r8.optimize.argumentpropagation.ArgumentPropagator;
 import com.android.tools.r8.optimize.argumentpropagation.ArgumentPropagatorIROptimizer;
 import com.android.tools.r8.optimize.interfaces.analysis.OpenClosedInterfacesAnalysis;
-import com.android.tools.r8.optimize.interfaces.analysis.OpenClosedInterfacesAnalysisImpl;
 import com.android.tools.r8.position.MethodPosition;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.shaking.KeepMethodInfo;
@@ -93,6 +92,7 @@
 import com.android.tools.r8.utils.DescriptorUtils;
 import com.android.tools.r8.utils.ExceptionUtils;
 import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.InternalOptions.NeverMergeGroup;
 import com.android.tools.r8.utils.ListUtils;
 import com.android.tools.r8.utils.StringDiagnostic;
 import com.android.tools.r8.utils.ThreadUtils;
@@ -107,7 +107,6 @@
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.atomic.AtomicBoolean;
-import java.util.stream.Collectors;
 
 public class IRConverter {
 
@@ -158,7 +157,7 @@
   private List<Action> onWaveDoneActions = null;
   private final Set<DexMethod> prunedMethodsInWave = Sets.newIdentityHashSet();
 
-  private final List<DexString> neverMergePrefixes;
+  private final NeverMergeGroup<DexString> neverMerge;
   // Use AtomicBoolean to satisfy TSAN checking (see b/153714743).
   AtomicBoolean seenNotNeverMergePrefix = new AtomicBoolean();
   AtomicBoolean seenNeverMergePrefix = new AtomicBoolean();
@@ -184,11 +183,11 @@
     this.deadCodeRemover = new DeadCodeRemover(appView, codeRewriter);
     this.assertionsRewriter = new AssertionsRewriter(appView);
     this.idempotentFunctionCallCanonicalizer = new IdempotentFunctionCallCanonicalizer(appView);
-    this.neverMergePrefixes =
-        options.neverMergePrefixes.stream()
-            .map(prefix -> "L" + DescriptorUtils.getPackageBinaryNameFromJavaType(prefix))
-            .map(options.itemFactory::createString)
-            .collect(Collectors.toList());
+    this.neverMerge =
+        options.neverMerge.map(
+            prefix ->
+                options.itemFactory.createString(
+                    "L" + DescriptorUtils.getPackageBinaryNameFromJavaType(prefix)));
     if (options.isDesugaredLibraryCompilation()) {
       // Specific L8 Settings, performs all desugaring including L8 specific desugaring.
       //
@@ -259,7 +258,8 @@
       this.memberValuePropagation = new MemberValuePropagation(appViewWithLiveness);
       this.methodOptimizationInfoCollector =
           new MethodOptimizationInfoCollector(appViewWithLiveness, this);
-      this.openClosedInterfacesAnalysis = new OpenClosedInterfacesAnalysisImpl(appViewWithLiveness);
+      // TODO(b/214496607): Enable open/closed interfaces analysis.
+      this.openClosedInterfacesAnalysis = OpenClosedInterfacesAnalysis.empty();
       if (options.isMinifying()) {
         this.identifierNameStringMarker = new IdentifierNameStringMarker(appViewWithLiveness);
       } else {
@@ -550,11 +550,17 @@
     if (!appView.options().enableNeverMergePrefixes) {
       return;
     }
-    for (DexString neverMergePrefix : neverMergePrefixes) {
-      if (method.getHolderType().descriptor.startsWith(neverMergePrefix)) {
+    DexString descriptor = method.getHolderType().descriptor;
+    for (DexString neverMergePrefix : neverMerge.getPrefixes()) {
+      if (descriptor.startsWith(neverMergePrefix)) {
         seenNeverMergePrefix.getAndSet(true);
       } else {
-        seenNotNeverMergePrefix.getAndSet(true);
+        for (DexString exceptionPrefix : neverMerge.getExceptionPrefixes()) {
+          if (!descriptor.startsWith(exceptionPrefix)) {
+            seenNotNeverMergePrefix.getAndSet(true);
+            break;
+          }
+        }
       }
       // Don't mix.
       // TODO(b/168001352): Consider requiring that no 'never merge' prefix is ever seen as a
@@ -562,16 +568,37 @@
       if (seenNeverMergePrefix.get() && seenNotNeverMergePrefix.get()) {
         StringBuilder message = new StringBuilder();
         message
-            .append("Merging dex file containing classes with prefix")
-            .append(neverMergePrefixes.size() > 1 ? "es " : " ");
-        for (int i = 0; i < neverMergePrefixes.size(); i++) {
+            .append("Merging DEX file containing classes with prefix")
+            .append(neverMerge.getPrefixes().size() > 1 ? "es " : " ");
+        for (int i = 0; i < neverMerge.getPrefixes().size(); i++) {
           message
               .append("'")
-              .append(neverMergePrefixes.get(0).toString().substring(1).replace('/', '.'))
+              .append(neverMerge.getPrefixes().get(i).toString().substring(1).replace('/', '.'))
               .append("'")
-              .append(i < neverMergePrefixes.size() - 1 ? ", " : "");
+              .append(i < neverMerge.getPrefixes().size() - 1 ? ", " : "");
         }
-        message.append(" with classes with any other prefixes is not allowed: ");
+        if (!neverMerge.getExceptionPrefixes().isEmpty()) {
+          message
+              .append(" with other classes, except classes with prefix")
+              .append(neverMerge.getExceptionPrefixes().size() > 1 ? "es " : " ");
+          for (int i = 0; i < neverMerge.getExceptionPrefixes().size(); i++) {
+            message
+                .append("'")
+                .append(
+                    neverMerge
+                        .getExceptionPrefixes()
+                        .get(i)
+                        .toString()
+                        .substring(1)
+                        .replace('/', '.'))
+                .append("'")
+                .append(i < neverMerge.getExceptionPrefixes().size() - 1 ? ", " : "");
+          }
+          message.append(",");
+        } else {
+          message.append(" with classes with any other prefixes");
+        }
+        message.append(" is not allowed: ");
         boolean first = true;
         int limit = 11;
         for (DexProgramClass clazz : appView.appInfo().classesWithDeterministicOrder()) {
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/callgraph/InvokeExtractor.java b/src/main/java/com/android/tools/r8/ir/conversion/callgraph/InvokeExtractor.java
index 4b15bbc..edd4f14 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/callgraph/InvokeExtractor.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/callgraph/InvokeExtractor.java
@@ -124,7 +124,8 @@
                   lookupResult
                       .asLookupResultSuccess()
                       .forEach(
-                          methodTarget -> {
+                          lookupMethodTarget -> {
+                            DexClassAndMethod methodTarget = lookupMethodTarget.getTarget();
                             if (methodTarget.isProgramMethod()) {
                               targets.add(methodTarget.asProgramMethod());
                             }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/DesugaredLibraryAmender.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/DesugaredLibraryAmender.java
index c943028..92bd91e 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/DesugaredLibraryAmender.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/DesugaredLibraryAmender.java
@@ -8,8 +8,13 @@
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexDefinitionSupplier;
+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.DexLibraryClass;
 import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexReference;
+import com.android.tools.r8.graph.FieldAccessFlags;
 import com.android.tools.r8.graph.MethodAccessFlags;
 import com.android.tools.r8.utils.Reporter;
 import java.util.Map;
@@ -28,20 +33,23 @@
   public static void run(AppView<?> appView) {
     run(
         appView.options().machineDesugaredLibrarySpecification.getAmendLibraryMethods(),
+        appView.options().machineDesugaredLibrarySpecification.getAmendLibraryFields(),
         appView,
         appView.options().reporter,
         appView.computedMinApiLevel());
   }
 
   public static void run(
-      Map<DexMethod, MethodAccessFlags> amendLibrary,
+      Map<DexMethod, MethodAccessFlags> amendLibraryMethod,
+      Map<DexField, FieldAccessFlags> amendLibraryField,
       DexDefinitionSupplier definitions,
       Reporter reporter,
       ComputedApiLevel minAPILevel) {
-    if (amendLibrary.isEmpty()) {
+    if (amendLibraryMethod.isEmpty() && amendLibraryField.isEmpty()) {
       return;
     }
-    new DesugaredLibraryAmender(definitions, reporter, minAPILevel).run(amendLibrary);
+    new DesugaredLibraryAmender(definitions, reporter, minAPILevel)
+        .run(amendLibraryMethod, amendLibraryField);
   }
 
   private DesugaredLibraryAmender(
@@ -51,22 +59,40 @@
     this.minAPILevel = minAPILevel;
   }
 
-  private void run(Map<DexMethod, MethodAccessFlags> amendLibrary) {
-    amendLibrary.forEach(this::amendLibraryMethod);
+  private void run(
+      Map<DexMethod, MethodAccessFlags> amendLibraryMethod,
+      Map<DexField, FieldAccessFlags> amendLibraryField) {
+    amendLibraryMethod.forEach(this::amendLibraryMethod);
+    amendLibraryField.forEach(this::amendLibraryField);
+  }
+
+  private void amendLibraryField(DexField field, FieldAccessFlags fieldAccessFlags) {
+    DexLibraryClass libClass = getLibraryClass(field);
+    if (libClass == null) {
+      return;
+    }
+    if (libClass.lookupField(field) != null) {
+      return;
+    }
+    DexEncodedField encodedField =
+        DexEncodedField.syntheticBuilder()
+            .setField(field)
+            .setAccessFlags(fieldAccessFlags)
+            .setApiLevel(minAPILevel)
+            .build();
+    if (fieldAccessFlags.isStatic()) {
+      libClass.appendStaticField(encodedField);
+    } else {
+      libClass.appendInstanceField(encodedField);
+    }
   }
 
   private void amendLibraryMethod(DexMethod method, MethodAccessFlags methodAccessFlags) {
-    DexClass dexClass = definitions.contextIndependentDefinitionFor(method.getHolderType());
-    if (dexClass == null || !dexClass.isLibraryClass()) {
-      // Consider just throwing an error.
-      reporter.warning(
-          "Desugared library: Cannot amend library method "
-              + method
-              + " because the holder is not a library class"
-              + (dexClass == null ? "(null)." : "."));
+    DexLibraryClass libClass = getLibraryClass(method);
+    if (libClass == null) {
       return;
     }
-    if (dexClass.lookupMethod(method) != null) {
+    if (libClass.lookupMethod(method) != null) {
       return;
     }
     DexEncodedMethod encodedMethod =
@@ -76,6 +102,20 @@
             .setCode(null)
             .setApiLevelForDefinition(minAPILevel)
             .build();
-    dexClass.getMethodCollection().addMethod(encodedMethod);
+    libClass.getMethodCollection().addMethod(encodedMethod);
+  }
+
+  private DexLibraryClass getLibraryClass(DexReference reference) {
+    DexClass dexClass = definitions.contextIndependentDefinitionFor(reference.getContextType());
+    if (dexClass == null || !dexClass.isLibraryClass()) {
+      // Consider just throwing an error.
+      reporter.warning(
+          "Desugared library: Cannot amend library reference "
+              + reference
+              + " because the holder is not a library class"
+              + (dexClass == null ? "(null)." : "."));
+      return null;
+    }
+    return dexClass.asLibraryClass();
   }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/humanspecification/AbstractMethodParser.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/humanspecification/AbstractMethodParser.java
deleted file mode 100644
index d63efb6..0000000
--- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/humanspecification/AbstractMethodParser.java
+++ /dev/null
@@ -1,81 +0,0 @@
-// Copyright (c) 2022, 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.desugaredlibrary.humanspecification;
-
-import com.android.tools.r8.dex.Constants;
-import com.android.tools.r8.errors.CompilationError;
-import com.android.tools.r8.graph.DexItemFactory;
-import com.android.tools.r8.graph.DexString;
-import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.utils.DescriptorUtils;
-import com.google.common.collect.ImmutableMap;
-import java.util.Map;
-
-/** Parse methods of the form: modifiers* returnType holder#name(arg0, ..., argN) */
-public abstract class AbstractMethodParser {
-
-  private static final String SEPARATORS = "\\s+|,\\s+|#|\\(|\\)";
-
-  private static final Map<String, Integer> modifiers =
-      ImmutableMap.<String, Integer>builder()
-          .put("public", Constants.ACC_PUBLIC)
-          .put("private", Constants.ACC_PRIVATE)
-          .put("protected", Constants.ACC_PROTECTED)
-          .put("final", Constants.ACC_FINAL)
-          .put("abstract", Constants.ACC_ABSTRACT)
-          .put("static", Constants.ACC_STATIC)
-          .build();
-
-  final DexItemFactory factory;
-
-  protected AbstractMethodParser(DexItemFactory factory) {
-    this.factory = factory;
-  }
-
-  // TODO(b/218755060): It would be nice to avoid the split regexp and use a nextToken()
-  //  method instead, then add a TraversalContinuation.
-  public void parseMethod(String signature) {
-    String[] tokens = signature.split(SEPARATORS);
-    if (tokens.length < 3) {
-      throw new CompilationError("Desugared library: cannot parse method " + signature);
-    }
-    methodStart();
-    int first = parseModifiers(tokens);
-    returnType(stringTypeToDexType(tokens[first]));
-    holderType(stringTypeToDexType(tokens[first + 1]));
-    methodName(factory.createString(tokens[first + 1 + 1]));
-    for (int i = first + 3; i < tokens.length; i++) {
-      argType(stringTypeToDexType(tokens[i]));
-    }
-    methodEnd();
-  }
-
-  private DexType stringTypeToDexType(String stringType) {
-    return factory.createType(DescriptorUtils.javaTypeToDescriptor(stringType));
-  }
-
-  private int parseModifiers(String[] split) {
-    int index = 0;
-    while (modifiers.containsKey(split[index])) {
-      modifier(modifiers.get(split[index]));
-      index++;
-    }
-    return index;
-  }
-
-  protected abstract void methodStart();
-
-  protected abstract void methodEnd();
-
-  protected abstract void returnType(DexType type);
-
-  protected abstract void argType(DexType type);
-
-  protected abstract void modifier(int access);
-
-  protected abstract void holderType(DexType type);
-
-  protected abstract void methodName(DexString name);
-}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/humanspecification/HumanDesugaredLibrarySpecificationParser.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/humanspecification/HumanDesugaredLibrarySpecificationParser.java
index 2eb550b..5936d8c 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/humanspecification/HumanDesugaredLibrarySpecificationParser.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/humanspecification/HumanDesugaredLibrarySpecificationParser.java
@@ -8,10 +8,13 @@
 import static com.android.tools.r8.ir.desugar.desugaredlibrary.DesugaredLibrarySpecificationParser.isHumanSpecification;
 
 import com.android.tools.r8.StringResource;
+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.DexType;
 import com.android.tools.r8.ir.desugar.desugaredlibrary.TopLevelFlagsBuilder;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.humanspecification.memberparser.HumanFieldParser;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.humanspecification.memberparser.HumanMethodParser;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.DescriptorUtils;
@@ -48,6 +51,8 @@
   static final String WRAPPER_CONVERSION_EXCLUDING_KEY = "wrapper_conversion_excluding";
   static final String CUSTOM_CONVERSION_KEY = "custom_conversion";
   static final String REWRITE_PREFIX_KEY = "rewrite_prefix";
+  static final String MAINTAIN_PREFIX_KEY = "maintain_prefix";
+  static final String RETARGET_STATIC_FIELD_KEY = "retarget_static_field";
   static final String RETARGET_METHOD_KEY = "retarget_method";
   static final String RETARGET_METHOD_EMULATED_DISPATCH_KEY =
       "retarget_method_with_emulated_dispatch";
@@ -57,11 +62,13 @@
   static final String DONT_RETARGET_KEY = "dont_retarget";
   static final String BACKPORT_KEY = "backport";
   static final String AMEND_LIBRARY_METHOD_KEY = "amend_library_method";
+  static final String AMEND_LIBRARY_FIELD_KEY = "amend_library_field";
   static final String SHRINKER_CONFIG_KEY = "shrinker_config";
   static final String SUPPORT_ALL_CALLBACKS_FROM_LIBRARY_KEY = "support_all_callbacks_from_library";
 
   private final DexItemFactory dexItemFactory;
   private final HumanMethodParser methodParser;
+  private final HumanFieldParser fieldParser;
   private final Reporter reporter;
   private final boolean libraryCompilation;
   private final int minAPILevel;
@@ -76,6 +83,7 @@
       int minAPILevel) {
     this.dexItemFactory = dexItemFactory;
     this.methodParser = new HumanMethodParser(dexItemFactory);
+    this.fieldParser = new HumanFieldParser(dexItemFactory);
     this.reporter = reporter;
     this.minAPILevel = minAPILevel;
     this.libraryCompilation = libraryCompilation;
@@ -235,6 +243,11 @@
         builder.putRewritePrefix(rewritePrefix.getKey(), rewritePrefix.getValue().getAsString());
       }
     }
+    if (jsonFlagSet.has(MAINTAIN_PREFIX_KEY)) {
+      for (JsonElement maintainPrefix : jsonFlagSet.get(MAINTAIN_PREFIX_KEY).getAsJsonArray()) {
+        builder.putMaintainPrefix(maintainPrefix.getAsString());
+      }
+    }
     if (jsonFlagSet.has(REWRITE_DERIVED_PREFIX_KEY)) {
       for (Map.Entry<String, JsonElement> prefixToMatch :
           jsonFlagSet.get(REWRITE_DERIVED_PREFIX_KEY).getAsJsonObject().entrySet()) {
@@ -245,6 +258,14 @@
         }
       }
     }
+    if (jsonFlagSet.has(RETARGET_STATIC_FIELD_KEY)) {
+      for (Map.Entry<String, JsonElement> retarget :
+          jsonFlagSet.get(RETARGET_STATIC_FIELD_KEY).getAsJsonObject().entrySet()) {
+        builder.retargetStaticField(
+            parseField(retarget.getKey()),
+            stringDescriptorToDexType(retarget.getValue().getAsString()));
+      }
+    }
     if (jsonFlagSet.has(RETARGET_METHOD_KEY)) {
       for (Map.Entry<String, JsonElement> retarget :
           jsonFlagSet.get(RETARGET_METHOD_KEY).getAsJsonObject().entrySet()) {
@@ -317,6 +338,13 @@
         builder.amendLibraryMethod(methodParser.getMethod(), methodParser.getFlags());
       }
     }
+    if (jsonFlagSet.has(AMEND_LIBRARY_FIELD_KEY)) {
+      JsonArray amendLibraryMember = jsonFlagSet.get(AMEND_LIBRARY_FIELD_KEY).getAsJsonArray();
+      for (JsonElement amend : amendLibraryMember) {
+        fieldParser.parseField(amend.getAsString());
+        builder.amendLibraryField(fieldParser.getField(), fieldParser.getFlags());
+      }
+    }
   }
 
   private Set<DexMethod> parseMethods(JsonArray array) {
@@ -332,6 +360,11 @@
     return methodParser.getMethod();
   }
 
+  private DexField parseField(String signature) {
+    fieldParser.parseField(signature);
+    return fieldParser.getField();
+  }
+
   private DexType stringDescriptorToDexType(String stringClass) {
     return dexItemFactory.createType(DescriptorUtils.javaTypeToDescriptor(stringClass));
   }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/humanspecification/HumanRewritingFlags.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/humanspecification/HumanRewritingFlags.java
index 9eb3ed7..b0c7920 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/humanspecification/HumanRewritingFlags.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/humanspecification/HumanRewritingFlags.java
@@ -4,8 +4,10 @@
 
 package com.android.tools.r8.ir.desugar.desugaredlibrary.humanspecification;
 
+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.FieldAccessFlags;
 import com.android.tools.r8.graph.MethodAccessFlags;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.utils.Reporter;
@@ -24,8 +26,10 @@
 public class HumanRewritingFlags {
 
   private final Map<String, String> rewritePrefix;
+  private final Set<String> maintainPrefix;
   private final Map<String, Map<String, String>> rewriteDerivedPrefix;
   private final Map<DexType, DexType> emulatedInterfaces;
+  private final Map<DexField, DexType> retargetStaticField;
   private final Map<DexMethod, DexType> retargetMethod;
   private final Map<DexMethod, DexType> retargetMethodEmulatedDispatch;
   private final Map<DexType, DexType> legacyBackport;
@@ -34,11 +38,14 @@
   private final Set<DexType> dontRetarget;
   private final Map<DexType, Set<DexMethod>> wrapperConversions;
   private final Map<DexMethod, MethodAccessFlags> amendLibraryMethod;
+  private final Map<DexField, FieldAccessFlags> amendLibraryField;
 
   HumanRewritingFlags(
       Map<String, String> rewritePrefix,
+      Set<String> maintainPrefix,
       Map<String, Map<String, String>> rewriteDerivedPrefix,
       Map<DexType, DexType> emulateLibraryInterface,
+      Map<DexField, DexType> retargetStaticField,
       Map<DexMethod, DexType> retargetMethod,
       Map<DexMethod, DexType> retargetMethodEmulatedDispatch,
       Map<DexType, DexType> legacyBackport,
@@ -46,10 +53,13 @@
       Set<DexMethod> dontRewriteInvocation,
       Set<DexType> dontRetarget,
       Map<DexType, Set<DexMethod>> wrapperConversion,
-      Map<DexMethod, MethodAccessFlags> amendLibraryMethod) {
+      Map<DexMethod, MethodAccessFlags> amendLibraryMethod,
+      Map<DexField, FieldAccessFlags> amendLibraryField) {
     this.rewritePrefix = rewritePrefix;
+    this.maintainPrefix = maintainPrefix;
     this.rewriteDerivedPrefix = rewriteDerivedPrefix;
     this.emulatedInterfaces = emulateLibraryInterface;
+    this.retargetStaticField = retargetStaticField;
     this.retargetMethod = retargetMethod;
     this.retargetMethodEmulatedDispatch = retargetMethodEmulatedDispatch;
     this.legacyBackport = legacyBackport;
@@ -58,11 +68,14 @@
     this.dontRetarget = dontRetarget;
     this.wrapperConversions = wrapperConversion;
     this.amendLibraryMethod = amendLibraryMethod;
+    this.amendLibraryField = amendLibraryField;
   }
 
   public static HumanRewritingFlags empty() {
     return new HumanRewritingFlags(
         ImmutableMap.of(),
+        ImmutableSet.of(),
+        ImmutableMap.of(),
         ImmutableMap.of(),
         ImmutableMap.of(),
         ImmutableMap.of(),
@@ -72,6 +85,7 @@
         ImmutableSet.of(),
         ImmutableSet.of(),
         ImmutableMap.of(),
+        ImmutableMap.of(),
         ImmutableMap.of());
   }
 
@@ -84,8 +98,10 @@
         reporter,
         origin,
         rewritePrefix,
+        maintainPrefix,
         rewriteDerivedPrefix,
         emulatedInterfaces,
+        retargetStaticField,
         retargetMethod,
         retargetMethodEmulatedDispatch,
         legacyBackport,
@@ -93,13 +109,18 @@
         dontRewriteInvocation,
         dontRetarget,
         wrapperConversions,
-        amendLibraryMethod);
+        amendLibraryMethod,
+        amendLibraryField);
   }
 
   public Map<String, String> getRewritePrefix() {
     return rewritePrefix;
   }
 
+  public Set<String> getMaintainPrefix() {
+    return maintainPrefix;
+  }
+
   public Map<String, Map<String, String>> getRewriteDerivedPrefix() {
     return rewriteDerivedPrefix;
   }
@@ -108,6 +129,10 @@
     return emulatedInterfaces;
   }
 
+  public Map<DexField, DexType> getRetargetStaticField() {
+    return retargetStaticField;
+  }
+
   public Map<DexMethod, DexType> getRetargetMethod() {
     return retargetMethod;
   }
@@ -140,11 +165,17 @@
     return amendLibraryMethod;
   }
 
+  public Map<DexField, FieldAccessFlags> getAmendLibraryField() {
+    return amendLibraryField;
+  }
+
   public boolean isEmpty() {
     return rewritePrefix.isEmpty()
         && rewriteDerivedPrefix.isEmpty()
         && emulatedInterfaces.isEmpty()
-        && retargetMethod.isEmpty();
+        && retargetMethod.isEmpty()
+        && retargetMethodEmulatedDispatch.isEmpty()
+        && retargetStaticField.isEmpty();
   }
 
   public static class Builder {
@@ -153,8 +184,10 @@
     private final Origin origin;
 
     private final Map<String, String> rewritePrefix;
+    private final Set<String> maintainPrefix;
     private final Map<String, Map<String, String>> rewriteDerivedPrefix;
     private final Map<DexType, DexType> emulatedInterfaces;
+    private final Map<DexField, DexType> retargetStaticField;
     private final Map<DexMethod, DexType> retargetMethod;
     private final Map<DexMethod, DexType> retargetMethodEmulatedDispatch;
     private final Map<DexType, DexType> legacyBackport;
@@ -163,21 +196,25 @@
     private final Set<DexType> dontRetarget;
     private final Map<DexType, Set<DexMethod>> wrapperConversions;
     private final Map<DexMethod, MethodAccessFlags> amendLibraryMethod;
+    private final Map<DexField, FieldAccessFlags> amendLibraryField;
 
     Builder(Reporter reporter, Origin origin) {
       this(
           reporter,
           origin,
           new HashMap<>(),
+          Sets.newIdentityHashSet(),
           new HashMap<>(),
           new IdentityHashMap<>(),
           new IdentityHashMap<>(),
           new IdentityHashMap<>(),
           new IdentityHashMap<>(),
           new IdentityHashMap<>(),
+          new IdentityHashMap<>(),
           Sets.newIdentityHashSet(),
           Sets.newIdentityHashSet(),
           new IdentityHashMap<>(),
+          new IdentityHashMap<>(),
           new IdentityHashMap<>());
     }
 
@@ -185,8 +222,10 @@
         Reporter reporter,
         Origin origin,
         Map<String, String> rewritePrefix,
+        Set<String> maintainPrefix,
         Map<String, Map<String, String>> rewriteDerivedPrefix,
         Map<DexType, DexType> emulateLibraryInterface,
+        Map<DexField, DexType> retargetStaticField,
         Map<DexMethod, DexType> retargetMethod,
         Map<DexMethod, DexType> retargetMethodEmulatedDispatch,
         Map<DexType, DexType> backportCoreLibraryMember,
@@ -194,12 +233,15 @@
         Set<DexMethod> dontRewriteInvocation,
         Set<DexType> dontRetargetLibMember,
         Map<DexType, Set<DexMethod>> wrapperConversions,
-        Map<DexMethod, MethodAccessFlags> amendLibrary) {
+        Map<DexMethod, MethodAccessFlags> amendLibraryMethod,
+        Map<DexField, FieldAccessFlags> amendLibraryField) {
       this.reporter = reporter;
       this.origin = origin;
       this.rewritePrefix = new HashMap<>(rewritePrefix);
+      this.maintainPrefix = Sets.newHashSet(maintainPrefix);
       this.rewriteDerivedPrefix = new HashMap<>(rewriteDerivedPrefix);
       this.emulatedInterfaces = new IdentityHashMap<>(emulateLibraryInterface);
+      this.retargetStaticField = new IdentityHashMap<>(retargetStaticField);
       this.retargetMethod = new IdentityHashMap<>(retargetMethod);
       this.retargetMethodEmulatedDispatch = new IdentityHashMap<>(retargetMethodEmulatedDispatch);
       this.legacyBackport = new IdentityHashMap<>(backportCoreLibraryMember);
@@ -209,7 +251,8 @@
       this.dontRetarget = Sets.newIdentityHashSet();
       this.dontRetarget.addAll(dontRetargetLibMember);
       this.wrapperConversions = new IdentityHashMap<>(wrapperConversions);
-      this.amendLibraryMethod = new IdentityHashMap<>(amendLibrary);
+      this.amendLibraryMethod = new IdentityHashMap<>(amendLibraryMethod);
+      this.amendLibraryField = new IdentityHashMap<>(amendLibraryField);
     }
 
     // Utility to set values.
@@ -237,6 +280,11 @@
       return this;
     }
 
+    public Builder putMaintainPrefix(String prefix) {
+      maintainPrefix.add(prefix);
+      return this;
+    }
+
     public Builder putRewriteDerivedPrefix(
         String prefixToMatch, String prefixToRewrite, String rewrittenPrefix) {
       Map<String, String> map =
@@ -285,6 +333,15 @@
       return this;
     }
 
+    public Builder retargetStaticField(DexField key, DexType rewrittenType) {
+      put(
+          retargetStaticField,
+          key,
+          rewrittenType,
+          HumanDesugaredLibrarySpecificationParser.RETARGET_STATIC_FIELD_KEY);
+      return this;
+    }
+
     public Builder retargetMethodEmulatedDispatch(DexMethod key, DexType rewrittenType) {
       put(
           retargetMethodEmulatedDispatch,
@@ -318,12 +375,19 @@
       return this;
     }
 
+    public Builder amendLibraryField(DexField member, FieldAccessFlags flags) {
+      amendLibraryField.put(member, flags);
+      return this;
+    }
+
     public HumanRewritingFlags build() {
       validate();
       return new HumanRewritingFlags(
           ImmutableMap.copyOf(rewritePrefix),
+          ImmutableSet.copyOf(maintainPrefix),
           ImmutableMap.copyOf(rewriteDerivedPrefix),
           ImmutableMap.copyOf(emulatedInterfaces),
+          ImmutableMap.copyOf(retargetStaticField),
           ImmutableMap.copyOf(retargetMethod),
           ImmutableMap.copyOf(retargetMethodEmulatedDispatch),
           ImmutableMap.copyOf(legacyBackport),
@@ -331,7 +395,8 @@
           ImmutableSet.copyOf(dontRewriteInvocation),
           ImmutableSet.copyOf(dontRetarget),
           ImmutableMap.copyOf(wrapperConversions),
-          ImmutableMap.copyOf(amendLibraryMethod));
+          ImmutableMap.copyOf(amendLibraryMethod),
+          ImmutableMap.copyOf(amendLibraryField));
     }
 
     private void validate() {
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/humanspecification/HumanTopLevelFlags.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/humanspecification/HumanTopLevelFlags.java
index 6f1f435..5383b69 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/humanspecification/HumanTopLevelFlags.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/humanspecification/HumanTopLevelFlags.java
@@ -47,7 +47,7 @@
 
   public static HumanTopLevelFlags testing() {
     return new HumanTopLevelFlags(
-        AndroidApiLevel.R, "unused", "testing", null, true, ImmutableList.of());
+        AndroidApiLevel.O, "unused", "testing", null, true, ImmutableList.of());
   }
 
   public static Builder builder() {
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/humanspecification/memberparser/AbstractFieldParser.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/humanspecification/memberparser/AbstractFieldParser.java
new file mode 100644
index 0000000..8062063
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/humanspecification/memberparser/AbstractFieldParser.java
@@ -0,0 +1,43 @@
+// Copyright (c) 2022, 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.desugaredlibrary.humanspecification.memberparser;
+
+import com.android.tools.r8.errors.CompilationError;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexString;
+import com.android.tools.r8.graph.DexType;
+
+/** Parse fields of the form: modifiers* fieldType holder#fieldName */
+public abstract class AbstractFieldParser extends AbstractMemberParser {
+
+  protected AbstractFieldParser(DexItemFactory factory) {
+    super(factory);
+  }
+
+  // TODO(b/218755060): It would be nice to avoid the split regexp and use a nextToken()
+  //  method instead, then add a TraversalContinuation.
+  public void parseField(String signature) {
+    String[] tokens = signature.split(SEPARATORS);
+    if (tokens.length < 3) {
+      throw new CompilationError("Desugared library: cannot parse field " + signature);
+    }
+    fieldStart();
+    int first = parseModifiers(tokens);
+    fieldType(stringTypeToDexType(tokens[first]));
+    holderType(stringTypeToDexType(tokens[first + 1]));
+    fieldName(factory.createString(tokens[first + 1 + 1]));
+    fieldEnd();
+  }
+
+  protected abstract void fieldStart();
+
+  protected abstract void fieldEnd();
+
+  protected abstract void fieldType(DexType type);
+
+  protected abstract void holderType(DexType type);
+
+  protected abstract void fieldName(DexString name);
+}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/humanspecification/memberparser/AbstractMemberParser.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/humanspecification/memberparser/AbstractMemberParser.java
new file mode 100644
index 0000000..99475b2
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/humanspecification/memberparser/AbstractMemberParser.java
@@ -0,0 +1,48 @@
+// Copyright (c) 2022, 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.desugaredlibrary.humanspecification.memberparser;
+
+import com.android.tools.r8.dex.Constants;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.utils.DescriptorUtils;
+import com.google.common.collect.ImmutableMap;
+import java.util.Map;
+
+public abstract class AbstractMemberParser {
+
+  static final String SEPARATORS = "\\s+|,\\s+|#|\\(|\\)";
+
+  static final Map<String, Integer> MODIFIERS =
+      ImmutableMap.<String, Integer>builder()
+          .put("public", Constants.ACC_PUBLIC)
+          .put("private", Constants.ACC_PRIVATE)
+          .put("protected", Constants.ACC_PROTECTED)
+          .put("final", Constants.ACC_FINAL)
+          .put("abstract", Constants.ACC_ABSTRACT)
+          .put("static", Constants.ACC_STATIC)
+          .build();
+
+  final DexItemFactory factory;
+
+  protected AbstractMemberParser(DexItemFactory factory) {
+    this.factory = factory;
+  }
+
+  DexType stringTypeToDexType(String stringType) {
+    return factory.createType(DescriptorUtils.javaTypeToDescriptor(stringType));
+  }
+
+  int parseModifiers(String[] split) {
+    int index = 0;
+    while (MODIFIERS.containsKey(split[index])) {
+      modifier(MODIFIERS.get(split[index]));
+      index++;
+    }
+    return index;
+  }
+
+  protected abstract void modifier(int access);
+}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/humanspecification/memberparser/AbstractMethodParser.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/humanspecification/memberparser/AbstractMethodParser.java
new file mode 100644
index 0000000..1afff88
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/humanspecification/memberparser/AbstractMethodParser.java
@@ -0,0 +1,48 @@
+// Copyright (c) 2022, 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.desugaredlibrary.humanspecification.memberparser;
+
+import com.android.tools.r8.errors.CompilationError;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexString;
+import com.android.tools.r8.graph.DexType;
+
+/** Parse methods of the form: modifiers* returnType holder#name(arg0, ..., argN) */
+public abstract class AbstractMethodParser extends AbstractMemberParser {
+
+  protected AbstractMethodParser(DexItemFactory factory) {
+    super(factory);
+  }
+
+  // TODO(b/218755060): It would be nice to avoid the split regexp and use a nextToken()
+  //  method instead, then add a TraversalContinuation.
+  public void parseMethod(String signature) {
+    String[] tokens = signature.split(SEPARATORS);
+    if (tokens.length < 3) {
+      throw new CompilationError("Desugared library: cannot parse method " + signature);
+    }
+    methodStart();
+    int first = parseModifiers(tokens);
+    returnType(stringTypeToDexType(tokens[first]));
+    holderType(stringTypeToDexType(tokens[first + 1]));
+    methodName(factory.createString(tokens[first + 1 + 1]));
+    for (int i = first + 3; i < tokens.length; i++) {
+      argType(stringTypeToDexType(tokens[i]));
+    }
+    methodEnd();
+  }
+
+  protected abstract void methodStart();
+
+  protected abstract void methodEnd();
+
+  protected abstract void returnType(DexType type);
+
+  protected abstract void argType(DexType type);
+
+  protected abstract void holderType(DexType type);
+
+  protected abstract void methodName(DexString name);
+}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/humanspecification/memberparser/HumanFieldParser.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/humanspecification/memberparser/HumanFieldParser.java
new file mode 100644
index 0000000..9a5a44d
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/humanspecification/memberparser/HumanFieldParser.java
@@ -0,0 +1,81 @@
+// Copyright (c) 2022, 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.desugaredlibrary.humanspecification.memberparser;
+
+import com.android.tools.r8.graph.DexField;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexString;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.FieldAccessFlags;
+
+public class HumanFieldParser extends AbstractFieldParser {
+
+  // Values accumulated while parsing.
+  private FieldAccessFlags.Builder flagBuilder;
+  private DexType fieldType;
+  private DexType holder;
+  private DexString fieldName;
+  // Resulting values.
+  private DexField field;
+  private FieldAccessFlags flags;
+
+  public HumanFieldParser(DexItemFactory factory) {
+    super(factory);
+  }
+
+  private boolean parsingFinished() {
+    return field != null;
+  }
+
+  public DexField getField() {
+    assert parsingFinished();
+    return field;
+  }
+
+  public FieldAccessFlags getFlags() {
+    assert parsingFinished();
+    return flags;
+  }
+
+  @Override
+  protected void modifier(int access) {
+    assert !parsingFinished();
+    flagBuilder.set(access);
+  }
+
+  @Override
+  protected void holderType(DexType type) {
+    assert !parsingFinished();
+    holder = type;
+  }
+
+  @Override
+  protected void fieldName(DexString name) {
+    assert !parsingFinished();
+    fieldName = name;
+  }
+
+  @Override
+  protected void fieldStart() {
+    flagBuilder = FieldAccessFlags.builder();
+    fieldType = null;
+    holder = null;
+    fieldName = null;
+    field = null;
+    flags = null;
+  }
+
+  @Override
+  protected void fieldEnd() {
+    field = factory.createField(holder, fieldType, fieldName);
+    flags = flagBuilder.build();
+  }
+
+  @Override
+  protected void fieldType(DexType type) {
+    assert !parsingFinished();
+    fieldType = type;
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/humanspecification/HumanMethodParser.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/humanspecification/memberparser/HumanMethodParser.java
similarity index 96%
rename from src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/humanspecification/HumanMethodParser.java
rename to src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/humanspecification/memberparser/HumanMethodParser.java
index b4bac60..d78267b 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/humanspecification/HumanMethodParser.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/humanspecification/memberparser/HumanMethodParser.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.ir.desugar.desugaredlibrary.humanspecification;
+package com.android.tools.r8.ir.desugar.desugaredlibrary.humanspecification.memberparser;
 
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexMethod;
@@ -25,7 +25,7 @@
   private DexMethod method;
   private MethodAccessFlags flags;
 
-  protected HumanMethodParser(DexItemFactory factory) {
+  public HumanMethodParser(DexItemFactory factory) {
     super(factory);
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/machinespecification/MachineDesugaredLibrarySpecification.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/machinespecification/MachineDesugaredLibrarySpecification.java
index 1f52b3d..d974c56 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/machinespecification/MachineDesugaredLibrarySpecification.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/machinespecification/MachineDesugaredLibrarySpecification.java
@@ -4,9 +4,11 @@
 
 package com.android.tools.r8.ir.desugar.desugaredlibrary.machinespecification;
 
+import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexReference;
 import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.FieldAccessFlags;
 import com.android.tools.r8.graph.MethodAccessFlags;
 import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.SemanticVersion;
@@ -82,10 +84,18 @@
     return rewritingFlags.getRewriteType();
   }
 
+  public Set<DexType> getMaintainType() {
+    return rewritingFlags.getMaintainType();
+  }
+
   public Map<DexType, DexType> getRewriteDerivedTypeOnly() {
     return rewritingFlags.getRewriteDerivedTypeOnly();
   }
 
+  public Map<DexField, DexField> getStaticFieldRetarget() {
+    return rewritingFlags.getStaticFieldRetarget();
+  }
+
   public Map<DexMethod, DexMethod> getStaticRetarget() {
     return rewritingFlags.getStaticRetarget();
   }
@@ -143,6 +153,10 @@
     return rewritingFlags.getAmendLibraryMethod();
   }
 
+  public Map<DexField, FieldAccessFlags> getAmendLibraryFields() {
+    return rewritingFlags.getAmendLibraryField();
+  }
+
   public boolean hasRetargeting() {
     return rewritingFlags.hasRetargeting();
   }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/machinespecification/MachineRewritingFlags.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/machinespecification/MachineRewritingFlags.java
index 00a4747..dcaa744 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/machinespecification/MachineRewritingFlags.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/machinespecification/MachineRewritingFlags.java
@@ -4,8 +4,10 @@
 
 package com.android.tools.r8.ir.desugar.desugaredlibrary.machinespecification;
 
+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.FieldAccessFlags;
 import com.android.tools.r8.graph.MethodAccessFlags;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
@@ -25,7 +27,9 @@
 
   MachineRewritingFlags(
       Map<DexType, DexType> rewriteType,
+      Set<DexType> maintainType,
       Map<DexType, DexType> rewriteDerivedTypeOnly,
+      Map<DexField, DexField> staticFieldRetarget,
       Map<DexMethod, DexMethod> staticRetarget,
       Map<DexMethod, DexMethod> nonEmulatedVirtualRetarget,
       Map<DexMethod, EmulatedDispatchMethodDescriptor> emulatedVirtualRetarget,
@@ -35,9 +39,12 @@
       Map<DexType, DexType> legacyBackport,
       Set<DexType> dontRetarget,
       Map<DexType, CustomConversionDescriptor> customConversions,
-      Map<DexMethod, MethodAccessFlags> amendLibraryMethods) {
+      Map<DexMethod, MethodAccessFlags> amendLibraryMethods,
+      Map<DexField, FieldAccessFlags> amendLibraryFields) {
     this.rewriteType = rewriteType;
+    this.maintainType = maintainType;
     this.rewriteDerivedTypeOnly = rewriteDerivedTypeOnly;
+    this.staticFieldRetarget = staticFieldRetarget;
     this.staticRetarget = staticRetarget;
     this.nonEmulatedVirtualRetarget = nonEmulatedVirtualRetarget;
     this.emulatedVirtualRetarget = emulatedVirtualRetarget;
@@ -49,14 +56,20 @@
     this.dontRetarget = dontRetarget;
     this.customConversions = customConversions;
     this.amendLibraryMethod = amendLibraryMethods;
+    this.amendLibraryField = amendLibraryFields;
   }
 
   // Rewrites all the references to the keys as well as synthetic types derived from any key.
   private final Map<DexType, DexType> rewriteType;
+  // Maintains the references in the desugared library dex file.
+  private final Set<DexType> maintainType;
   // Rewrites only synthetic types derived from any key.
   private final Map<DexType, DexType> rewriteDerivedTypeOnly;
 
-  // Static methods to retarget, duplicated to library boundaries.
+  // Fields to retarget.
+  private final Map<DexField, DexField> staticFieldRetarget;
+
+  // Static methods to retarget.
   private final Map<DexMethod, DexMethod> staticRetarget;
 
   // Virtual methods to retarget, which are guaranteed not to require emulated dispatch.
@@ -83,15 +96,24 @@
   private final Set<DexType> dontRetarget;
   private final Map<DexType, CustomConversionDescriptor> customConversions;
   private final Map<DexMethod, MethodAccessFlags> amendLibraryMethod;
+  private final Map<DexField, FieldAccessFlags> amendLibraryField;
 
   public Map<DexType, DexType> getRewriteType() {
     return rewriteType;
   }
 
+  public Set<DexType> getMaintainType() {
+    return maintainType;
+  }
+
   public Map<DexType, DexType> getRewriteDerivedTypeOnly() {
     return rewriteDerivedTypeOnly;
   }
 
+  public Map<DexField, DexField> getStaticFieldRetarget() {
+    return staticFieldRetarget;
+  }
+
   public Map<DexMethod, DexMethod> getStaticRetarget() {
     return staticRetarget;
   }
@@ -146,10 +168,15 @@
     return amendLibraryMethod;
   }
 
+  public Map<DexField, FieldAccessFlags> getAmendLibraryField() {
+    return amendLibraryField;
+  }
+
   public boolean hasRetargeting() {
     return !staticRetarget.isEmpty()
         || !nonEmulatedVirtualRetarget.isEmpty()
-        || !emulatedVirtualRetarget.isEmpty();
+        || !emulatedVirtualRetarget.isEmpty()
+        || !staticFieldRetarget.isEmpty();
   }
 
   public boolean isEmulatedInterfaceRewrittenType(DexType type) {
@@ -174,7 +201,10 @@
     Builder() {}
 
     private final Map<DexType, DexType> rewriteType = new IdentityHashMap<>();
+    private final ImmutableSet.Builder<DexType> maintainType = ImmutableSet.builder();
     private final Map<DexType, DexType> rewriteDerivedTypeOnly = new IdentityHashMap<>();
+    private final ImmutableMap.Builder<DexField, DexField> staticFieldRetarget =
+        ImmutableMap.builder();
     private final ImmutableMap.Builder<DexMethod, DexMethod> staticRetarget =
         ImmutableMap.builder();
     private final ImmutableMap.Builder<DexMethod, DexMethod> nonEmulatedVirtualRetarget =
@@ -192,6 +222,8 @@
         ImmutableMap.builder();
     private final ImmutableMap.Builder<DexMethod, MethodAccessFlags> amendLibraryMethod =
         ImmutableMap.builder();
+    private final ImmutableMap.Builder<DexField, FieldAccessFlags> amendLibraryField =
+        ImmutableMap.builder();
 
     public void rewriteType(DexType src, DexType target) {
       assert src != null;
@@ -201,10 +233,19 @@
       rewriteType.put(src, target);
     }
 
+    public void maintainType(DexType type) {
+      assert type != null;
+      maintainType.add(type);
+    }
+
     public void rewriteDerivedTypeOnly(DexType src, DexType target) {
       rewriteDerivedTypeOnly.put(src, target);
     }
 
+    public void putStaticFieldRetarget(DexField src, DexField dest) {
+      staticFieldRetarget.put(src, dest);
+    }
+
     public void putStaticRetarget(DexMethod src, DexMethod dest) {
       staticRetarget.put(src, dest);
     }
@@ -245,6 +286,10 @@
       amendLibraryMethod.put(missingReference, flags);
     }
 
+    public void amendLibraryField(DexField missingReference, FieldAccessFlags flags) {
+      amendLibraryField.put(missingReference, flags);
+    }
+
     public DexType getRewrittenType(DexType type) {
       return rewriteType.get(type);
     }
@@ -252,7 +297,9 @@
     public MachineRewritingFlags build() {
       return new MachineRewritingFlags(
           rewriteType,
+          maintainType.build(),
           rewriteDerivedTypeOnly,
+          staticFieldRetarget.build(),
           staticRetarget.build(),
           nonEmulatedVirtualRetarget.build(),
           emulatedVirtualRetarget.build(),
@@ -262,7 +309,8 @@
           legacyBackport.build(),
           dontRetarget.build(),
           customConversions.build(),
-          amendLibraryMethod.build());
+          amendLibraryMethod.build(),
+          amendLibraryField.build());
     }
   }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/retargeter/DesugaredLibraryRetargeter.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/retargeter/DesugaredLibraryRetargeter.java
index a4b984f..a929fb9 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/retargeter/DesugaredLibraryRetargeter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/retargeter/DesugaredLibraryRetargeter.java
@@ -6,12 +6,15 @@
 
 import static com.android.tools.r8.ir.desugar.desugaredlibrary.retargeter.DesugaredLibraryRetargeter.InvokeRetargetingResult.NO_REWRITING;
 
+import com.android.tools.r8.cf.code.CfFieldInstruction;
 import com.android.tools.r8.cf.code.CfInstruction;
 import com.android.tools.r8.cf.code.CfInvoke;
 import com.android.tools.r8.contexts.CompilationContext.MethodProcessingContext;
 import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexClassAndMethod;
+import com.android.tools.r8.graph.DexEncodedField;
+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.MethodResolutionResult;
@@ -26,6 +29,7 @@
 import com.android.tools.r8.ir.desugar.desugaredlibrary.retargeter.DesugaredLibraryRetargeterSynthesizerEventConsumer.DesugaredLibraryRetargeterInstructionEventConsumer;
 import java.util.Collection;
 import java.util.Collections;
+import java.util.List;
 import java.util.Map;
 import java.util.function.Consumer;
 import java.util.function.Function;
@@ -36,6 +40,7 @@
   private final AppView<?> appView;
   private final DesugaredLibraryRetargeterSyntheticHelper syntheticHelper;
 
+  private final Map<DexField, DexField> staticFieldRetarget;
   private final Map<DexMethod, DexMethod> staticRetarget;
   private final Map<DexMethod, DexMethod> nonEmulatedVirtualRetarget;
   private final Map<DexMethod, EmulatedDispatchMethodDescriptor> emulatedVirtualRetarget;
@@ -45,6 +50,7 @@
     this.syntheticHelper = new DesugaredLibraryRetargeterSyntheticHelper(appView);
     MachineDesugaredLibrarySpecification specification =
         appView.options().machineDesugaredLibrarySpecification;
+    staticFieldRetarget = specification.getStaticFieldRetarget();
     staticRetarget = specification.getStaticRetarget();
     nonEmulatedVirtualRetarget = specification.getNonEmulatedVirtualRetarget();
     emulatedVirtualRetarget = specification.getEmulatedVirtualRetarget();
@@ -67,20 +73,53 @@
       MethodProcessingContext methodProcessingContext,
       CfInstructionDesugaringCollection desugaringCollection,
       DexItemFactory dexItemFactory) {
-    InvokeRetargetingResult invokeRetargetingResult = computeNewInvokeTarget(instruction, context);
-
-    if (!invokeRetargetingResult.hasNewInvokeTarget()) {
-      return null;
+    if (instruction.isFieldInstruction() && needsDesugaring(instruction, context)) {
+      return desugarFieldInstruction(instruction.asFieldInstruction(), context);
+    } else if (instruction.isInvoke() && needsDesugaring(instruction, context)) {
+      return desugarInvoke(instruction.asInvoke(), eventConsumer, context);
     }
+    return null;
+  }
 
+  private Collection<CfInstruction> desugarFieldInstruction(
+      CfFieldInstruction fieldInstruction, ProgramMethod context) {
+    DexField fieldRetarget = fieldRetarget(fieldInstruction, context);
+    assert fieldRetarget != null;
+    assert fieldInstruction.isStaticFieldGet() || fieldInstruction.isStaticFieldPut();
+    return Collections.singletonList(fieldInstruction.createWithField(fieldRetarget));
+  }
+
+  private List<CfInstruction> desugarInvoke(
+      CfInvoke invoke, CfInstructionDesugaringEventConsumer eventConsumer, ProgramMethod context) {
+    InvokeRetargetingResult invokeRetargetingResult = computeNewInvokeTarget(invoke, context);
+    assert invokeRetargetingResult.hasNewInvokeTarget();
     DexMethod newInvokeTarget = invokeRetargetingResult.getNewInvokeTarget(eventConsumer);
     return Collections.singletonList(
-        new CfInvoke(Opcodes.INVOKESTATIC, newInvokeTarget, instruction.asInvoke().isInterface()));
+        new CfInvoke(Opcodes.INVOKESTATIC, newInvokeTarget, invoke.isInterface()));
   }
 
   @Override
   public boolean needsDesugaring(CfInstruction instruction, ProgramMethod context) {
-    return computeNewInvokeTarget(instruction, context).hasNewInvokeTarget();
+    if (instruction.isFieldInstruction()) {
+      return fieldRetarget(instruction.asFieldInstruction(), context) != null;
+    } else if (instruction.isInvoke()) {
+      return computeNewInvokeTarget(instruction.asInvoke(), context).hasNewInvokeTarget();
+    }
+    return false;
+  }
+
+  private DexField fieldRetarget(CfFieldInstruction fieldInstruction, ProgramMethod context) {
+    DexEncodedField resolvedField =
+        appView
+            .appInfoForDesugaring()
+            .resolveField(fieldInstruction.getField(), context)
+            .getResolvedField();
+    if (resolvedField != null) {
+      assert resolvedField.isStatic()
+          || !staticFieldRetarget.containsKey(resolvedField.getReference());
+      return staticFieldRetarget.get(resolvedField.getReference());
+    }
+    return null;
   }
 
   InvokeRetargetingResult ensureInvokeRetargetingResult(DexMethod retarget) {
@@ -124,10 +163,7 @@
   }
 
   private InvokeRetargetingResult computeNewInvokeTarget(
-      CfInstruction instruction, ProgramMethod context) {
-    if (!instruction.isInvoke()) {
-      return NO_REWRITING;
-    }
+      CfInvoke instruction, ProgramMethod context) {
     if (appView
         .options()
         .machineDesugaredLibrarySpecification
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/specificationconversion/HumanToMachinePrefixConverter.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/specificationconversion/HumanToMachinePrefixConverter.java
index 87c03e3..78fc59f 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/specificationconversion/HumanToMachinePrefixConverter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/specificationconversion/HumanToMachinePrefixConverter.java
@@ -13,6 +13,7 @@
 import com.android.tools.r8.ir.desugar.desugaredlibrary.machinespecification.MachineRewritingFlags;
 import com.android.tools.r8.utils.DescriptorUtils;
 import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Sets;
 import java.util.Map;
 import java.util.Set;
@@ -25,6 +26,7 @@
   private final String synthesizedPrefix;
   private final boolean libraryCompilation;
   private final Map<DexString, DexString> descriptorPrefix;
+  private final Set<DexString> descriptorMaintainPrefix;
   private final Map<DexString, Map<DexString, DexString>> descriptorDifferentPrefix;
   private final Set<DexString> usedPrefix = Sets.newIdentityHashSet();
 
@@ -38,6 +40,7 @@
     this.synthesizedPrefix = humanSpec.getSynthesizedLibraryClassesPackagePrefix();
     this.libraryCompilation = humanSpec.isLibraryCompilation();
     this.descriptorPrefix = convertRewritePrefix(rewritingFlags.getRewritePrefix());
+    this.descriptorMaintainPrefix = convertMaintainPrefix(rewritingFlags.getMaintainPrefix());
     this.descriptorDifferentPrefix =
         convertRewriteDifferentPrefix(rewritingFlags.getRewriteDerivedPrefix());
   }
@@ -56,6 +59,7 @@
   private void warnIfUnusedPrefix(BiConsumer<String, Set<DexString>> warnConsumer) {
     Set<DexString> prefixes = Sets.newIdentityHashSet();
     prefixes.addAll(descriptorPrefix.keySet());
+    prefixes.addAll(descriptorMaintainPrefix);
     prefixes.addAll(descriptorDifferentPrefix.keySet());
     prefixes.removeAll(usedPrefix);
     warnConsumer.accept("The following prefixes do not match any type: ", prefixes);
@@ -99,6 +103,7 @@
 
   private void registerClassType(DexType type) {
     registerType(type);
+    registerMaintainType(type);
     registerDifferentType(type);
   }
 
@@ -109,6 +114,15 @@
     }
   }
 
+  private void registerMaintainType(DexType type) {
+    DexString prefix = prefixMatching(type, descriptorMaintainPrefix);
+    if (prefix == null) {
+      return;
+    }
+    builder.maintainType(type);
+    usedPrefix.add(prefix);
+  }
+
   private void registerDifferentType(DexType type) {
     DexString prefix = prefixMatching(type, descriptorDifferentPrefix.keySet());
     if (prefix == null) {
@@ -160,6 +174,14 @@
     return mapBuilder.build();
   }
 
+  private ImmutableSet<DexString> convertMaintainPrefix(Set<String> maintainPrefix) {
+    ImmutableSet.Builder<DexString> builder = ImmutableSet.builder();
+    for (String prefix : maintainPrefix) {
+      builder.add(toDescriptorPrefix(prefix));
+    }
+    return builder.build();
+  }
+
   private ImmutableMap<DexString, DexString> convertRewritePrefix(
       Map<String, String> rewritePrefix) {
     ImmutableMap.Builder<DexString, DexString> mapBuilder = ImmutableMap.builder();
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/specificationconversion/HumanToMachineRetargetConverter.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/specificationconversion/HumanToMachineRetargetConverter.java
index f64a3d0..56af1fc 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/specificationconversion/HumanToMachineRetargetConverter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/specificationconversion/HumanToMachineRetargetConverter.java
@@ -6,7 +6,9 @@
 
 import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
 import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexEncodedField;
 import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexProto;
 import com.android.tools.r8.graph.DexReference;
@@ -26,7 +28,7 @@
 public class HumanToMachineRetargetConverter {
 
   private final AppInfoWithClassHierarchy appInfo;
-  private final Set<DexMethod> missingMethods = Sets.newIdentityHashSet();
+  private final Set<DexReference> missingReferences = Sets.newIdentityHashSet();
 
   public HumanToMachineRetargetConverter(AppInfoWithClassHierarchy appInfo) {
     this.appInfo = appInfo;
@@ -37,6 +39,9 @@
       MachineRewritingFlags.Builder builder,
       BiConsumer<String, Set<? extends DexReference>> warnConsumer) {
     rewritingFlags
+        .getRetargetStaticField()
+        .forEach((field, type) -> convertRetargetField(builder, field, type));
+    rewritingFlags
         .getRetargetMethod()
         .forEach((method, type) -> convertRetargetMethod(builder, method, type));
     rewritingFlags
@@ -44,7 +49,19 @@
         .forEach(
             (method, type) ->
                 convertRetargetMethodEmulatedDispatch(builder, rewritingFlags, method, type));
-    warnConsumer.accept("Cannot retarget missing methods: ", missingMethods);
+    warnConsumer.accept("Cannot retarget missing references: ", missingReferences);
+  }
+
+  private void convertRetargetField(
+      MachineRewritingFlags.Builder builder, DexField field, DexType type) {
+    DexClass holder = appInfo.definitionFor(field.holder);
+    DexEncodedField foundField = holder.lookupField(field);
+    if (foundField == null) {
+      missingReferences.add(field);
+      return;
+    }
+    builder.putStaticFieldRetarget(
+        field, appInfo.dexItemFactory().createField(type, field.type, field.name));
   }
 
   private void convertRetargetMethodEmulatedDispatch(
@@ -55,7 +72,7 @@
     DexClass holder = appInfo.definitionFor(method.holder);
     DexEncodedMethod foundMethod = holder.lookupMethod(method);
     if (foundMethod == null) {
-      missingMethods.add(method);
+      missingReferences.add(method);
       return;
     }
     if (foundMethod.isStatic()) {
@@ -83,7 +100,7 @@
     DexClass holder = appInfo.definitionFor(method.holder);
     DexEncodedMethod foundMethod = holder.lookupMethod(method);
     if (foundMethod == null) {
-      missingMethods.add(method);
+      missingReferences.add(method);
       return;
     }
     if (foundMethod.isStatic()) {
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/specificationconversion/HumanToMachineSpecificationConverter.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/specificationconversion/HumanToMachineSpecificationConverter.java
index 5ea225d..0cec776 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/specificationconversion/HumanToMachineSpecificationConverter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/specificationconversion/HumanToMachineSpecificationConverter.java
@@ -86,8 +86,13 @@
     HumanRewritingFlags rewritingFlags = humanSpec.getRewritingFlags();
     MachineRewritingFlags.Builder builder = MachineRewritingFlags.builder();
     DesugaredLibraryAmender.run(
-        rewritingFlags.getAmendLibraryMethod(), appInfo, reporter, ComputedApiLevel.unknown());
+        rewritingFlags.getAmendLibraryMethod(),
+        rewritingFlags.getAmendLibraryField(),
+        appInfo,
+        reporter,
+        ComputedApiLevel.unknown());
     rewritingFlags.getAmendLibraryMethod().forEach(builder::amendLibraryMethod);
+    rewritingFlags.getAmendLibraryField().forEach(builder::amendLibraryField);
     new HumanToMachineRetargetConverter(appInfo)
         .convertRetargetFlags(rewritingFlags, builder, this::warnMissingReferences);
     new HumanToMachineEmulatedInterfaceConverter(appInfo)
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 b1c59bc..4446275 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,6 +24,7 @@
 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.LookupMethodTarget;
 import com.android.tools.r8.graph.MethodAccessFlags;
 import com.android.tools.r8.graph.MethodResolutionResult;
 import com.android.tools.r8.graph.ProgramMethod;
@@ -717,8 +718,9 @@
       }
     }
     assert resolutionResult.isSuccessfulMemberResolutionResult();
-    DexClassAndMethod virtualDispatchTarget =
+    LookupMethodTarget lookupMethodTarget =
         resolutionResult.lookupVirtualDispatchTarget(clazz, appInfo);
+    DexClassAndMethod virtualDispatchTarget = lookupMethodTarget.getTarget();
     assert virtualDispatchTarget != null;
 
     // If resolution targets a default interface method, forward it.
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 dc739bb..3919202 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
@@ -98,6 +98,13 @@
     if (isEmulatedInterface(clazz.type)) {
       return true;
     }
+    if (appView
+        .options()
+        .machineDesugaredLibrarySpecification
+        .getMaintainType()
+        .contains(clazz.type)) {
+      return true;
+    }
     return appView.typeRewriter.hasRewrittenType(clazz.type, appView);
   }
 
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 2ae7d7d..778ed23 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
@@ -713,7 +713,10 @@
       DexClass clazz, DexMethod invokedMethod, ProgramMethod context) {
     DexClassAndMethod superTarget =
         appView.appInfoForDesugaring().lookupSuperTarget(invokedMethod, context);
-    if (clazz.isInterface() && appView.typeRewriter.hasRewrittenType(clazz.type, appView)) {
+    if (clazz.isInterface()
+        && clazz.isLibraryClass()
+        && helper.isInDesugaredLibrary(clazz)
+        && !helper.isEmulatedInterface(clazz.type)) {
       if (superTarget != null && superTarget.getDefinition().isDefaultMethod()) {
         DexClass holder = superTarget.getHolder();
         if (holder.isLibraryClass() && holder.isInterface()) {
diff --git a/src/main/java/com/android/tools/r8/optimize/interfaces/collection/DefaultOpenClosedInterfacesCollection.java b/src/main/java/com/android/tools/r8/optimize/interfaces/collection/DefaultOpenClosedInterfacesCollection.java
index df264e4..f8aeb81 100644
--- a/src/main/java/com/android/tools/r8/optimize/interfaces/collection/DefaultOpenClosedInterfacesCollection.java
+++ b/src/main/java/com/android/tools/r8/optimize/interfaces/collection/DefaultOpenClosedInterfacesCollection.java
@@ -22,7 +22,8 @@
 
   @Override
   public boolean isDefinitelyClosed(DexClass clazz) {
-    return false;
+    // TODO(b/214496607): Should return false.
+    return true;
   }
 
   @Override
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 8b8e13b..eac02d2 100644
--- a/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
+++ b/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
@@ -36,6 +36,7 @@
 import com.android.tools.r8.graph.GraphLens;
 import com.android.tools.r8.graph.GraphLens.NonIdentityGraphLens;
 import com.android.tools.r8.graph.InstantiatedSubTypeInfo;
+import com.android.tools.r8.graph.LookupMethodTarget;
 import com.android.tools.r8.graph.LookupResult.LookupResultSuccess;
 import com.android.tools.r8.graph.LookupTarget;
 import com.android.tools.r8.graph.MethodAccessInfoCollection;
@@ -1467,17 +1468,17 @@
     if (receiverLowerBoundType != null
         && receiverLowerBoundType.getClassType() == refinedReceiverType) {
       if (refinedReceiverClass.isProgramClass()) {
-        DexClassAndMethod clazzAndMethod =
+        LookupMethodTarget methodTarget =
             resolution.lookupVirtualDispatchTarget(refinedReceiverClass.asProgramClass(), this);
-        if (clazzAndMethod == null
-            || (clazzAndMethod.isProgramMethod()
+        if (methodTarget == null
+            || (methodTarget.getTarget().isProgramMethod()
                 && !getKeepInfo()
-                    .getMethodInfo(clazzAndMethod.asProgramMethod())
+                    .getMethodInfo(methodTarget.getTarget().asProgramMethod())
                     .isOptimizationAllowed(options()))) {
           // TODO(b/150640456): We should maybe only consider program methods.
           return DexEncodedMethod.SENTINEL;
         }
-        return clazzAndMethod.getDefinition();
+        return methodTarget.getDefinition();
       } else {
         // TODO(b/150640456): We should maybe only consider program methods.
         // If we resolved to a method on the refined receiver in the library, then we report the
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 c7917ce..7d54b09 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -66,6 +66,7 @@
 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.LookupMethodTarget;
 import com.android.tools.r8.graph.LookupResult;
 import com.android.tools.r8.graph.LookupTarget;
 import com.android.tools.r8.graph.MethodAccessInfoCollection;
@@ -3277,20 +3278,25 @@
   private void markVirtualDispatchTargetAsLive(
       LookupTarget target, Function<ProgramMethod, KeepReasonWitness> reason) {
     target.accept(
-        method -> markVirtualDispatchTargetAsLive(method, reason),
-        lambda -> markVirtualDispatchTargetAsLive(lambda, reason));
+        method -> markVirtualDispatchMethodTargetAsLive(method, reason),
+        lambda -> markVirtualDispatchLambdaTargetAsLive(lambda, reason));
     analyses.forEach(analysis -> analysis.notifyMarkVirtualDispatchTargetAsLive(target, workList));
   }
 
-  private void markVirtualDispatchTargetAsLive(
-      DexClassAndMethod target, Function<ProgramMethod, KeepReasonWitness> reason) {
-    ProgramMethod programMethod = target.asProgramMethod();
+  private void markVirtualDispatchMethodTargetAsLive(
+      LookupMethodTarget target, Function<ProgramMethod, KeepReasonWitness> reason) {
+    ProgramMethod programMethod = target.getTarget().asProgramMethod();
     if (programMethod != null && !programMethod.getDefinition().isAbstract()) {
-      markVirtualMethodAsLive(programMethod, reason.apply(programMethod));
+      KeepReasonWitness appliedReason = reason.apply(programMethod);
+      markVirtualMethodAsLive(programMethod, appliedReason);
+      DexClassAndMethod accessOverride = target.getAccessOverride();
+      if (accessOverride != null && accessOverride.isProgramMethod()) {
+        markMethodAsTargeted(accessOverride.asProgramMethod(), appliedReason);
+      }
     }
   }
 
-  private void markVirtualDispatchTargetAsLive(
+  private void markVirtualDispatchLambdaTargetAsLive(
       LookupLambdaTarget target, Function<ProgramMethod, KeepReasonWitness> reason) {
     ProgramMethod implementationMethod = target.getImplementationMethod().asProgramMethod();
     if (implementationMethod != null) {
@@ -4434,7 +4440,7 @@
     if (override.isLambdaTarget()) {
       return true;
     }
-    ProgramMethod programMethod = override.asMethodTarget().asProgramMethod();
+    ProgramMethod programMethod = override.asMethodTarget().getTarget().asProgramMethod();
     if (programMethod == null) {
       return false;
     }
diff --git a/src/main/java/com/android/tools/r8/shaking/L8TreePruner.java b/src/main/java/com/android/tools/r8/shaking/L8TreePruner.java
index 46e77e8..d462818 100644
--- a/src/main/java/com/android/tools/r8/shaking/L8TreePruner.java
+++ b/src/main/java/com/android/tools/r8/shaking/L8TreePruner.java
@@ -9,7 +9,6 @@
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.ir.desugar.TypeRewriter;
 import com.android.tools.r8.utils.InternalOptions;
-import com.google.common.collect.Sets;
 import java.util.ArrayList;
 import java.util.IdentityHashMap;
 import java.util.List;
@@ -22,25 +21,24 @@
 public class L8TreePruner {
 
   private final InternalOptions options;
-  private final Set<DexType> emulatedInterfaces = Sets.newIdentityHashSet();
-  private final Set<DexType> backports = Sets.newIdentityHashSet();
   private final List<DexType> pruned = new ArrayList<>();
 
   public L8TreePruner(InternalOptions options) {
     this.options = options;
-    backports.addAll(options.machineDesugaredLibrarySpecification.getLegacyBackport().keySet());
-    emulatedInterfaces.addAll(
-        options.machineDesugaredLibrarySpecification.getEmulatedInterfaces().keySet());
   }
 
   public DexApplication prune(DexApplication app, TypeRewriter typeRewriter) {
+    Set<DexType> maintainType = options.machineDesugaredLibrarySpecification.getMaintainType();
+    Set<DexType> emulatedInterfaces =
+        options.machineDesugaredLibrarySpecification.getEmulatedInterfaces().keySet();
     Map<DexType, DexProgramClass> typeMap = new IdentityHashMap<>();
     List<DexProgramClass> toKeep = new ArrayList<>();
     boolean pruneNestMember = false;
     for (DexProgramClass aClass : app.classes()) {
       typeMap.put(aClass.type, aClass);
       if (typeRewriter.hasRewrittenType(aClass.type, null)
-          || emulatedInterfaces.contains(aClass.type)) {
+          || emulatedInterfaces.contains(aClass.type)
+          || maintainType.contains(aClass.type)) {
         toKeep.add(aClass);
       } else {
         pruneNestMember |= aClass.isInANest();
diff --git a/src/main/java/com/android/tools/r8/synthesis/SyntheticMarker.java b/src/main/java/com/android/tools/r8/synthesis/SyntheticMarker.java
index ceb9a37..67dbe6a 100644
--- a/src/main/java/com/android/tools/r8/synthesis/SyntheticMarker.java
+++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticMarker.java
@@ -14,6 +14,7 @@
 import com.android.tools.r8.synthesis.SyntheticNaming.SyntheticKind;
 import com.android.tools.r8.utils.DescriptorUtils;
 import com.android.tools.r8.utils.InternalOptions;
+import java.nio.charset.StandardCharsets;
 import org.objectweb.asm.Attribute;
 import org.objectweb.asm.ByteVector;
 import org.objectweb.asm.ClassReader;
@@ -23,33 +24,51 @@
 public class SyntheticMarker {
 
   private static final String SYNTHETIC_MARKER_ATTRIBUTE_TYPE_NAME =
-      "com.android.tools.r8.SynthesizedClass";
+      "com.android.tools.r8.SynthesizedClassV2";
 
   public static Attribute getMarkerAttributePrototype(SyntheticNaming syntheticNaming) {
-    return new MarkerAttribute(null, syntheticNaming);
+    return new MarkerAttribute(null, null, syntheticNaming);
   }
 
   public static void writeMarkerAttribute(
       ClassWriter writer, SyntheticKind kind, SyntheticItems syntheticItems) {
-    writer.visitAttribute(new MarkerAttribute(kind, syntheticItems.getNaming()));
+    SyntheticNaming naming = syntheticItems.getNaming();
+    writer.visitAttribute(new MarkerAttribute(kind, naming.getVersionHash(), naming));
   }
 
   public static SyntheticMarker readMarkerAttribute(Attribute attribute) {
     if (attribute instanceof MarkerAttribute) {
       MarkerAttribute marker = (MarkerAttribute) attribute;
-      return new SyntheticMarker(marker.kind, null);
+      if (marker.versionHash.equals(marker.syntheticNaming.getVersionHash())) {
+        return new SyntheticMarker(marker.kind, null);
+      }
     }
     return null;
   }
 
+  /**
+   * CF attribute for marking synthetic classes.
+   *
+   * <p>The attribute name is defined by {@code SYNTHETIC_MARKER_ATTRIBUTE_TYPE_NAME}. The format of
+   * the attribute payload is
+   *
+   * <pre>
+   *   u2 syntheticKindId
+   *   u2 versionHashLength
+   *   u1[versionHashLength] versionHashBytes
+   * </pre>
+   */
   private static class MarkerAttribute extends Attribute {
 
-    private SyntheticKind kind;
+    private final SyntheticKind kind;
+    private final String versionHash;
     private final SyntheticNaming syntheticNaming;
 
-    public MarkerAttribute(SyntheticKind kind, SyntheticNaming syntheticNaming) {
+    public MarkerAttribute(
+        SyntheticKind kind, String versionHash, SyntheticNaming syntheticNaming) {
       super(SYNTHETIC_MARKER_ATTRIBUTE_TYPE_NAME);
       this.kind = kind;
+      this.versionHash = versionHash;
       this.syntheticNaming = syntheticNaming;
     }
 
@@ -61,18 +80,29 @@
         char[] charBuffer,
         int codeAttributeOffset,
         Label[] labels) {
-      short id = classReader.readShort(offset);
-      assert id >= 0;
-      SyntheticKind kind = syntheticNaming.fromId(id);
-      return new MarkerAttribute(kind, syntheticNaming);
+      short syntheticKindId = classReader.readShort(offset);
+      offset += 2;
+      short versionHashLength = classReader.readShort(offset);
+      offset += 2;
+      byte[] versionHashBytes = new byte[versionHashLength];
+      for (int i = 0; i < versionHashLength; i++) {
+        versionHashBytes[i] = (byte) classReader.readByte(offset++);
+      }
+      assert syntheticKindId >= 0;
+      SyntheticKind kind = syntheticNaming.fromId(syntheticKindId);
+      String versionHash = new String(versionHashBytes, StandardCharsets.UTF_8);
+      return new MarkerAttribute(kind, versionHash, syntheticNaming);
     }
 
     @Override
     protected ByteVector write(
         ClassWriter classWriter, byte[] code, int codeLength, int maxStack, int maxLocals) {
-      ByteVector byteVector = new ByteVector();
       assert 0 <= kind.getId() && kind.getId() <= Short.MAX_VALUE;
+      ByteVector byteVector = new ByteVector();
       byteVector.putShort(kind.getId());
+      byte[] versionHashBytes = versionHash.getBytes(StandardCharsets.UTF_8);
+      byteVector.putShort(versionHashBytes.length);
+      byteVector.putByteArray(versionHashBytes, 0, versionHashBytes.length);
       return byteVector;
     }
   }
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 edc591e..3559fe3 100644
--- a/src/main/java/com/android/tools/r8/synthesis/SyntheticNaming.java
+++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticNaming.java
@@ -3,6 +3,7 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.synthesis;
 
+import com.android.tools.r8.Version;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexType;
@@ -12,131 +13,143 @@
 import com.android.tools.r8.utils.DescriptorUtils;
 import com.android.tools.r8.utils.structural.Equatable;
 import com.android.tools.r8.utils.structural.Ordered;
-import it.unimi.dsi.fastutil.ints.IntArraySet;
-import it.unimi.dsi.fastutil.ints.IntSet;
+import com.google.common.hash.Hasher;
+import com.google.common.hash.Hashing;
+import java.nio.charset.StandardCharsets;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.List;
 
 public class SyntheticNaming {
 
-  public SyntheticNaming() {}
-
   private KindGenerator generator = new KindGenerator();
 
   // Global synthetics.
-  public final SyntheticKind RECORD_TAG = generator.forGlobalClass(1);
-  public final SyntheticKind API_MODEL_STUB = generator.forGlobalClass(33);
+  public final SyntheticKind RECORD_TAG = generator.forGlobalClass();
+  public final SyntheticKind API_MODEL_STUB = generator.forGlobalClass();
 
   // Classpath only synthetics in the global type namespace.
-  public final SyntheticKind RETARGET_STUB = generator.forGlobalClasspathClass(36);
-  public final SyntheticKind EMULATED_INTERFACE_MARKER_CLASS =
-      generator.forGlobalClasspathClass(29);
+  public final SyntheticKind RETARGET_STUB = generator.forGlobalClasspathClass();
+  public final SyntheticKind EMULATED_INTERFACE_MARKER_CLASS = generator.forGlobalClasspathClass();
 
   // Fixed suffix synthetics. Each has a hygienic prefix type.
   public final SyntheticKind ENUM_UNBOXING_LOCAL_UTILITY_CLASS =
-      generator.forFixedClass(24, "$EnumUnboxingLocalUtility");
+      generator.forFixedClass("$EnumUnboxingLocalUtility");
   public final SyntheticKind ENUM_UNBOXING_SHARED_UTILITY_CLASS =
-      generator.forFixedClass(25, "$EnumUnboxingSharedUtility");
-  public final SyntheticKind COMPANION_CLASS = generator.forFixedClass(2, "$-CC");
+      generator.forFixedClass("$EnumUnboxingSharedUtility");
+  public final SyntheticKind COMPANION_CLASS = generator.forFixedClass("$-CC");
   public final SyntheticKind EMULATED_INTERFACE_CLASS =
-      generator.forFixedClass(3, InterfaceDesugaringForTesting.EMULATED_INTERFACE_CLASS_SUFFIX);
-  public final SyntheticKind RETARGET_CLASS = generator.forFixedClass(20, "RetargetClass");
-  public final SyntheticKind RETARGET_INTERFACE = generator.forFixedClass(21, "RetargetInterface");
-  public final SyntheticKind WRAPPER = generator.forFixedClass(22, "$Wrapper");
-  public final SyntheticKind VIVIFIED_WRAPPER = generator.forFixedClass(23, "$VivifiedWrapper");
-  public final SyntheticKind INIT_TYPE_ARGUMENT = generator.forFixedClass(5, "-IA");
+      generator.forFixedClass(InterfaceDesugaringForTesting.EMULATED_INTERFACE_CLASS_SUFFIX);
+  public final SyntheticKind RETARGET_CLASS = generator.forFixedClass("RetargetClass");
+  public final SyntheticKind RETARGET_INTERFACE = generator.forFixedClass("RetargetInterface");
+  public final SyntheticKind WRAPPER = generator.forFixedClass("$Wrapper");
+  public final SyntheticKind VIVIFIED_WRAPPER = generator.forFixedClass("$VivifiedWrapper");
+  public final SyntheticKind INIT_TYPE_ARGUMENT = generator.forFixedClass("-IA");
   public final SyntheticKind HORIZONTAL_INIT_TYPE_ARGUMENT_1 =
-      generator.forFixedClass(6, SYNTHETIC_CLASS_SEPARATOR + "IA$1");
+      generator.forFixedClass(SYNTHETIC_CLASS_SEPARATOR + "IA$1");
   public final SyntheticKind HORIZONTAL_INIT_TYPE_ARGUMENT_2 =
-      generator.forFixedClass(7, SYNTHETIC_CLASS_SEPARATOR + "IA$2");
+      generator.forFixedClass(SYNTHETIC_CLASS_SEPARATOR + "IA$2");
   public final SyntheticKind HORIZONTAL_INIT_TYPE_ARGUMENT_3 =
-      generator.forFixedClass(8, SYNTHETIC_CLASS_SEPARATOR + "IA$3");
-  public final SyntheticKind ENUM_CONVERSION = generator.forFixedClass(31, "$EnumConversion");
+      generator.forFixedClass(SYNTHETIC_CLASS_SEPARATOR + "IA$3");
+  public final SyntheticKind ENUM_CONVERSION = generator.forFixedClass("$EnumConversion");
 
   // Locally generated synthetic classes.
-  public final SyntheticKind LAMBDA = generator.forInstanceClass(4, "Lambda");
+  public final SyntheticKind LAMBDA = generator.forInstanceClass("Lambda");
 
   // TODO(b/214901256): Sharing of synthetic classes may lead to duplicate method errors.
   public final SyntheticKind NON_FIXED_INIT_TYPE_ARGUMENT =
-      generator.forNonSharableInstanceClass(35, "$IA");
-  public final SyntheticKind CONST_DYNAMIC = generator.forInstanceClass(30, "$Condy");
+      generator.forNonSharableInstanceClass("$IA");
+  public final SyntheticKind CONST_DYNAMIC = generator.forInstanceClass("$Condy");
 
   // Method synthetics.
   public final SyntheticKind ENUM_UNBOXING_CHECK_NOT_ZERO_METHOD =
-      generator.forSingleMethod(27, "CheckNotZero");
-  public final SyntheticKind RECORD_HELPER = generator.forSingleMethod(9, "Record");
-  public final SyntheticKind BACKPORT = generator.forSingleMethod(10, "Backport");
+      generator.forSingleMethod("CheckNotZero");
+  public final SyntheticKind RECORD_HELPER = generator.forSingleMethod("Record");
+  public final SyntheticKind BACKPORT = generator.forSingleMethod("Backport");
   public final SyntheticKind BACKPORT_WITH_FORWARDING =
-      generator.forSingleMethod(34, "BackportWithForwarding");
+      generator.forSingleMethod("BackportWithForwarding");
   public final SyntheticKind STATIC_INTERFACE_CALL =
-      generator.forSingleMethod(11, "StaticInterfaceCall");
-  public final SyntheticKind TO_STRING_IF_NOT_NULL =
-      generator.forSingleMethod(12, "ToStringIfNotNull");
-  public final SyntheticKind THROW_CCE_IF_NOT_NULL =
-      generator.forSingleMethod(13, "ThrowCCEIfNotNull");
-  public final SyntheticKind THROW_IAE = generator.forSingleMethod(14, "ThrowIAE");
-  public final SyntheticKind THROW_ICCE = generator.forSingleMethod(15, "ThrowICCE");
-  public final SyntheticKind THROW_NSME = generator.forSingleMethod(16, "ThrowNSME");
-  public final SyntheticKind TWR_CLOSE_RESOURCE = generator.forSingleMethod(17, "TwrCloseResource");
-  public final SyntheticKind SERVICE_LOADER = generator.forSingleMethod(18, "ServiceLoad");
-  public final SyntheticKind OUTLINE = generator.forSingleMethod(19, "Outline");
-  public final SyntheticKind API_CONVERSION = generator.forSingleMethod(26, "APIConversion");
+      generator.forSingleMethod("StaticInterfaceCall");
+  public final SyntheticKind TO_STRING_IF_NOT_NULL = generator.forSingleMethod("ToStringIfNotNull");
+  public final SyntheticKind THROW_CCE_IF_NOT_NULL = generator.forSingleMethod("ThrowCCEIfNotNull");
+  public final SyntheticKind THROW_IAE = generator.forSingleMethod("ThrowIAE");
+  public final SyntheticKind THROW_ICCE = generator.forSingleMethod("ThrowICCE");
+  public final SyntheticKind THROW_NSME = generator.forSingleMethod("ThrowNSME");
+  public final SyntheticKind TWR_CLOSE_RESOURCE = generator.forSingleMethod("TwrCloseResource");
+  public final SyntheticKind SERVICE_LOADER = generator.forSingleMethod("ServiceLoad");
+  public final SyntheticKind OUTLINE = generator.forSingleMethod("Outline");
+  public final SyntheticKind API_CONVERSION = generator.forSingleMethod("APIConversion");
   public final SyntheticKind API_CONVERSION_PARAMETERS =
-      generator.forSingleMethod(28, "APIConversionParameters");
-  public final SyntheticKind ARRAY_CONVERSION = generator.forSingleMethod(37, "$ArrayConversion");
-  public final SyntheticKind API_MODEL_OUTLINE = generator.forSingleMethod(32, "ApiModelOutline");
+      generator.forSingleMethod("APIConversionParameters");
+  public final SyntheticKind ARRAY_CONVERSION = generator.forSingleMethod("$ArrayConversion");
+  public final SyntheticKind API_MODEL_OUTLINE = generator.forSingleMethod("ApiModelOutline");
 
-  private final List<SyntheticKind> ALL_KINDS = generator.getAllKinds();
+  private final String versionHash;
+  private final List<SyntheticKind> ALL_KINDS;
+
+  public SyntheticNaming() {
+    generator.hasher.putString(Version.getVersionString(), StandardCharsets.UTF_8);
+    versionHash = generator.hasher.hash().toString();
+    ALL_KINDS = generator.getAllKinds();
+    generator = null;
+  }
+
+  public String getVersionHash() {
+    return versionHash;
+  }
 
   public Collection<SyntheticKind> kinds() {
     return ALL_KINDS;
   }
 
   public SyntheticKind fromId(int id) {
-    for (SyntheticKind kind : ALL_KINDS) {
-      if (kind.getId() == id) {
-        return kind;
-      }
+    if (0 < id && id <= ALL_KINDS.size()) {
+      return ALL_KINDS.get(id - 1);
     }
     return null;
   }
 
   private static class KindGenerator {
+    private int nextId = 1;
     private List<SyntheticKind> kinds = new ArrayList<>();
-    private IntSet usedIds = new IntArraySet();
+    private Hasher hasher = Hashing.sha256().newHasher();
 
     private SyntheticKind register(SyntheticKind kind) {
-      if (!usedIds.add(kind.getId())) {
-        throw new Unreachable("Invalid reuse of synthetic kind id: " + kind.getId());
-      }
+      kind.hash(hasher);
       kinds.add(kind);
+      if (kinds.size() != kind.getId()) {
+        throw new Unreachable("Invalid synthetic kind id: " + kind.getId());
+      }
       return kind;
     }
 
-    SyntheticKind forSingleMethod(int id, String descriptor) {
-      return register(new SyntheticMethodKind(id, descriptor));
+    private int getNextId() {
+      return nextId++;
+    }
+
+    SyntheticKind forSingleMethod(String descriptor) {
+      return register(new SyntheticMethodKind(getNextId(), descriptor));
     }
 
     // TODO(b/214901256): Remove once fixed.
-    SyntheticKind forNonSharableInstanceClass(int id, String descriptor) {
-      return register(new SyntheticClassKind(id, descriptor, false));
+    SyntheticKind forNonSharableInstanceClass(String descriptor) {
+      return register(new SyntheticClassKind(getNextId(), descriptor, false));
     }
 
-    SyntheticKind forInstanceClass(int id, String descriptor) {
-      return register(new SyntheticClassKind(id, descriptor, true));
+    SyntheticKind forInstanceClass(String descriptor) {
+      return register(new SyntheticClassKind(getNextId(), descriptor, true));
     }
 
-    SyntheticKind forFixedClass(int id, String descriptor) {
-      return register(new SyntheticFixedClassKind(id, descriptor, false));
+    SyntheticKind forFixedClass(String descriptor) {
+      return register(new SyntheticFixedClassKind(getNextId(), descriptor, false));
     }
 
-    SyntheticKind forGlobalClass(int id) {
-      return register(new SyntheticFixedClassKind(id, "", true));
+    SyntheticKind forGlobalClass() {
+      return register(new SyntheticFixedClassKind(getNextId(), "", true));
     }
 
-    SyntheticKind forGlobalClasspathClass(int id) {
-      return register(new SyntheticFixedClassKind(id, "", false));
+    SyntheticKind forGlobalClasspathClass() {
+      return register(new SyntheticFixedClassKind(getNextId(), "", false));
     }
 
     List<SyntheticKind> getAllKinds() {
@@ -198,6 +211,13 @@
 
     public abstract boolean isMayOverridesNonProgramType();
 
+    public final void hash(Hasher hasher) {
+      hasher.putInt(getId());
+      hasher.putString(getDescriptor(), StandardCharsets.UTF_8);
+      internalHash(hasher);
+    }
+
+    public abstract void internalHash(Hasher hasher);
   }
 
   private static class SyntheticMethodKind extends SyntheticKind {
@@ -232,6 +252,10 @@
       return false;
     }
 
+    @Override
+    public void internalHash(Hasher hasher) {
+      hasher.putString("method", StandardCharsets.UTF_8);
+    }
   }
 
   private static class SyntheticClassKind extends SyntheticKind {
@@ -269,6 +293,11 @@
       return false;
     }
 
+    @Override
+    public void internalHash(Hasher hasher) {
+      hasher.putString("class", StandardCharsets.UTF_8);
+      hasher.putBoolean(sharable);
+    }
   }
 
   private static class SyntheticFixedClassKind extends SyntheticClassKind {
@@ -299,6 +328,11 @@
       return mayOverridesNonProgramType;
     }
 
+    @Override
+    public void internalHash(Hasher hasher) {
+      hasher.putString(isGlobal() ? "global" : "fixed", StandardCharsets.UTF_8);
+      hasher.putBoolean(mayOverridesNonProgramType);
+    }
   }
 
   private static final String SYNTHETIC_CLASS_SEPARATOR = "$$";
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 50c315a..309e92a5 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -25,7 +25,6 @@
 import com.android.tools.r8.dex.Marker.Tool;
 import com.android.tools.r8.dump.DumpOptions;
 import com.android.tools.r8.errors.CompilationError;
-import com.android.tools.r8.errors.ExperimentalClassFileVersionDiagnostic;
 import com.android.tools.r8.errors.IncompleteNestNestDesugarDiagnosic;
 import com.android.tools.r8.errors.InterfaceDesugarMissingTypeDiagnostic;
 import com.android.tools.r8.errors.InvalidDebugInfoException;
@@ -108,6 +107,7 @@
 import java.util.function.Consumer;
 import java.util.function.Function;
 import java.util.function.Predicate;
+import java.util.stream.Collectors;
 import org.objectweb.asm.Opcodes;
 
 public class InternalOptions implements GlobalKeepInfoConfiguration {
@@ -138,7 +138,6 @@
   }
 
   public static final CfVersion SUPPORTED_CF_VERSION = CfVersion.V17;
-  public static final CfVersion EXPERIMENTAL_CF_VERSION = CfVersion.V14;
 
   public static final int SUPPORTED_DEX_VERSION =
       AndroidApiLevel.LATEST.getDexVersion().getIntValue();
@@ -272,9 +271,34 @@
   // Flag to toggle if DEX code objects should pass-through without IR processing.
   public boolean passthroughDexCode = false;
 
+  public static class NeverMergeGroup<T> {
+    private final List<T> prefixes;
+    private final List<T> exceptionPrefixes;
+
+    NeverMergeGroup(List<T> prefixes, List<T> exceptionPrefixes) {
+      this.prefixes = prefixes;
+      this.exceptionPrefixes = exceptionPrefixes;
+    }
+
+    public List<T> getPrefixes() {
+      return prefixes;
+    }
+
+    public List<T> getExceptionPrefixes() {
+      return exceptionPrefixes;
+    }
+
+    public <R> NeverMergeGroup<R> map(Function<T, R> fn) {
+      return new NeverMergeGroup<>(
+          prefixes.stream().map(fn).collect(Collectors.toList()),
+          exceptionPrefixes.stream().map(fn).collect(Collectors.toList()));
+    }
+  }
+
   // Flag to toggle if the prefix based merge restriction should be enforced.
   public boolean enableNeverMergePrefixes = true;
-  public Set<String> neverMergePrefixes = ImmutableSet.of("j$.");
+  public NeverMergeGroup<String> neverMerge =
+      new NeverMergeGroup<>(ImmutableList.of("j$."), ImmutableList.of("java."));
 
   public boolean classpathInterfacesMayHaveStaticInitialization = false;
   public boolean libraryInterfacesMayHaveStaticInitialization = false;
@@ -1135,23 +1159,6 @@
     }
   }
 
-  private final Box<Boolean> reportedExperimentClassFileVersion = new Box<>(false);
-
-  public void warningExperimentalClassFileVersion(Origin origin) {
-    synchronized (reportedExperimentClassFileVersion) {
-      if (reportedExperimentClassFileVersion.get()) {
-        return;
-      }
-      reportedExperimentClassFileVersion.set(true);
-      reporter.warning(
-          new ExperimentalClassFileVersionDiagnostic(
-              origin,
-              "One or more classes has class file version >= "
-                  + EXPERIMENTAL_CF_VERSION.major()
-                  + " which is not officially supported."));
-    }
-  }
-
   public boolean printWarnings() {
     boolean printed = false;
     boolean printOutdatedToolchain = false;
@@ -1722,10 +1729,6 @@
       }
     }
 
-    public static void allowExperimentClassFileVersion(InternalOptions options) {
-      options.reportedExperimentClassFileVersion.set(true);
-    }
-
     public static int NO_LIMIT = -1;
 
     public ArgumentPropagatorEventConsumer argumentPropagatorEventConsumer =
diff --git a/src/test/java/com/android/tools/r8/L8TestBuilder.java b/src/test/java/com/android/tools/r8/L8TestBuilder.java
index 7a42ab1..2ff60ba 100644
--- a/src/test/java/com/android/tools/r8/L8TestBuilder.java
+++ b/src/test/java/com/android/tools/r8/L8TestBuilder.java
@@ -184,7 +184,10 @@
         .inspect(
             inspector ->
                 inspector.forAllClasses(
-                    clazz -> assertTrue(clazz.getFinalName().startsWith("j$."))));
+                    clazz ->
+                        assertTrue(
+                            clazz.getFinalName().startsWith("j$.")
+                                || clazz.getFinalName().startsWith("java."))));
   }
 
   private Collection<Path> getProgramFiles() {
diff --git a/src/test/java/com/android/tools/r8/R8CommandTest.java b/src/test/java/com/android/tools/r8/R8CommandTest.java
index d076ec0..1242d17 100644
--- a/src/test/java/com/android/tools/r8/R8CommandTest.java
+++ b/src/test/java/com/android/tools/r8/R8CommandTest.java
@@ -86,6 +86,7 @@
     assertTrue(command.getEnableTreeShaking());
     assertEquals(CompilationMode.RELEASE, command.getMode());
     assertTrue(command.getProgramConsumer() instanceof DexIndexedConsumer);
+    assertFalse(command.getProguardCompatibility());
   }
 
   @Test(expected = CompilationFailedException.class)
@@ -268,6 +269,12 @@
   }
 
   @Test
+  public void proguardCompatMode() throws Throwable {
+    assertFalse(parse("").getProguardCompatibility());
+    assertTrue(parse("--pg-compat").getProguardCompatibility());
+  }
+
+  @Test
   public void classFileOutputModeOption() throws Throwable {
     assertTrue(parse("--classfile").getProgramConsumer() instanceof ClassFileConsumer);
   }
diff --git a/src/test/java/com/android/tools/r8/TestBase.java b/src/test/java/com/android/tools/r8/TestBase.java
index 4daa6a9..3a482a5 100644
--- a/src/test/java/com/android/tools/r8/TestBase.java
+++ b/src/test/java/com/android/tools/r8/TestBase.java
@@ -416,7 +416,7 @@
   }
 
   public static TestParametersBuilder getTestParameters() {
-    return TestParametersBuilder.builder();
+    return TestParameters.builder();
   }
 
   public static KotlinTestParameters.Builder getKotlinTestParameters() {
diff --git a/src/test/java/com/android/tools/r8/TestParameters.java b/src/test/java/com/android/tools/r8/TestParameters.java
index a9b67e3..7853170 100644
--- a/src/test/java/com/android/tools/r8/TestParameters.java
+++ b/src/test/java/com/android/tools/r8/TestParameters.java
@@ -31,6 +31,14 @@
     this.apiLevel = apiLevel;
   }
 
+  public static TestParametersBuilder builder() {
+    return new TestParametersBuilder();
+  }
+
+  public static TestParametersCollection justNoneRuntime() {
+    return builder().withNoneRuntime().build();
+  }
+
   public boolean canUseDefaultAndStaticInterfaceMethods() {
     assert isCfRuntime() || isDexRuntime();
     assert !isCfRuntime() || apiLevel == null
diff --git a/src/test/java/com/android/tools/r8/TestParametersBuilder.java b/src/test/java/com/android/tools/r8/TestParametersBuilder.java
index 375f6f8..cb24356 100644
--- a/src/test/java/com/android/tools/r8/TestParametersBuilder.java
+++ b/src/test/java/com/android/tools/r8/TestParametersBuilder.java
@@ -26,11 +26,7 @@
   private Predicate<TestParameters> filter = param -> false;
   private boolean hasDexRuntimeFilter = false;
 
-  private TestParametersBuilder() {}
-
-  public static TestParametersBuilder builder() {
-    return new TestParametersBuilder();
-  }
+  TestParametersBuilder() {}
 
   private TestParametersBuilder withFilter(Predicate<TestParameters> predicate) {
     filter = filter.or(predicate);
diff --git a/src/test/java/com/android/tools/r8/TestRuntime.java b/src/test/java/com/android/tools/r8/TestRuntime.java
index 336ffd1..ec3d29c 100644
--- a/src/test/java/com/android/tools/r8/TestRuntime.java
+++ b/src/test/java/com/android/tools/r8/TestRuntime.java
@@ -189,7 +189,7 @@
     if (version.startsWith("9.")) {
       return new CfRuntime(CfVm.JDK9, Paths.get(home));
     }
-    if (version.startsWith("11.")) {
+    if (version.equals("11") || version.startsWith("11.")) {
       return new CfRuntime(CfVm.JDK11, Paths.get(home));
     }
     throw new Unimplemented("No support for JDK version: " + version);
diff --git a/src/test/java/com/android/tools/r8/bisect/BisectTest.java b/src/test/java/com/android/tools/r8/bisect/BisectTest.java
index bd1d3c4..3b34747 100644
--- a/src/test/java/com/android/tools/r8/bisect/BisectTest.java
+++ b/src/test/java/com/android/tools/r8/bisect/BisectTest.java
@@ -7,7 +7,6 @@
 
 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.bisect.BisectOptions.Result;
 import com.android.tools.r8.dex.ApplicationReader;
@@ -34,7 +33,7 @@
 
   @Parameters(name = "{0}")
   public static TestParametersCollection parameters() {
-    return TestParametersBuilder.builder().withNoneRuntime().build();
+    return TestParameters.builder().withNoneRuntime().build();
   }
 
   public BisectTest(TestParameters parameters) {
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 14fff26..8c70d15 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
@@ -3,24 +3,22 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.cf.bootstrap;
 
-import static com.android.tools.r8.utils.FileUtils.JAR_EXTENSION;
 import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
 
-import com.android.tools.r8.ClassFileConsumer;
 import com.android.tools.r8.CompilationMode;
-import com.android.tools.r8.R8Command;
+import com.android.tools.r8.R8;
 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.ToolHelper.ProcessResult;
-import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.utils.FileUtils;
+import com.android.tools.r8.utils.StringUtils;
 import com.google.common.base.Charsets;
-import com.google.common.collect.ImmutableList;
 import java.nio.file.Path;
 import java.nio.file.Paths;
+import java.util.Collections;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
@@ -29,18 +27,10 @@
 @RunWith(Parameterized.class)
 public class BootstrapTest extends TestBase {
 
-  private static final Path R8_STABLE_JAR = Paths.get("third_party/r8/r8.jar");
+  private static final Path R8_STABLE_JAR =
+      Paths.get("third_party", "r8-releases", "3.2.54", "r8.jar");
 
-  private static final String R8_NAME = "com.android.tools.r8.R8";
-  private static final String[] KEEP_R8 = {
-    "-keep public class " + R8_NAME + " {", "  public static void main(...);", "}",
-  };
-  private static final Path DONTWARN_R8 = Paths.get("src/main/dontwarn.txt");
-
-  private static final String HELLO_NAME = "hello.Hello";
-  private static final String[] KEEP_HELLO = {
-    "-keep class " + HELLO_NAME + " {", "  public static void main(...);", "}",
-  };
+  private static final String HELLO_EXPECTED = StringUtils.lines("Hello World!");
 
   private static class R8Result {
 
@@ -65,98 +55,112 @@
     return getTestParameters().withNoneRuntime().build();
   }
 
+  private static CfRuntime getHostRuntime() {
+    return CfRuntime.getSystemRuntime();
+  }
+
   public BootstrapTest(TestParameters parameters) {
-    // TODO: use parameters to fork the right Java.
     parameters.assertNoneRuntime();
   }
 
+  private Path getHelloInputs() {
+    return ToolHelper.getClassFileForTestClass(Hello.class);
+  }
+
+  private String getHelloKeepRules() {
+    return TestBase.keepMainProguardConfiguration(Hello.class);
+  }
+
   @Test
-  public void test() throws Exception {
-    // Run hello.jar to ensure it exists and is valid.
-    Path hello = Paths.get(ToolHelper.EXAMPLES_BUILD_DIR, "hello" + JAR_EXTENSION);
-    ProcessResult runHello = ToolHelper.runJava(hello, "hello.Hello");
-    assertEquals(0, runHello.exitCode);
+  public void reference() throws Exception {
+    testForJvm()
+        .addProgramFiles(getHelloInputs())
+        .run(getHostRuntime(), Hello.class)
+        .assertSuccessWithOutput(HELLO_EXPECTED);
+  }
 
+  @Test
+  public void testDebug() throws Exception {
+    compareForMode(CompilationMode.DEBUG);
+  }
+
+  @Test
+  public void testRelease() throws Exception {
+    compareForMode(CompilationMode.RELEASE);
+  }
+
+  private R8Result compareForMode(CompilationMode mode) throws Exception {
     // Run r8.jar on hello.jar to ensure that r8.jar is a working compiler.
-    R8Result runInputR8 = runExternalR8(R8_STABLE_JAR, hello, "input", KEEP_HELLO, "--debug");
-    ProcessResult runHelloR8 = ToolHelper.runJava(runInputR8.outputJar, "hello.Hello");
-    assertEquals(runHello.toString(), runHelloR8.toString());
+    R8Result helloCompiledWithR8 =
+        runExternalR8(R8_STABLE_JAR, getHelloInputs(), getHelloKeepRules(), mode);
+    testForJvm()
+        .addProgramFiles(helloCompiledWithR8.outputJar)
+        .run(getHostRuntime(), Hello.class)
+        .assertSuccessWithOutput(HELLO_EXPECTED);
 
-    compareR8(hello, runInputR8, CompilationMode.RELEASE, "r8-r8-rel", "--release", "output-rel");
-    compareR8(hello, runInputR8, CompilationMode.DEBUG, "r8-r8", "--debug", "output");
+    compareR8(helloCompiledWithR8, mode);
+    return helloCompiledWithR8;
   }
 
-  private void compareR8(
-      Path hello,
-      R8Result runInputR8,
-      CompilationMode internalMode,
-      String internalOutput,
-      String externalMode,
-      String externalOutput)
-      throws Exception {
+  private void compareR8(R8Result referenceCompilation, CompilationMode mode) throws Exception {
     // Run R8 on r8.jar.
-    Path output = runR8(internalOutput, internalMode);
+    Path r8CompiledByR8 = compileR8WithR8(mode);
     // Run the resulting compiler on hello.jar.
-    R8Result runR8R8 = runExternalR8(output, hello, externalOutput, KEEP_HELLO, externalMode);
+    R8Result runR8R8 = runExternalR8(r8CompiledByR8, getHelloInputs(), getHelloKeepRules(), mode);
     // Check that the process outputs (exit code, stdout, stderr) are the same.
-    assertEquals(runInputR8.toString(), runR8R8.toString());
+    assertEquals(referenceCompilation.toString(), runR8R8.toString());
     // Check that the output jars are the same.
-    if (true) {
-      // TODO(b/223770583): These should be equal but an error in assertProgramEquals was hiding it.
-      assertFalse(filesAreEqual(runInputR8.outputJar, runR8R8.outputJar));
-    } else {
-      assertProgramsEqual(runInputR8.outputJar, runR8R8.outputJar);
-    }
+    assertProgramsEqual(referenceCompilation.outputJar, runR8R8.outputJar);
   }
 
-  private Path runR8(String outputFolder, CompilationMode mode) throws Exception {
-    Path outputPath = temp.newFolder(outputFolder).toPath();
-    Path outputJar = outputPath.resolve("output.jar");
-    Path pgConfigFile = outputPath.resolve("keep.rules");
-    FileUtils.writeTextFile(pgConfigFile, BootstrapTest.KEEP_R8);
-    ToolHelper.runR8(
-        R8Command.builder()
-            .setMode(mode)
-            .addLibraryFiles(ToolHelper.getJava8RuntimeJar())
-            .setProgramConsumer(new ClassFileConsumer.ArchiveConsumer(outputJar, true))
-            .addProgramFiles(R8_STABLE_JAR)
-            .addProguardConfigurationFiles(pgConfigFile, DONTWARN_R8)
-            // The R8_STABLE_JAR is from when Guava 23.0 was used, and that included
-            // javax.annotation.Nullable annotations in the retained code.
-            .addProguardConfiguration(
-                ImmutableList.of("-dontwarn javax.annotation.Nullable"), Origin.unknown())
-            .build());
-    return outputJar;
+  private Path compileR8WithR8(CompilationMode mode) throws Exception {
+    return testForR8(Backend.CF)
+        .setMode(mode)
+        .addProgramFiles(R8_STABLE_JAR)
+        .addKeepRules(TestBase.keepMainProguardConfiguration(R8.class))
+        // The r8 stable/release hits open interface issues.
+        .addOptionsModification(o -> o.getOpenClosedInterfacesOptions().suppressAllOpenInterfaces())
+        // The r8 stable/release contains missing com.google.errorprone annotation references.
+        .addDontWarnGoogle()
+        .compile()
+        .writeToZip();
   }
 
-  private R8Result runExternalR8(
-      Path r8Jar, Path inputJar, String outputFolder, String[] keepRules, String mode)
+  private R8Result runExternalR8(Path r8Jar, Path inputJar, String keepRules, CompilationMode mode)
       throws Exception {
-    Path outputPath = temp.newFolder(outputFolder).toPath();
+    Path outputPath = temp.newFolder().toPath();
     Path pgConfigFile = outputPath.resolve("keep.rules");
     Path outputJar = outputPath.resolve("output.jar");
     Path pgMapFile = outputPath.resolve("map.txt");
     FileUtils.writeTextFile(pgConfigFile, keepRules);
     ProcessResult processResult =
         ToolHelper.runJava(
-            r8Jar,
-            R8_NAME,
+            getHostRuntime(),
+            Collections.singletonList(r8Jar),
+            R8.class.getTypeName(),
             "--lib",
-            ToolHelper.JAVA_8_RUNTIME,
+            ToolHelper.getJava8RuntimeJar().toString(),
             "--classfile",
             inputJar.toString(),
             "--output",
             outputJar.toString(),
             "--pg-conf",
             pgConfigFile.toString(),
-            mode,
+            mode == CompilationMode.DEBUG ? "--debug" : "--release",
             "--pg-map-output",
             pgMapFile.toString());
     if (processResult.exitCode != 0) {
       System.out.println(processResult);
     }
-    assertEquals(processResult.stderr, 0, processResult.exitCode);
+    assertEquals(processResult.toString(), 0, processResult.exitCode);
     String pgMap = FileUtils.readTextFile(pgMapFile, Charsets.UTF_8);
     return new R8Result(processResult, outputJar, pgMap);
   }
+
+  public static class Hello {
+
+    public static void main(String[] args) {
+      System.out.println("Hello World!");
+    }
+  }
 }
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 7081080..a136180 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
@@ -18,8 +18,8 @@
 import com.android.tools.r8.TestBase;
 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.codeinspector.ClassSubject;
-import com.android.tools.r8.utils.codeinspector.HorizontallyMergedClassesInspector;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
@@ -46,7 +46,13 @@
         // I and J are not eligible for merging, since the lambda that implements I & J inherits a
         // default m() method from K, which is also on J.
         .addHorizontallyMergedClassesInspector(
-            HorizontallyMergedClassesInspector::assertNoClassesMerged)
+            inspector -> {
+              if (parameters.isCfRuntime()) {
+                inspector.assertIsCompleteMergeGroup(I.class, J.class);
+              } else {
+                inspector.assertNoClassesMerged();
+              }
+            })
         .enableInliningAnnotations()
         .enableNeverClassInliningAnnotations()
         .enableNoHorizontalClassMergingAnnotations()
@@ -60,13 +66,26 @@
               ClassSubject aClassSubject = inspector.clazz(A.class);
               if (parameters.canUseDefaultAndStaticInterfaceMethods()) {
                 assertThat(aClassSubject, isPresent());
-                assertThat(aClassSubject, isImplementing(inspector.clazz(J.class)));
+                if (parameters.isCfRuntime()) {
+                  assertThat(aClassSubject, isImplementing(inspector.clazz(I.class)));
+                } else {
+                  assertThat(aClassSubject, isImplementing(inspector.clazz(J.class)));
+                }
               } else {
                 assertThat(aClassSubject, isAbsent());
               }
             })
         .run(parameters.getRuntime(), Main.class)
-        .assertSuccessWithOutputLines("K", "J");
+        // TODO(b/173990042): Should succeed with "K", "J".
+        .applyIf(
+            parameters.isCfRuntime(),
+            builder ->
+                builder.assertFailureWithErrorThatThrows(
+                    parameters.isCfRuntime()
+                            && parameters.getRuntime().asCf().isNewerThanOrEqual(CfVm.JDK11)
+                        ? AbstractMethodError.class
+                        : IncompatibleClassChangeError.class),
+            builder -> builder.assertSuccessWithOutputLines("K", "J"));
   }
 
   static class Main {
diff --git a/src/test/java/com/android/tools/r8/debug/KotlinStdLibCompilationTest.java b/src/test/java/com/android/tools/r8/debug/KotlinStdLibCompilationTest.java
index ebba822..2c3a478 100644
--- a/src/test/java/com/android/tools/r8/debug/KotlinStdLibCompilationTest.java
+++ b/src/test/java/com/android/tools/r8/debug/KotlinStdLibCompilationTest.java
@@ -13,7 +13,6 @@
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestDiagnosticMessages;
 import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.TestParametersBuilder;
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.utils.AndroidApiLevel;
 import java.util.List;
@@ -31,7 +30,7 @@
   @Parameters(name = "{0}, kotlinc: {1}")
   public static List<Object[]> setup() {
     return buildParameters(
-        TestParametersBuilder.builder().withAllRuntimesAndApiLevels().build(),
+        TestParameters.builder().withAllRuntimesAndApiLevels().build(),
         getKotlinTestParameters().withAllCompilers().withNoTargetVersion().build());
   }
 
diff --git a/src/test/java/com/android/tools/r8/debug/LineNumberOptimizationTest.java b/src/test/java/com/android/tools/r8/debug/LineNumberOptimizationTest.java
index d227ea3..af193de 100644
--- a/src/test/java/com/android/tools/r8/debug/LineNumberOptimizationTest.java
+++ b/src/test/java/com/android/tools/r8/debug/LineNumberOptimizationTest.java
@@ -9,7 +9,6 @@
 import com.android.tools.r8.CompilationMode;
 import com.android.tools.r8.R8TestCompileResult;
 import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.TestParametersBuilder;
 import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.utils.InternalOptions.LineNumberOptimization;
 import org.junit.Test;
@@ -34,7 +33,7 @@
 
   @Parameterized.Parameters(name = "{0}")
   public static TestParametersCollection setup() {
-    return TestParametersBuilder.builder().withAllRuntimesAndApiLevels().build();
+    return TestParameters.builder().withAllRuntimesAndApiLevels().build();
   }
 
   public LineNumberOptimizationTest(TestParameters parameters) {
diff --git a/src/test/java/com/android/tools/r8/debuginfo/DebugSetFileSmaliTest.java b/src/test/java/com/android/tools/r8/debuginfo/DebugSetFileSmaliTest.java
index 008d3cf..416cada 100644
--- a/src/test/java/com/android/tools/r8/debuginfo/DebugSetFileSmaliTest.java
+++ b/src/test/java/com/android/tools/r8/debuginfo/DebugSetFileSmaliTest.java
@@ -8,7 +8,6 @@
 
 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.graph.DexDebugEvent.SetFile;
 import com.android.tools.r8.smali.SmaliBuilder;
@@ -30,10 +29,7 @@
 
   @Parameters(name = "{0}")
   public static TestParametersCollection parameters() {
-    return TestParametersBuilder.builder()
-        .withDexRuntimes()
-        .withApiLevel(AndroidApiLevel.B)
-        .build();
+    return TestParameters.builder().withDexRuntimes().withApiLevel(AndroidApiLevel.B).build();
   }
 
   private static final String CLASS_NAME = "Test";
diff --git a/src/test/java/com/android/tools/r8/debuginfo/InliningWithoutPositionsTestRunner.java b/src/test/java/com/android/tools/r8/debuginfo/InliningWithoutPositionsTestRunner.java
index b683d7d..6af4440 100644
--- a/src/test/java/com/android/tools/r8/debuginfo/InliningWithoutPositionsTestRunner.java
+++ b/src/test/java/com/android/tools/r8/debuginfo/InliningWithoutPositionsTestRunner.java
@@ -11,7 +11,6 @@
 
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.TestParametersBuilder;
 import com.android.tools.r8.debuginfo.InliningWithoutPositionsTestSourceDump.Location;
 import com.android.tools.r8.naming.retrace.StackTrace;
 import com.android.tools.r8.naming.retrace.StackTrace.StackTraceLine;
@@ -44,7 +43,7 @@
   public static Collection<Object[]> data() {
     List<Object[]> testCases = new ArrayList<>();
     for (TestParameters parameters :
-        TestParametersBuilder.builder().withAllRuntimes().withApiLevel(AndroidApiLevel.B).build()) {
+        TestParameters.builder().withAllRuntimes().withApiLevel(AndroidApiLevel.B).build()) {
       for (int i = 0; i < 16; ++i) {
         for (Location throwLocation : Location.values()) {
           if (throwLocation != Location.MAIN) {
diff --git a/src/test/java/com/android/tools/r8/desugar/DesugarToClassFileB156591935.java b/src/test/java/com/android/tools/r8/desugar/DesugarToClassFileB156591935.java
index 674f288..e288d12 100644
--- a/src/test/java/com/android/tools/r8/desugar/DesugarToClassFileB156591935.java
+++ b/src/test/java/com/android/tools/r8/desugar/DesugarToClassFileB156591935.java
@@ -8,7 +8,6 @@
 
 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.cf.code.CfStackInstruction.Opcode;
 import com.android.tools.r8.utils.AndroidApiLevel;
@@ -29,7 +28,7 @@
 
   @Parameterized.Parameters(name = "{0}")
   public static TestParametersCollection data() {
-    return TestParametersBuilder.builder().withCfRuntimes().withAllApiLevelsAlsoForCf().build();
+    return TestParameters.builder().withCfRuntimes().withAllApiLevelsAlsoForCf().build();
   }
 
   private final AndroidApiLevel apiLevel;
diff --git a/src/test/java/com/android/tools/r8/desugar/InterfaceInvokePrivateTest.java b/src/test/java/com/android/tools/r8/desugar/InterfaceInvokePrivateTest.java
index 86c5440..1d88df8 100644
--- a/src/test/java/com/android/tools/r8/desugar/InterfaceInvokePrivateTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/InterfaceInvokePrivateTest.java
@@ -3,7 +3,6 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.desugar;
 
-import static com.android.tools.r8.utils.InternalOptions.EXPERIMENTAL_CF_VERSION;
 import static org.hamcrest.CoreMatchers.containsString;
 import static org.junit.Assume.assumeTrue;
 
@@ -106,8 +105,6 @@
         // TODO(b/185463156): Not keeping I and its members will "fix" the ICCE for all runtimes.
         .addKeepClassAndMembersRules(I.class)
         .setMinApi(parameters.getApiLevel())
-        .allowDiagnosticWarningMessages(
-            inputCfVersion.isGreaterThanOrEqualTo(EXPERIMENTAL_CF_VERSION))
         .compile()
         .run(parameters.getRuntime(), TestRunner.class)
         .applyIf(
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/BufferedReaderTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/BufferedReaderTest.java
index f79b585..93ea904 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/BufferedReaderTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/BufferedReaderTest.java
@@ -51,13 +51,7 @@
   }
 
   private String expectedOutput() {
-    return StringUtils.lines(
-        "Hello",
-        "Larry",
-        "Page",
-        parameters.getApiLevel().isGreaterThanOrEqualTo(AndroidApiLevel.N)
-            ? "Caught java.io.UncheckedIOException"
-            : "Caught j$.io.UncheckedIOException");
+    return StringUtils.lines("Hello", "Larry", "Page", "Caught java.io.UncheckedIOException");
   }
 
   DesugaredLibrarySpecification configurationAlternative3(
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/LocalDateEpochTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/LocalDateEpochTest.java
new file mode 100644
index 0000000..23a146e
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/LocalDateEpochTest.java
@@ -0,0 +1,132 @@
+// Copyright (c) 2022, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.desugar.desugaredlibrary;
+
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.ToolHelper.DexVm.Version;
+import com.android.tools.r8.dex.Constants;
+import com.android.tools.r8.graph.DexField;
+import com.android.tools.r8.graph.DexString;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.FieldAccessFlags;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.DesugaredLibrarySpecification;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.humanspecification.HumanDesugaredLibrarySpecification;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.humanspecification.HumanRewritingFlags;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.humanspecification.HumanTopLevelFlags;
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.transformers.MethodTransformer;
+import com.android.tools.r8.utils.DescriptorUtils;
+import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.StringUtils;
+import com.google.common.collect.ImmutableList;
+import java.io.IOException;
+import java.time.LocalDate;
+import java.util.Collection;
+import org.junit.Assume;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class LocalDateEpochTest extends DesugaredLibraryTestBase {
+
+  private final TestParameters parameters;
+
+  private static final String EXPECTED_OUTPUT = StringUtils.lines("1970-01-01");
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters()
+        .withDexRuntimesStartingFromIncluding(Version.V8_1_0)
+        .withAllApiLevels()
+        .build();
+  }
+
+  public LocalDateEpochTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testD8() throws Exception {
+    testForD8(parameters.getBackend())
+        .addLibraryFiles(getLibraryFile())
+        .addProgramClasses(DesugarLocalDate.class)
+        .addProgramClassFileData(getMainClassFileData())
+        .setMinApi(parameters.getApiLevel())
+        .addOptionsModification(opt -> opt.setDesugaredLibrarySpecification(getSpecification(opt)))
+        .compile()
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutput(EXPECTED_OUTPUT);
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    Assume.assumeTrue(parameters.isDexRuntime());
+    testForR8(parameters.getBackend())
+        .addLibraryFiles(getLibraryFile())
+        .addProgramClasses(DesugarLocalDate.class)
+        .addProgramClassFileData(getMainClassFileData())
+        .addKeepMainRule(Main.class)
+        .setMinApi(parameters.getApiLevel())
+        .addOptionsModification(opt -> opt.setDesugaredLibrarySpecification(getSpecification(opt)))
+        .compile()
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutput(EXPECTED_OUTPUT);
+  }
+
+  private DesugaredLibrarySpecification getSpecification(InternalOptions options) {
+    DexType date = options.dexItemFactory().createType("Ljava/time/LocalDate;");
+    DexType desugarDate =
+        options
+            .dexItemFactory()
+            .createType("L" + DescriptorUtils.getClassBinaryName(DesugarLocalDate.class) + ";");
+    DexString epoch = options.dexItemFactory().createString("EPOCH");
+    DexField src = options.dexItemFactory().createField(date, date, epoch);
+    HumanRewritingFlags rewritingFlags =
+        HumanRewritingFlags.builder(options.reporter, Origin.unknown())
+            .retargetStaticField(src, desugarDate)
+            .amendLibraryField(
+                src,
+                FieldAccessFlags.fromSharedAccessFlags(
+                    Constants.ACC_PUBLIC | Constants.ACC_STATIC | Constants.ACC_FINAL))
+            .build();
+    return new HumanDesugaredLibrarySpecification(
+        HumanTopLevelFlags.testing(), rewritingFlags, false);
+  }
+
+  private Collection<byte[]> getMainClassFileData() throws IOException {
+    return ImmutableList.of(
+        transformer(Main.class)
+            .addMethodTransformer(
+                new MethodTransformer() {
+                  @Override
+                  public void visitFieldInsn(
+                      final int opcode,
+                      final String owner,
+                      final String name,
+                      final String descriptor) {
+                    if (name.equals("MIN")) {
+                      super.visitFieldInsn(opcode, owner, "EPOCH", descriptor);
+                    } else {
+                      super.visitFieldInsn(opcode, owner, name, descriptor);
+                    }
+                  }
+                })
+            .transform());
+  }
+
+  static class Main {
+
+    public static void main(String[] args) {
+      System.out.println(LocalDate.MIN);
+    }
+  }
+
+  static class DesugarLocalDate {
+
+    public static final LocalDate EPOCH = LocalDate.of(1970, 1, 1);
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/MergingJ$Test.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/MergingJ$Test.java
index 1208414..568de28 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/MergingJ$Test.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/MergingJ$Test.java
@@ -7,12 +7,14 @@
 import static com.android.tools.r8.DiagnosticsMatcher.diagnosticType;
 import static junit.framework.TestCase.assertNotNull;
 import static junit.framework.TestCase.assertTrue;
+import static org.hamcrest.CoreMatchers.containsString;
 import static org.junit.Assert.assertThrows;
 import static org.junit.Assert.fail;
 
 import com.android.tools.r8.CompilationFailedException;
 import com.android.tools.r8.D8Command;
 import com.android.tools.r8.DexFileMergerHelper;
+import com.android.tools.r8.DiagnosticsMatcher;
 import com.android.tools.r8.L8;
 import com.android.tools.r8.L8Command;
 import com.android.tools.r8.OutputMode;
@@ -24,8 +26,10 @@
 import com.android.tools.r8.errors.DuplicateTypesDiagnostic;
 import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
 import java.nio.file.Path;
+import java.util.List;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
@@ -127,4 +131,134 @@
             .build());
     return outputDex;
   }
+
+  @Test
+  public void testJavaMergesWithEverything() throws Exception {
+    // java and other prefixes can co-exist in same DEX.
+    Path dexWithJavaAndNonJ$Classes =
+        testForD8()
+            .addProgramClassFileData(
+                transformer(A.class).setClassDescriptor("Ljava/A;").transform())
+            .addInnerClasses(getClass())
+            .compile()
+            .writeToZip();
+
+    // Can be merged with other java classes.
+    testForD8()
+        .addProgramFiles(dexWithJavaAndNonJ$Classes)
+        .addProgramFiles(
+            testForD8()
+                .addProgramClassFileData(
+                    transformer(B.class).setClassDescriptor("Ljava/B;").transform())
+                .compile()
+                .writeToZip())
+        .compile();
+
+    // Cannot be merged with j$ classes.
+    Path j$Dex =
+        testForD8()
+            .addProgramClassFileData(transformer(A.class).setClassDescriptor("Lj$/A;").transform())
+            .compile()
+            .writeToZip();
+    assertThrows(
+        CompilationFailedException.class,
+        () ->
+            testForD8()
+                .addProgramFiles(dexWithJavaAndNonJ$Classes)
+                .addProgramFiles(j$Dex)
+                .compileWithExpectedDiagnostics(
+                    diagnostics ->
+                        diagnostics.assertErrorsMatch(
+                            DiagnosticsMatcher.diagnosticMessage(
+                                containsString(
+                                    "Merging DEX file containing classes with prefix")))));
+
+    // Cannot be merged with j$ even if they are together with java.
+    Path j$JavaDex =
+        testForD8()
+            .addProgramClassFileData(transformer(A.class).setClassDescriptor("Lj$/A;").transform())
+            .addProgramClassFileData(
+                transformer(B.class).setClassDescriptor("Ljava/B;").transform())
+            .compile()
+            .writeToZip();
+    assertThrows(
+        CompilationFailedException.class,
+        () ->
+            testForD8()
+                .addProgramFiles(dexWithJavaAndNonJ$Classes)
+                .addProgramFiles(j$JavaDex)
+                .compileWithExpectedDiagnostics(
+                    diagnostics ->
+                        diagnostics.assertErrorsMatch(
+                            DiagnosticsMatcher.diagnosticMessage(
+                                containsString(
+                                    "Merging DEX file containing classes with prefix")))));
+  }
+
+  @Test
+  public void testJ$OnlyMergesWithJava() throws Exception {
+    Path j$Dex =
+        testForD8()
+            .addProgramClassFileData(transformer(A.class).setClassDescriptor("Lj$/A;").transform())
+            .compile()
+            .writeToZip();
+    Path javaDex =
+        testForD8()
+            .addProgramClassFileData(
+                transformer(A.class).setClassDescriptor("Ljava/A;").transform())
+            .compile()
+            .writeToZip();
+    Path j$Dex2 =
+        testForD8()
+            .addProgramClassFileData(transformer(B.class).setClassDescriptor("Lj$/B;").transform())
+            .compile()
+            .writeToZip();
+    Path javaDex2 =
+        testForD8()
+            .addProgramClassFileData(
+                transformer(B.class).setClassDescriptor("Ljava/B;").transform())
+            .compile()
+            .writeToZip();
+    Path javaJ$Dex =
+        testForD8()
+            .addProgramClassFileData(transformer(C.class).setClassDescriptor("Lj$/C;").transform())
+            .addProgramClassFileData(
+                transformer(C.class).setClassDescriptor("Ljava/C;").transform())
+            .compile()
+            .writeToZip();
+
+    List<Path> j$Dexes = ImmutableList.of(j$Dex, j$Dex2, javaJ$Dex);
+    List<Path> allDexes = ImmutableList.of(j$Dex, javaDex, j$Dex2, javaDex2, javaJ$Dex);
+    testForD8().addProgramFiles(allDexes).compile();
+
+    for (Path first : allDexes) {
+      for (Path second : allDexes) {
+        if (first != second) {
+          testForD8().addProgramFiles(first, second).compile();
+        }
+      }
+      if (j$Dexes.contains(first)) {
+        assertThrows(
+            CompilationFailedException.class,
+            () ->
+                testForD8()
+                    .addInnerClasses(getClass())
+                    .addProgramFiles(first)
+                    .compileWithExpectedDiagnostics(
+                        diagnostics ->
+                            diagnostics.assertErrorsMatch(
+                                DiagnosticsMatcher.diagnosticMessage(
+                                    containsString(
+                                        "Merging DEX file containing classes with prefix")))));
+      } else {
+        testForD8().addInnerClasses(getClass()).addProgramFiles(first).compile();
+      }
+    }
+  }
+
+  static class A {}
+
+  static class B {}
+
+  static class C {}
 }
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/NeverMergeCoreLibDesugarClasses.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/NeverMergeCoreLibDesugarClasses.java
index 8a86def..607ff9b 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/NeverMergeCoreLibDesugarClasses.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/NeverMergeCoreLibDesugarClasses.java
@@ -52,8 +52,9 @@
                 assertThat(
                     message,
                     containsString(
-                        "Merging dex file containing classes with prefix 'j$.' "
-                            + "with classes with any other prefixes is not allowed"));
+                        "Merging DEX file containing classes with prefix 'j$.' "
+                            + "with other classes, except classes with prefix 'java.', "
+                            + "is not allowed:"));
               });
     } catch (CompilationFailedException e) {
       // Expected compilation failed.
@@ -78,8 +79,9 @@
                 assertThat(
                     message,
                     containsString(
-                        "Merging dex file containing classes with prefix 'j$.' "
-                            + "with classes with any other prefixes is not allowed"));
+                        "Merging DEX file containing classes with prefix 'j$.' "
+                            + "with other classes, except classes with prefix 'java.', "
+                            + "is not allowed:"));
               });
     } catch (CompilationFailedException e) {
       // Expected compilation failed.
diff --git a/src/test/java/com/android/tools/r8/desugar/jdk8272564/Jdk8272564Test.java b/src/test/java/com/android/tools/r8/desugar/jdk8272564/Jdk8272564Test.java
index 13e021d..15473c5 100644
--- a/src/test/java/com/android/tools/r8/desugar/jdk8272564/Jdk8272564Test.java
+++ b/src/test/java/com/android/tools/r8/desugar/jdk8272564/Jdk8272564Test.java
@@ -14,7 +14,6 @@
 import com.android.tools.r8.TestRuntime.CfVm;
 import com.android.tools.r8.examples.jdk18.jdk8272564.Jdk8272564;
 import com.android.tools.r8.utils.AndroidApiLevel;
-import com.android.tools.r8.utils.InternalOptions.TestingOptions;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.android.tools.r8.utils.codeinspector.InstructionSubject;
 import org.junit.Test;
@@ -160,7 +159,6 @@
         .setMinApi(parameters.getApiLevel())
         .noTreeShaking()
         .addKeepClassAndMembersRules(Jdk8272564.Main.typeName())
-        .addOptionsModification(TestingOptions::allowExperimentClassFileVersion)
         .run(parameters.getRuntime(), Jdk8272564.Main.typeName())
         .inspect(this::assertJdk8272564NotFixedCodeR8)
         .assertSuccess();
diff --git a/src/test/java/com/android/tools/r8/desugar/records/EmptyRecordAnnotationTest.java b/src/test/java/com/android/tools/r8/desugar/records/EmptyRecordAnnotationTest.java
index 1624c29..8de3689 100644
--- a/src/test/java/com/android/tools/r8/desugar/records/EmptyRecordAnnotationTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/records/EmptyRecordAnnotationTest.java
@@ -4,7 +4,6 @@
 
 package com.android.tools.r8.desugar.records;
 
-import static com.android.tools.r8.utils.InternalOptions.TestingOptions;
 import static org.junit.Assume.assumeTrue;
 
 import com.android.tools.r8.R8FullTestBuilder;
@@ -61,7 +60,6 @@
     testForD8(parameters.getBackend())
         .addProgramClassFileData(PROGRAM_DATA)
         .setMinApi(parameters.getApiLevel())
-        .addOptionsModification(TestingOptions::allowExperimentClassFileVersion)
         .compile()
         .run(parameters.getRuntime(), MAIN_TYPE)
         .assertSuccessWithOutput(EXPECTED_RESULT_DEX);
@@ -77,8 +75,7 @@
             .addKeepRules("-keep class records.EmptyRecordAnnotation { *; }")
             .addKeepRules("-keepattributes *Annotation*")
             .addKeepRules("-keep class records.EmptyRecordAnnotation$Empty")
-            .addKeepMainRule(MAIN_TYPE)
-            .addOptionsModification(TestingOptions::allowExperimentClassFileVersion);
+            .addKeepMainRule(MAIN_TYPE);
     if (parameters.isCfRuntime()) {
       builder
           .addLibraryFiles(RecordTestUtils.getJdk15LibraryFiles(temp))
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 5f1b68f..3e44232 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
@@ -4,8 +4,6 @@
 
 package com.android.tools.r8.desugar.records;
 
-import static com.android.tools.r8.utils.InternalOptions.TestingOptions;
-
 import com.android.tools.r8.R8FullTestBuilder;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
@@ -52,7 +50,6 @@
     testForD8(parameters.getBackend())
         .addProgramClassFileData(PROGRAM_DATA)
         .setMinApi(parameters.getApiLevel())
-        .addOptionsModification(TestingOptions::allowExperimentClassFileVersion)
         .compile()
         .run(parameters.getRuntime(), MAIN_TYPE)
         .assertSuccessWithOutput(EXPECTED_RESULT_D8);
@@ -64,8 +61,7 @@
         testForR8(parameters.getBackend())
             .addProgramClassFileData(PROGRAM_DATA)
             .setMinApi(parameters.getApiLevel())
-            .addKeepMainRule(MAIN_TYPE)
-            .addOptionsModification(TestingOptions::allowExperimentClassFileVersion);
+            .addKeepMainRule(MAIN_TYPE);
     if (parameters.isCfRuntime()) {
       builder
           .addLibraryFiles(RecordTestUtils.getJdk15LibraryFiles(temp))
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 743b2b4..f0d89d6 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
@@ -8,7 +8,6 @@
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestRuntime.CfVm;
-import com.android.tools.r8.utils.InternalOptions.TestingOptions;
 import com.android.tools.r8.utils.StringUtils;
 import java.util.List;
 import org.junit.Test;
@@ -50,7 +49,6 @@
     testForD8(parameters.getBackend())
         .addProgramClassFileData(PROGRAM_DATA)
         .setMinApi(parameters.getApiLevel())
-        .addOptionsModification(TestingOptions::allowExperimentClassFileVersion)
         .compile()
         .run(parameters.getRuntime(), MAIN_TYPE)
         .assertSuccessWithOutput(EXPECTED_RESULT);
@@ -62,8 +60,7 @@
         testForR8(parameters.getBackend())
             .addProgramClassFileData(PROGRAM_DATA)
             .setMinApi(parameters.getApiLevel())
-            .addKeepMainRule(MAIN_TYPE)
-            .addOptionsModification(TestingOptions::allowExperimentClassFileVersion);
+            .addKeepMainRule(MAIN_TYPE);
     if (parameters.isCfRuntime()) {
       builder
           .addLibraryFiles(RecordTestUtils.getJdk15LibraryFiles(temp))
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
index 9ee21d2..f6c39e6 100644
--- a/src/test/java/com/android/tools/r8/desugar/records/RecordInvokeCustomSplitDesugaringTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/records/RecordInvokeCustomSplitDesugaringTest.java
@@ -6,7 +6,6 @@
 
 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;
@@ -56,13 +55,11 @@
         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_D8);
@@ -74,14 +71,12 @@
         testForD8(Backend.CF)
             .addProgramClassFileData(PROGRAM_DATA)
             .setMinApi(parameters.getApiLevel())
-            .addOptionsModification(TestingOptions::allowExperimentClassFileVersion)
             .compile()
             .writeToZip();
     testForR8(parameters.getBackend())
         .addProgramFiles(desugared)
         .setMinApi(parameters.getApiLevel())
         .addKeepMainRule(MAIN_TYPE)
-        .addOptionsModification(TestingOptions::allowExperimentClassFileVersion)
         .compile()
         .run(parameters.getRuntime(), MAIN_TYPE)
         .assertSuccessWithOutput(EXPECTED_RESULT_R8);
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 61ee05b..b851c88 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
@@ -8,7 +8,6 @@
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestRuntime.CfVm;
-import com.android.tools.r8.utils.InternalOptions.TestingOptions;
 import com.android.tools.r8.utils.StringUtils;
 import java.util.List;
 import org.junit.Test;
@@ -67,7 +66,6 @@
     testForD8(parameters.getBackend())
         .addProgramClassFileData(PROGRAM_DATA)
         .setMinApi(parameters.getApiLevel())
-        .addOptionsModification(TestingOptions::allowExperimentClassFileVersion)
         .compile()
         .run(parameters.getRuntime(), MAIN_TYPE)
         .assertSuccessWithOutput(EXPECTED_RESULT_D8);
@@ -79,8 +77,7 @@
         testForR8(parameters.getBackend())
             .addProgramClassFileData(PROGRAM_DATA)
             .setMinApi(parameters.getApiLevel())
-            .addKeepMainRule(MAIN_TYPE)
-            .addOptionsModification(TestingOptions::allowExperimentClassFileVersion);
+            .addKeepMainRule(MAIN_TYPE);
     if (parameters.isCfRuntime()) {
       builder
           .addLibraryFiles(RecordTestUtils.getJdk15LibraryFiles(temp))
diff --git a/src/test/java/com/android/tools/r8/desugar/records/RecordKeepRulesTest.java b/src/test/java/com/android/tools/r8/desugar/records/RecordKeepRulesTest.java
index 49df3e2..f34bd67 100644
--- a/src/test/java/com/android/tools/r8/desugar/records/RecordKeepRulesTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/records/RecordKeepRulesTest.java
@@ -7,7 +7,6 @@
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.utils.BooleanUtils;
-import com.android.tools.r8.utils.InternalOptions.TestingOptions;
 import com.android.tools.r8.utils.StringUtils;
 import java.nio.file.Path;
 import java.util.List;
@@ -87,7 +86,6 @@
         .setMinApi(parameters.getApiLevel())
         .addKeepMainRule(MAIN_TYPE)
         .addKeepRules(keepRules)
-        .addOptionsModification(TestingOptions::allowExperimentClassFileVersion)
         .run(parameters.getRuntime(), MAIN_TYPE)
         .assertSuccessWithOutput(expectedOutput);
   }
@@ -100,13 +98,11 @@
             .addKeepMainRule(MAIN_TYPE)
             .addKeepRules(keepRules)
             .addLibraryFiles(RecordTestUtils.getJdk15LibraryFiles(temp))
-            .addOptionsModification(TestingOptions::allowExperimentClassFileVersion)
             .compile()
             .writeToZip();
     testForD8(parameters.getBackend())
         .addProgramFiles(desugared)
         .setMinApi(parameters.getApiLevel())
-        .addOptionsModification(TestingOptions::allowExperimentClassFileVersion)
         .run(parameters.getRuntime(), MAIN_TYPE)
         .assertSuccessWithOutput(expectedOutput);
   }
diff --git a/src/test/java/com/android/tools/r8/desugar/records/RecordLibMergeTest.java b/src/test/java/com/android/tools/r8/desugar/records/RecordLibMergeTest.java
index 53eff63..1701934 100644
--- a/src/test/java/com/android/tools/r8/desugar/records/RecordLibMergeTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/records/RecordLibMergeTest.java
@@ -8,7 +8,6 @@
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestRuntime.CfVm;
-import com.android.tools.r8.utils.InternalOptions.TestingOptions;
 import com.android.tools.r8.utils.StringUtils;
 import java.nio.file.Path;
 import java.util.List;
@@ -52,7 +51,6 @@
                 "-keep class records.RecordLib { public static java.lang.Object getRecord(); }")
             .addKeepRules("-keep class records.RecordLib$LibRecord")
             .addLibraryFiles(RecordTestUtils.getJdk15LibraryFiles(temp))
-            .addOptionsModification(TestingOptions::allowExperimentClassFileVersion)
             .compile()
             .writeToZip();
     R8FullTestBuilder builder =
@@ -62,8 +60,7 @@
             .setMinApi(parameters.getApiLevel())
             .addKeepMainRule(MAIN_TYPE)
             .addKeepRules("-keep class records.RecordLib$LibRecord")
-            .addKeepRules("-keep class records.RecordMain$MainRecord")
-            .addOptionsModification(TestingOptions::allowExperimentClassFileVersion);
+            .addKeepRules("-keep class records.RecordMain$MainRecord");
     if (parameters.isCfRuntime()) {
       builder
           .addLibraryFiles(RecordTestUtils.getJdk15LibraryFiles(temp))
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 cf3c955..d3816f8 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
@@ -19,7 +19,6 @@
 import com.android.tools.r8.errors.DuplicateTypesDiagnostic;
 import com.android.tools.r8.errors.MissingGlobalSyntheticsConsumerDiagnostic;
 import com.android.tools.r8.synthesis.globals.GlobalSyntheticsConsumerAndProvider;
-import com.android.tools.r8.utils.InternalOptions.TestingOptions;
 import com.android.tools.r8.utils.StringUtils;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import java.nio.file.Path;
@@ -62,7 +61,6 @@
             testForD8(parameters.getBackend())
                 .addProgramClassFileData(PROGRAM_DATA_1)
                 .setMinApi(parameters.getApiLevel())
-                .addOptionsModification(TestingOptions::allowExperimentClassFileVersion)
                 .setIntermediate(true)
                 .compileWithExpectedDiagnostics(
                     diagnostics ->
@@ -79,7 +77,6 @@
         testForD8(parameters.getBackend())
             .addProgramClassFileData(PROGRAM_DATA_1)
             .setMinApi(parameters.getApiLevel())
-            .addOptionsModification(TestingOptions::allowExperimentClassFileVersion)
             .setIntermediate(true)
             .apply(b -> b.getBuilder().setGlobalSyntheticsConsumer(globals1))
             .compile()
@@ -91,7 +88,6 @@
         testForD8(parameters.getBackend())
             .addProgramClassFileData(PROGRAM_DATA_2)
             .setMinApi(parameters.getApiLevel())
-            .addOptionsModification(TestingOptions::allowExperimentClassFileVersion)
             .setIntermediate(true)
             .apply(b -> b.getBuilder().setGlobalSyntheticsConsumer(globals2))
             .compile()
@@ -106,7 +102,6 @@
             .addProgramFiles(output1, output2)
             .apply(b -> b.getBuilder().addGlobalSyntheticsResourceProviders(globals1, globals2))
             .setMinApi(parameters.getApiLevel())
-            .addOptionsModification(TestingOptions::allowExperimentClassFileVersion)
             .compile()
             .inspect(this::assertHasRecordTag);
 
@@ -121,7 +116,6 @@
         testForD8(parameters.getBackend())
             .addProgramClassFileData(PROGRAM_DATA_1)
             .setMinApi(parameters.getApiLevel())
-            .addOptionsModification(TestingOptions::allowExperimentClassFileVersion)
             .setIntermediate(true)
             .apply(b -> b.getBuilder().setGlobalSyntheticsConsumer(globals1))
             .compile()
@@ -133,7 +127,6 @@
             .apply(b -> b.getBuilder().addGlobalSyntheticsResourceProviders(globals1))
             .addProgramClassFileData(PROGRAM_DATA_2)
             .setMinApi(parameters.getApiLevel())
-            .addOptionsModification(TestingOptions::allowExperimentClassFileVersion)
             .compile();
     result.run(parameters.getRuntime(), MAIN_TYPE_1).assertSuccessWithOutput(EXPECTED_RESULT_1);
     result.run(parameters.getRuntime(), MAIN_TYPE_2).assertSuccessWithOutput(EXPECTED_RESULT_2);
@@ -145,7 +138,6 @@
         testForD8(parameters.getBackend())
             .addProgramClassFileData(PROGRAM_DATA_1)
             .setMinApi(parameters.getApiLevel())
-            .addOptionsModification(TestingOptions::allowExperimentClassFileVersion)
             .compile()
             .inspect(this::assertHasRecordTag)
             .writeToZip();
@@ -154,7 +146,6 @@
         testForD8(parameters.getBackend())
             .addProgramClassFileData(PROGRAM_DATA_2)
             .setMinApi(parameters.getApiLevel())
-            .addOptionsModification(TestingOptions::allowExperimentClassFileVersion)
             .compile()
             .inspect(this::assertHasRecordTag)
             .writeToZip();
@@ -165,7 +156,6 @@
             testForD8(parameters.getBackend())
                 .addProgramFiles(output1, output2)
                 .setMinApi(parameters.getApiLevel())
-                .addOptionsModification(TestingOptions::allowExperimentClassFileVersion)
                 .compileWithExpectedDiagnostics(
                     diagnostics ->
                         diagnostics
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 7b1096d..1bdc08d 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
@@ -7,7 +7,6 @@
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestRuntime.CfVm;
-import com.android.tools.r8.utils.InternalOptions.TestingOptions;
 import com.android.tools.r8.utils.StringUtils;
 import java.util.List;
 import org.junit.Test;
@@ -61,7 +60,6 @@
         .addKeepRules("-keepattributes *")
         .addKeepRules("-keep class * extends java.lang.Record { private final <fields>; }")
         .addLibraryFiles(RecordTestUtils.getJdk15LibraryFiles(temp))
-        .addOptionsModification(TestingOptions::allowExperimentClassFileVersion)
         .compile()
         .inspect(RecordTestUtils::assertRecordsAreRecords)
         .enableJVMPreview()
diff --git a/src/test/java/com/android/tools/r8/desugar/records/RecordShrinkFieldTest.java b/src/test/java/com/android/tools/r8/desugar/records/RecordShrinkFieldTest.java
index 8dc4a82..2c426fc 100644
--- a/src/test/java/com/android/tools/r8/desugar/records/RecordShrinkFieldTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/records/RecordShrinkFieldTest.java
@@ -8,7 +8,6 @@
 
 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 com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
@@ -47,7 +46,6 @@
     testForD8(parameters.getBackend())
         .addProgramClassFileData(PROGRAM_DATA)
         .setMinApi(parameters.getApiLevel())
-        .addOptionsModification(TestingOptions::allowExperimentClassFileVersion)
         .compile()
         .run(parameters.getRuntime(), MAIN_TYPE)
         .assertSuccessWithOutput(EXPECTED_RESULT_D8);
@@ -59,7 +57,6 @@
         .addProgramClassFileData(PROGRAM_DATA)
         .setMinApi(parameters.getApiLevel())
         .addKeepMainRule(MAIN_TYPE)
-        .addOptionsModification(TestingOptions::allowExperimentClassFileVersion)
         .compile()
         .inspect(this::assertSingleField)
         .run(parameters.getRuntime(), MAIN_TYPE)
@@ -74,14 +71,12 @@
             .setMinApi(parameters.getApiLevel())
             .addKeepMainRule(MAIN_TYPE)
             .addLibraryFiles(RecordTestUtils.getJdk15LibraryFiles(temp))
-            .addOptionsModification(TestingOptions::allowExperimentClassFileVersion)
             .compile()
             .writeToZip();
     testForR8(parameters.getBackend())
         .addProgramFiles(desugared)
         .setMinApi(parameters.getApiLevel())
         .addKeepMainRule(MAIN_TYPE)
-        .addOptionsModification(TestingOptions::allowExperimentClassFileVersion)
         .compile()
         .inspect(this::assertSingleField)
         .run(parameters.getRuntime(), MAIN_TYPE)
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 eb8ef2d..07ae7cb 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
@@ -8,7 +8,6 @@
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestRuntime.CfVm;
-import com.android.tools.r8.utils.InternalOptions.TestingOptions;
 import com.android.tools.r8.utils.StringUtils;
 import java.util.List;
 import org.junit.Test;
@@ -52,7 +51,6 @@
     testForD8(parameters.getBackend())
         .addProgramClassFileData(PROGRAM_DATA)
         .setMinApi(parameters.getApiLevel())
-        .addOptionsModification(TestingOptions::allowExperimentClassFileVersion)
         .compile()
         .run(parameters.getRuntime(), MAIN_TYPE)
         .assertSuccessWithOutput(EXPECTED_RESULT);
@@ -64,8 +62,7 @@
         testForR8(parameters.getBackend())
             .addProgramClassFileData(PROGRAM_DATA)
             .setMinApi(parameters.getApiLevel())
-            .addKeepMainRule(MAIN_TYPE)
-            .addOptionsModification(TestingOptions::allowExperimentClassFileVersion);
+            .addKeepMainRule(MAIN_TYPE);
     if (parameters.isCfRuntime()) {
       builder
           .addLibraryFiles(RecordTestUtils.getJdk15LibraryFiles(temp))
diff --git a/src/test/java/com/android/tools/r8/desugar/records/RecordWithNonMaterializableConstClassTest.java b/src/test/java/com/android/tools/r8/desugar/records/RecordWithNonMaterializableConstClassTest.java
index f76b137..7ee039f 100644
--- a/src/test/java/com/android/tools/r8/desugar/records/RecordWithNonMaterializableConstClassTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/records/RecordWithNonMaterializableConstClassTest.java
@@ -6,7 +6,6 @@
 
 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;
@@ -56,7 +55,6 @@
         .addProgramClassFileData(PROGRAM_DATA)
         .addProgramClassFileData(EXTRA_DATA)
         .setMinApi(parameters.getApiLevel())
-        .addOptionsModification(TestingOptions::allowExperimentClassFileVersion)
         .compile()
         .run(parameters.getRuntime(), MAIN_TYPE)
         .assertSuccessWithOutput(EXPECTED_RESULT_D8);
@@ -70,7 +68,6 @@
         .setMinApi(parameters.getApiLevel())
         .addKeepMainRule(MAIN_TYPE)
         .addKeepRules("-keep class " + PRIVATE_CLASS_NAME)
-        .addOptionsModification(TestingOptions::allowExperimentClassFileVersion)
         .compile()
         .run(parameters.getRuntime(), MAIN_TYPE)
         .assertSuccessWithOutput(EXPECTED_RESULT_R8);
@@ -86,7 +83,6 @@
             .addKeepMainRule(MAIN_TYPE)
             .addKeepRules("-keep class " + PRIVATE_CLASS_NAME)
             .addLibraryFiles(RecordTestUtils.getJdk15LibraryFiles(temp))
-            .addOptionsModification(TestingOptions::allowExperimentClassFileVersion)
             .compile()
             .writeToZip();
     testForR8(parameters.getBackend())
@@ -94,7 +90,6 @@
         .setMinApi(parameters.getApiLevel())
         .addKeepMainRule(MAIN_TYPE)
         .addKeepRules("-keep class " + PRIVATE_CLASS_NAME)
-        .addOptionsModification(TestingOptions::allowExperimentClassFileVersion)
         .compile()
         .run(parameters.getRuntime(), MAIN_TYPE)
         .assertSuccessWithOutput(EXPECTED_RESULT_R8);
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 212098c..e94b6b6 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
@@ -10,11 +10,9 @@
 import com.android.tools.r8.R8FullTestBuilder;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.TestRuntime.CfRuntime;
 import com.android.tools.r8.TestRuntime.CfVm;
 import com.android.tools.r8.synthesis.globals.GlobalSyntheticsConsumerAndProvider;
 import com.android.tools.r8.utils.AndroidApiLevel;
-import com.android.tools.r8.utils.InternalOptions.TestingOptions;
 import com.android.tools.r8.utils.StringUtils;
 import java.nio.file.Path;
 import java.util.List;
@@ -66,7 +64,6 @@
     testForD8(parameters.getBackend())
         .addProgramClassFileData(PROGRAM_DATA)
         .setMinApi(parameters.getApiLevel())
-        .addOptionsModification(TestingOptions::allowExperimentClassFileVersion)
         .compile()
         .inspectWithOptions(
             RecordTestUtils::assertNoJavaLangRecord,
@@ -110,7 +107,6 @@
     return testForD8(Backend.DEX)
         .addProgramClassFileData(PROGRAM_DATA)
         .setMinApi(parameters.getApiLevel())
-        .addOptionsModification(TestingOptions::allowExperimentClassFileVersion)
         .setIntermediate(true)
         .setIncludeClassesChecksum(true)
         .apply(b -> b.getBuilder().setGlobalSyntheticsConsumer(globalSyntheticsConsumer))
@@ -125,8 +121,7 @@
         testForR8(parameters.getBackend())
             .addProgramClassFileData(PROGRAM_DATA)
             .setMinApi(parameters.getApiLevel())
-            .addKeepMainRule(MAIN_TYPE)
-            .addOptionsModification(TestingOptions::allowExperimentClassFileVersion);
+            .addKeepMainRule(MAIN_TYPE);
     if (parameters.isCfRuntime()) {
       builder
           .addLibraryFiles(RecordTestUtils.getJdk15LibraryFiles(temp))
@@ -153,8 +148,7 @@
             .addProgramClassFileData(PROGRAM_DATA)
             .noMinification()
             .setMinApi(parameters.getApiLevel())
-            .addKeepMainRule(MAIN_TYPE)
-            .addOptionsModification(TestingOptions::allowExperimentClassFileVersion);
+            .addKeepMainRule(MAIN_TYPE);
     if (parameters.isCfRuntime()) {
       builder
           .addLibraryFiles(RecordTestUtils.getJdk15LibraryFiles(temp))
diff --git a/src/test/java/com/android/tools/r8/desugar/records/UnusedRecordFieldTest.java b/src/test/java/com/android/tools/r8/desugar/records/UnusedRecordFieldTest.java
index 24ae5d7..bd59f23 100644
--- a/src/test/java/com/android/tools/r8/desugar/records/UnusedRecordFieldTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/records/UnusedRecordFieldTest.java
@@ -4,8 +4,6 @@
 
 package com.android.tools.r8.desugar.records;
 
-import static com.android.tools.r8.utils.InternalOptions.TestingOptions;
-
 import com.android.tools.r8.R8FullTestBuilder;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
@@ -51,7 +49,6 @@
     testForD8(parameters.getBackend())
         .addProgramClassFileData(PROGRAM_DATA)
         .setMinApi(parameters.getApiLevel())
-        .addOptionsModification(TestingOptions::allowExperimentClassFileVersion)
         .compile()
         .run(parameters.getRuntime(), MAIN_TYPE)
         .assertSuccessWithOutput(EXPECTED_RESULT);
@@ -64,8 +61,7 @@
             .addProgramClassFileData(PROGRAM_DATA)
             .setMinApi(parameters.getApiLevel())
             .addKeepRules("-keep class records.UnusedRecordField { *; }")
-            .addKeepMainRule(MAIN_TYPE)
-            .addOptionsModification(TestingOptions::allowExperimentClassFileVersion);
+            .addKeepMainRule(MAIN_TYPE);
     if (parameters.isCfRuntime()) {
       builder
           .addLibraryFiles(RecordTestUtils.getJdk15LibraryFiles(temp))
diff --git a/src/test/java/com/android/tools/r8/desugar/records/UnusedRecordMethodTest.java b/src/test/java/com/android/tools/r8/desugar/records/UnusedRecordMethodTest.java
index 702cc75..70ed745 100644
--- a/src/test/java/com/android/tools/r8/desugar/records/UnusedRecordMethodTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/records/UnusedRecordMethodTest.java
@@ -4,8 +4,6 @@
 
 package com.android.tools.r8.desugar.records;
 
-import static com.android.tools.r8.utils.InternalOptions.TestingOptions;
-
 import com.android.tools.r8.R8FullTestBuilder;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
@@ -51,7 +49,6 @@
     testForD8(parameters.getBackend())
         .addProgramClassFileData(PROGRAM_DATA)
         .setMinApi(parameters.getApiLevel())
-        .addOptionsModification(TestingOptions::allowExperimentClassFileVersion)
         .compile()
         .run(parameters.getRuntime(), MAIN_TYPE)
         .assertSuccessWithOutput(EXPECTED_RESULT);
@@ -64,8 +61,7 @@
             .addProgramClassFileData(PROGRAM_DATA)
             .setMinApi(parameters.getApiLevel())
             .addKeepRules("-keep class records.UnusedRecordMethod { *; }")
-            .addKeepMainRule(MAIN_TYPE)
-            .addOptionsModification(TestingOptions::allowExperimentClassFileVersion);
+            .addKeepMainRule(MAIN_TYPE);
     if (parameters.isCfRuntime()) {
       builder
           .addLibraryFiles(RecordTestUtils.getJdk15LibraryFiles(temp))
diff --git a/src/test/java/com/android/tools/r8/desugar/records/UnusedRecordReflectionTest.java b/src/test/java/com/android/tools/r8/desugar/records/UnusedRecordReflectionTest.java
index 0687934..066c68b 100644
--- a/src/test/java/com/android/tools/r8/desugar/records/UnusedRecordReflectionTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/records/UnusedRecordReflectionTest.java
@@ -4,8 +4,6 @@
 
 package com.android.tools.r8.desugar.records;
 
-import static com.android.tools.r8.utils.InternalOptions.TestingOptions;
-
 import com.android.tools.r8.R8FullTestBuilder;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
@@ -51,7 +49,6 @@
     testForD8(parameters.getBackend())
         .addProgramClassFileData(PROGRAM_DATA)
         .setMinApi(parameters.getApiLevel())
-        .addOptionsModification(TestingOptions::allowExperimentClassFileVersion)
         .compile()
         .run(parameters.getRuntime(), MAIN_TYPE)
         .assertSuccessWithOutput(EXPECTED_RESULT);
@@ -64,8 +61,7 @@
             .addProgramClassFileData(PROGRAM_DATA)
             .setMinApi(parameters.getApiLevel())
             .addKeepRules("-keep class records.UnusedRecordReflection { *; }")
-            .addKeepMainRule(MAIN_TYPE)
-            .addOptionsModification(TestingOptions::allowExperimentClassFileVersion);
+            .addKeepMainRule(MAIN_TYPE);
     if (parameters.isCfRuntime()) {
       builder
           .addLibraryFiles(RecordTestUtils.getJdk15LibraryFiles(temp))
diff --git a/src/test/java/com/android/tools/r8/desugar/sealed/SealedAttributeClasspathTest.java b/src/test/java/com/android/tools/r8/desugar/sealed/SealedAttributeClasspathTest.java
index a74132f..facd808 100644
--- a/src/test/java/com/android/tools/r8/desugar/sealed/SealedAttributeClasspathTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/sealed/SealedAttributeClasspathTest.java
@@ -10,7 +10,6 @@
 import com.android.tools.r8.TestCompilerBuilder;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.examples.jdk17.Sealed;
-import com.android.tools.r8.utils.InternalOptions.TestingOptions;
 import java.util.List;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -35,7 +34,6 @@
         .addClasspathFiles(Sealed.jar())
         .addInnerClasses(getClass())
         .setMinApi(parameters.getApiLevel())
-        .addOptionsModification(TestingOptions::allowExperimentClassFileVersion)
         .run(parameters.getRuntime(), TestRunner.class)
         .assertSuccessWithOutputLines("Hello, world!");
   }
diff --git a/src/test/java/com/android/tools/r8/desugar/sealed/SealedAttributeLibraryTest.java b/src/test/java/com/android/tools/r8/desugar/sealed/SealedAttributeLibraryTest.java
index 4eb4bd2..57684f3 100644
--- a/src/test/java/com/android/tools/r8/desugar/sealed/SealedAttributeLibraryTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/sealed/SealedAttributeLibraryTest.java
@@ -10,7 +10,6 @@
 import com.android.tools.r8.TestCompilerBuilder;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.examples.jdk17.Sealed;
-import com.android.tools.r8.utils.InternalOptions.TestingOptions;
 import java.util.List;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -36,7 +35,6 @@
         .addLibraryFiles(Sealed.jar())
         .addInnerClasses(getClass())
         .setMinApi(parameters.getApiLevel())
-        .addOptionsModification(TestingOptions::allowExperimentClassFileVersion)
         .run(parameters.getRuntime(), TestRunner.class)
         .assertSuccessWithOutputLines("Hello, world!");
   }
diff --git a/src/test/java/com/android/tools/r8/examples/abstractmethodremoval/AbstractMethodRemovalTestRunner.java b/src/test/java/com/android/tools/r8/examples/abstractmethodremoval/AbstractMethodRemovalTestRunner.java
index 20cda19..39d93b9 100644
--- a/src/test/java/com/android/tools/r8/examples/abstractmethodremoval/AbstractMethodRemovalTestRunner.java
+++ b/src/test/java/com/android/tools/r8/examples/abstractmethodremoval/AbstractMethodRemovalTestRunner.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.ToolHelper.DexVm.Version;
 import com.android.tools.r8.examples.abstractmethodremoval.a.PackageBase;
 import com.android.tools.r8.examples.abstractmethodremoval.a.Public;
 import com.android.tools.r8.examples.abstractmethodremoval.b.Impl1;
@@ -66,11 +65,6 @@
         .addKeepMainRule(getMainClass())
         .setMinApi(parameters.getApiLevel())
         .run(parameters.getRuntime(), getMainClass())
-        .applyIf(
-            // TODO(b/227302144): The program should not fail after R8.
-            parameters.isDexRuntime()
-                && parameters.asDexRuntime().getVersion().isNewerThanOrEqual(Version.V5_1_1),
-            r -> r.assertFailure(),
-            r -> r.assertSuccessWithOutput(getExpected()));
+        .assertSuccessWithOutput(getExpected());
   }
 }
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/MemberValuePropagationTest.java b/src/test/java/com/android/tools/r8/ir/optimize/MemberValuePropagationTest.java
index 5162100..1433f2e 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/MemberValuePropagationTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/MemberValuePropagationTest.java
@@ -7,7 +7,6 @@
 
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.TestParametersBuilder;
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.utils.FileUtils;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
@@ -38,7 +37,7 @@
   @Parameterized.Parameters(name = "Backend: {1}")
   public static List<Object[]> data() {
     return buildParameters(
-        TestParametersBuilder.builder().withNoneRuntime().build(), ToolHelper.getBackends());
+        TestParameters.builder().withNoneRuntime().build(), ToolHelper.getBackends());
   }
 
   public MemberValuePropagationTest(TestParameters parameters, TestBase.Backend backend) {
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/interfaces/OpenInterfaceCheckCastTest.java b/src/test/java/com/android/tools/r8/ir/optimize/interfaces/OpenInterfaceCheckCastTest.java
index c756f8c..e8d4e5f 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/interfaces/OpenInterfaceCheckCastTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/interfaces/OpenInterfaceCheckCastTest.java
@@ -41,7 +41,7 @@
         .addProgramClasses(getProgramClasses())
         .addProgramClassFileData(getTransformedMainClass())
         .run(parameters.getRuntime(), Main.class)
-        .assertSuccessWithOutputLines(getExpectedOutputLines());
+        .assertSuccessWithOutputLines(getExpectedOutputLines(false));
   }
 
   @Test
@@ -52,7 +52,7 @@
         .addProgramClassFileData(getTransformedMainClass())
         .setMinApi(parameters.getApiLevel())
         .run(parameters.getRuntime(), Main.class)
-        .assertSuccessWithOutputLines(getExpectedOutputLines());
+        .assertSuccessWithOutputLines(getExpectedOutputLines(false));
   }
 
   @Test
@@ -68,7 +68,7 @@
         .enableNoVerticalClassMergingAnnotations()
         .setMinApi(parameters.getApiLevel())
         .run(parameters.getRuntime(), Main.class)
-        .assertSuccessWithOutputLines(getExpectedOutputLines());
+        .assertSuccessWithOutputLines(getExpectedOutputLines(true));
   }
 
   private List<Class<?>> getProgramClasses() {
@@ -102,7 +102,11 @@
         .transform();
   }
 
-  private List<String> getExpectedOutputLines() {
+  private List<String> getExpectedOutputLines(boolean isR8) {
+    if (isR8) {
+      // TODO(b/214496607): R8 should not optimize the check-cast instruction since I is open.
+      return ImmutableList.of("OK", "OK");
+    }
     if (parameters.isDexRuntime()
         && parameters
             .getDexRuntimeVersion()
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/interfaces/OpenInterfaceInliningTest.java b/src/test/java/com/android/tools/r8/ir/optimize/interfaces/OpenInterfaceInliningTest.java
index 4eaa6a4..e2b4a29 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/interfaces/OpenInterfaceInliningTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/interfaces/OpenInterfaceInliningTest.java
@@ -71,7 +71,8 @@
         .enableNoVerticalClassMergingAnnotations()
         .setMinApi(parameters.getApiLevel())
         .run(parameters.getRuntime(), Main.class)
-        .assertSuccessWithOutputLines(getExpectedOutputLines());
+        // TODO(b/214496607): Should succeed with the expected output.
+        .assertFailureWithErrorThatThrows(ClassCastException.class);
   }
 
   private List<Class<?>> getProgramClasses() {
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/interfaces/OpenInterfaceInstanceofTest.java b/src/test/java/com/android/tools/r8/ir/optimize/interfaces/OpenInterfaceInstanceofTest.java
index dac3848..5a57dc4 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/interfaces/OpenInterfaceInstanceofTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/interfaces/OpenInterfaceInstanceofTest.java
@@ -41,7 +41,7 @@
         .addProgramClasses(getProgramClasses())
         .addProgramClassFileData(getTransformedMainClass())
         .run(parameters.getRuntime(), Main.class)
-        .assertSuccessWithOutputLines(getExpectedOutputLines());
+        .assertSuccessWithOutputLines(getExpectedOutputLines(false));
   }
 
   @Test
@@ -52,7 +52,7 @@
         .addProgramClassFileData(getTransformedMainClass())
         .setMinApi(parameters.getApiLevel())
         .run(parameters.getRuntime(), Main.class)
-        .assertSuccessWithOutputLines(getExpectedOutputLines());
+        .assertSuccessWithOutputLines(getExpectedOutputLines(false));
   }
 
   @Test
@@ -68,7 +68,7 @@
         .enableNoVerticalClassMergingAnnotations()
         .setMinApi(parameters.getApiLevel())
         .run(parameters.getRuntime(), Main.class)
-        .assertSuccessWithOutputLines(getExpectedOutputLines());
+        .assertSuccessWithOutputLines(getExpectedOutputLines(true));
   }
 
   private List<Class<?>> getProgramClasses() {
@@ -95,7 +95,11 @@
         .transform();
   }
 
-  private List<String> getExpectedOutputLines() {
+  private List<String> getExpectedOutputLines(boolean isR8) {
+    if (isR8) {
+      // TODO(b/214496607): R8 should not optimize the instanceof instruction since I is open.
+      return ImmutableList.of("true", "true");
+    }
     if (parameters.isDexRuntime() && parameters.getDexRuntimeVersion().isEqualTo(Version.V7_0_0)) {
       return ImmutableList.of("true", "true");
     }
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/interfaces/OpenUninstantiatedInterfaceInstanceofTest.java b/src/test/java/com/android/tools/r8/ir/optimize/interfaces/OpenUninstantiatedInterfaceInstanceofTest.java
index 606871f..a729899 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/interfaces/OpenUninstantiatedInterfaceInstanceofTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/interfaces/OpenUninstantiatedInterfaceInstanceofTest.java
@@ -40,7 +40,7 @@
         .addProgramClasses(getProgramClasses())
         .addProgramClassFileData(getTransformedMainClass())
         .run(parameters.getRuntime(), Main.class)
-        .assertSuccessWithOutputLines(getExpectedOutputLines());
+        .assertSuccessWithOutputLines(getExpectedOutputLines(false));
   }
 
   @Test
@@ -51,7 +51,7 @@
         .addProgramClassFileData(getTransformedMainClass())
         .setMinApi(parameters.getApiLevel())
         .run(parameters.getRuntime(), Main.class)
-        .assertSuccessWithOutputLines(getExpectedOutputLines());
+        .assertSuccessWithOutputLines(getExpectedOutputLines(false));
   }
 
   @Test
@@ -65,7 +65,7 @@
         .enableInliningAnnotations()
         .setMinApi(parameters.getApiLevel())
         .run(parameters.getRuntime(), Main.class)
-        .assertSuccessWithOutputLines(getExpectedOutputLines());
+        .assertSuccessWithOutputLines(getExpectedOutputLines(true));
   }
 
   private List<Class<?>> getProgramClasses() {
@@ -92,7 +92,11 @@
         .transform();
   }
 
-  private List<String> getExpectedOutputLines() {
+  private List<String> getExpectedOutputLines(boolean isR8) {
+    if (isR8) {
+      // TODO(b/214496607): R8 should not optimize the instanceof instruction since I is open.
+      return ImmutableList.of("true");
+    }
     if (parameters.isDexRuntime()) {
       if (parameters.getDexRuntimeVersion().isEqualTo(Version.V7_0_0)
           || parameters.getDexRuntimeVersion().isEqualTo(Version.V13_MASTER)) {
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/lambda/LambdaMethodInliningTest.java b/src/test/java/com/android/tools/r8/ir/optimize/lambda/LambdaMethodInliningTest.java
index a3d1e6b..a63addb 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/lambda/LambdaMethodInliningTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/lambda/LambdaMethodInliningTest.java
@@ -65,7 +65,7 @@
     assertTrue(
         testClassMethodSubject
             .streamInstructions()
-            .noneMatch(
+            .anyMatch(
                 instruction ->
                     instruction.isInvokeVirtual()
                         && instruction.getMethod().toSourceString().contains("println")));
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/uninstantiatedtypes/LambdaInstantiatedTypeTest.java b/src/test/java/com/android/tools/r8/ir/optimize/uninstantiatedtypes/LambdaInstantiatedTypeTest.java
index 1b3e93e..4d032c7 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/uninstantiatedtypes/LambdaInstantiatedTypeTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/uninstantiatedtypes/LambdaInstantiatedTypeTest.java
@@ -9,7 +9,6 @@
 import com.android.tools.r8.NeverInline;
 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.utils.StringUtils;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
@@ -30,7 +29,7 @@
 
   @Parameters(name = "{0}")
   public static TestParametersCollection data() {
-    return TestParametersBuilder.builder().withAllRuntimesAndApiLevels().build();
+    return TestParameters.builder().withAllRuntimesAndApiLevels().build();
   }
 
   public LambdaInstantiatedTypeTest(TestParameters parameters) {
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/unusedarguments/UnusedArgumentsTestBase.java b/src/test/java/com/android/tools/r8/ir/optimize/unusedarguments/UnusedArgumentsTestBase.java
index d88a978..822f9f9 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/unusedarguments/UnusedArgumentsTestBase.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/unusedarguments/UnusedArgumentsTestBase.java
@@ -11,7 +11,6 @@
 import com.android.tools.r8.R8FullTestBuilder;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.TestParametersBuilder;
 import com.android.tools.r8.utils.BooleanUtils;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
@@ -36,8 +35,7 @@
   @Parameters(name = "{0}, minification:{1}")
   public static Collection<Object[]> data() {
     return buildParameters(
-        TestParametersBuilder.builder().withAllRuntimesAndApiLevels().build(),
-        BooleanUtils.values());
+        TestParameters.builder().withAllRuntimesAndApiLevels().build(), BooleanUtils.values());
   }
 
   public void configure(R8FullTestBuilder builder) {
diff --git a/src/test/java/com/android/tools/r8/jasmin/InvalidClassNames.java b/src/test/java/com/android/tools/r8/jasmin/InvalidClassNames.java
index bd462b7..b8378c3 100644
--- a/src/test/java/com/android/tools/r8/jasmin/InvalidClassNames.java
+++ b/src/test/java/com/android/tools/r8/jasmin/InvalidClassNames.java
@@ -3,8 +3,7 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.jasmin;
 
-
-import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.TestParameters;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
@@ -23,32 +22,19 @@
   @Parameters(name = "\"{0}\", jvm: {1}, art: {2}")
   public static Collection<Object[]> data() {
     Collection<Object[]> data = new ArrayList<>();
-    data.addAll(NameTestBase.getCommonNameTestData(true));
-    data.addAll(
-        Arrays.asList(
-            new Object[][] {
-              {new TestString("a/b/c/a/D/"), true, false},
-              {
-                new TestString("a<b"),
-                !ToolHelper.isWindows() && !ToolHelper.isJava9Runtime(),
-                false
-              },
-              {
-                new TestString("a>b"),
-                !ToolHelper.isWindows() && !ToolHelper.isJava9Runtime(),
-                false
-              },
-              {
-                new TestString("<a>b"),
-                !ToolHelper.isWindows() && !ToolHelper.isJava9Runtime(),
-                false
-              },
-              {
-                new TestString("<a>"),
-                !ToolHelper.isWindows() && !ToolHelper.isJava9Runtime(),
-                false
-              }
-            }));
+    for (TestParameters parameter : TestParameters.justNoneRuntime()) {
+      parameter.assertNoneRuntime();
+      data.addAll(NameTestBase.getCommonNameTestData());
+      data.addAll(
+          Arrays.asList(
+              new Object[][] {
+                {new TestString("a/b/c/a/D/"), true, false},
+                {new TestString("a<b"), false, false},
+                {new TestString("a>b"), false, false},
+                {new TestString("<a>b"), false, false},
+                {new TestString("<a>"), false, false}
+              }));
+    }
     return data;
   }
 
diff --git a/src/test/java/com/android/tools/r8/jasmin/InvalidFieldNames.java b/src/test/java/com/android/tools/r8/jasmin/InvalidFieldNames.java
index 8f680ee..cea2557 100644
--- a/src/test/java/com/android/tools/r8/jasmin/InvalidFieldNames.java
+++ b/src/test/java/com/android/tools/r8/jasmin/InvalidFieldNames.java
@@ -3,7 +3,7 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.jasmin;
 
-import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.TestParameters;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
@@ -21,17 +21,20 @@
   @Parameters(name = "\"{0}\", jvm: {1}, art: {2}")
   public static Collection<Object[]> data() {
     Collection<Object[]> data = new ArrayList<>();
-    data.addAll(NameTestBase.getCommonNameTestData(false));
-    data.addAll(
-        Arrays.asList(
-            new Object[][] {
-              {new TestString("a/b"), false, false},
-              {new TestString("<a"), !ToolHelper.isJava9Runtime(), false},
-              {new TestString("a>"), !ToolHelper.isJava9Runtime(), false},
-              {new TestString("a<b>"), !ToolHelper.isJava9Runtime(), false},
-              {new TestString("<a>b"), !ToolHelper.isJava9Runtime(), false},
-              {new TestString("<a>"), false, true}
-            }));
+    for (TestParameters parameter : TestParameters.justNoneRuntime()) {
+      parameter.assertNoneRuntime();
+      data.addAll(NameTestBase.getCommonNameTestData());
+      data.addAll(
+          Arrays.asList(
+              new Object[][] {
+                {new TestString("a/b"), false, false},
+                {new TestString("<a"), false, false},
+                {new TestString("a>"), false, false},
+                {new TestString("a<b>"), false, false},
+                {new TestString("<a>b"), false, false},
+                {new TestString("<a>"), false, true}
+              }));
+    }
     return data;
   }
 
diff --git a/src/test/java/com/android/tools/r8/jasmin/InvalidMethodNames.java b/src/test/java/com/android/tools/r8/jasmin/InvalidMethodNames.java
index fa2882e..0327b76 100644
--- a/src/test/java/com/android/tools/r8/jasmin/InvalidMethodNames.java
+++ b/src/test/java/com/android/tools/r8/jasmin/InvalidMethodNames.java
@@ -3,8 +3,7 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.jasmin;
 
-
-import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.TestParameters;
 import com.google.common.collect.ImmutableList;
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -27,15 +26,18 @@
   @Parameters(name = "\"{0}\", jvm: {1}, art: {2}")
   public static Collection<Object[]> data() {
     Collection<Object[]> data = new ArrayList<>();
-    data.addAll(NameTestBase.getCommonNameTestData(false));
-    data.addAll(
-        Arrays.asList(
-            new Object[][] {
-              {new TestString("a/b"), false, false},
-              {new TestString("<a"), false, false},
-              {new TestString("a>"), !ToolHelper.isJava9Runtime(), false},
-              {new TestString("<a>"), false, false}
-            }));
+    for (TestParameters parameter : TestParameters.justNoneRuntime()) {
+      parameter.assertNoneRuntime();
+      data.addAll(NameTestBase.getCommonNameTestData());
+      data.addAll(
+          Arrays.asList(
+              new Object[][] {
+                {new TestString("a/b"), false, false},
+                {new TestString("<a"), false, false},
+                {new TestString("a>"), false, false},
+                {new TestString("<a>"), false, false}
+              }));
+    }
     return data;
   }
 
diff --git a/src/test/java/com/android/tools/r8/jasmin/NameTestBase.java b/src/test/java/com/android/tools/r8/jasmin/NameTestBase.java
index 594bdbe..0b335a9 100644
--- a/src/test/java/com/android/tools/r8/jasmin/NameTestBase.java
+++ b/src/test/java/com/android/tools/r8/jasmin/NameTestBase.java
@@ -39,51 +39,43 @@
   // - Name (String) to test (can be class name, field name, method name).
   // - boolean, whether it runs on the JVM.
   // - boolean, whether it runs on the ART.
-  static Collection<Object[]> getCommonNameTestData(boolean classNames) {
-
-    boolean windowsSensitive = classNames && ToolHelper.isWindows();
+  static Collection<Object[]> getCommonNameTestData() {
     boolean supportSpaces = ToolHelper.getMinApiLevelForDexVm().getLevel()
         >= AndroidApiLevel.R.getLevel();
-    boolean java9 = ToolHelper.isJava9Runtime();
-
     return Arrays.asList(
         new Object[][] {
           {new TestString("azAZ09$_"), true, true},
-          {new TestString("_"), !java9, true},
-          {new TestString("a-b"), !java9, true},
-          {new TestString("\u00a0"), !java9, supportSpaces},
-          {new TestString("\u00a1"), !java9, true},
-          {new TestString("\u1fff"), !java9, true},
-          {new TestString("\u2000"), !windowsSensitive && !java9, supportSpaces},
-          {new TestString("\u200f"), !windowsSensitive && !java9, false},
-          {new TestString("\u2010"), !windowsSensitive && !java9, true},
-          {new TestString("\u2027"), !windowsSensitive && !java9, true},
-          {new TestString("\u2028"), !windowsSensitive && !java9, false},
-          {new TestString("\u202f"), !windowsSensitive && !java9, supportSpaces},
-          {new TestString("\u2030"), !windowsSensitive && !java9, true},
-          {new TestString("\ud7ff"), !windowsSensitive && !java9, true},
-          {new TestString("\ue000"), !windowsSensitive && !java9, true},
-          {new TestString("\uffef"), !windowsSensitive && !java9, true},
-          {new TestString("\ufff0"), !windowsSensitive && !java9, false},
-          {new TestString("\uffff"), !windowsSensitive && !java9, false},
+          {new TestString("_"), false, true},
+          {new TestString("a-b"), false, true},
+          {new TestString("\u00a0"), false, supportSpaces},
+          {new TestString("\u00a1"), false, true},
+          {new TestString("\u1fff"), false, true},
+          {new TestString("\u2000"), false, supportSpaces},
+          {new TestString("\u200f"), false, false},
+          {new TestString("\u2010"), false, true},
+          {new TestString("\u2027"), false, true},
+          {new TestString("\u2028"), false, false},
+          {new TestString("\u202f"), false, supportSpaces},
+          {new TestString("\u2030"), false, true},
+          {new TestString("\ud7ff"), false, true},
+          {new TestString("\ue000"), false, true},
+          {new TestString("\uffef"), false, true},
+          {new TestString("\ufff0"), false, false},
+          {new TestString("\uffff"), false, false},
 
           // Standalone high and low surrogates.
-          {new TestString("\ud800"), !classNames && !java9, false},
-          {new TestString("\udbff"), !classNames && !java9, false},
-          {new TestString("\udc00"), !classNames && !java9, false},
-          {new TestString("\udfff"), !classNames && !java9, false},
+          {new TestString("\ud800"), false, false},
+          {new TestString("\udbff"), false, false},
+          {new TestString("\udc00"), false, false},
+          {new TestString("\udfff"), false, false},
 
           // Single and double code points above 0x10000.
           {new TestString("\ud800\udc00"), true, true},
           {new TestString("\ud800\udcfa"), true, true},
-          {new TestString("\ud800\udcfb"), !windowsSensitive && !java9, true},
-          {new TestString("\udbff\udfff"), !windowsSensitive && !java9, true},
+          {new TestString("\ud800\udcfb"), false, true},
+          {new TestString("\udbff\udfff"), false, true},
           {new TestString("\ud800\udc00\ud800\udcfa"), true, true},
-          {
-            new TestString("\ud800\udc00\udbff\udfff"),
-            !windowsSensitive && !java9,
-            true
-          }
+          {new TestString("\ud800\udc00\udbff\udfff"), false, true}
         });
   }
 
diff --git a/src/test/java/com/android/tools/r8/java_language/pattern_matching_for_instenceof/PattternMatchingForInstanceOfTest.java b/src/test/java/com/android/tools/r8/java_language/pattern_matching_for_instenceof/PattternMatchingForInstanceOfTest.java
index e81d20c..b40cca0 100644
--- a/src/test/java/com/android/tools/r8/java_language/pattern_matching_for_instenceof/PattternMatchingForInstanceOfTest.java
+++ b/src/test/java/com/android/tools/r8/java_language/pattern_matching_for_instenceof/PattternMatchingForInstanceOfTest.java
@@ -9,7 +9,6 @@
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestRuntime.CfVm;
 import com.android.tools.r8.examples.jdk17.PatternMatchingForInstanceof;
-import com.android.tools.r8.utils.InternalOptions.TestingOptions;
 import com.google.common.collect.ImmutableList;
 import java.nio.file.Path;
 import java.util.List;
@@ -50,7 +49,6 @@
     testForD8(parameters.getBackend())
         .addProgramFiles(JAR)
         .setMinApi(parameters.getApiLevel())
-        .addOptionsModification(TestingOptions::allowExperimentClassFileVersion)
         .compile()
         .run(parameters.getRuntime(), MAIN)
         .assertSuccessWithOutputLines(EXPECTED);
@@ -62,8 +60,7 @@
         testForR8(parameters.getBackend())
             .addProgramFiles(JAR)
             .setMinApi(parameters.getApiLevel())
-            .addKeepMainRule(MAIN)
-            .addOptionsModification(TestingOptions::allowExperimentClassFileVersion);
+            .addKeepMainRule(MAIN);
     if (parameters.getBackend().isDex()) {
       builder.run(parameters.getRuntime(), MAIN).assertSuccessWithOutputLines(EXPECTED);
     } else {
diff --git a/src/test/java/com/android/tools/r8/kotlin/SimplifyIfNotNullKotlinTest.java b/src/test/java/com/android/tools/r8/kotlin/SimplifyIfNotNullKotlinTest.java
index c14b357..488271b 100644
--- a/src/test/java/com/android/tools/r8/kotlin/SimplifyIfNotNullKotlinTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/SimplifyIfNotNullKotlinTest.java
@@ -73,9 +73,8 @@
                       .count();
               long paramNullCheckCount =
                   countCall(testMethod, "Intrinsics", "checkParameterIsNotNull");
-              // TODO(b/214496607): Should be one after Iterator#hasNext, and another in the filter
-              //  predicate: sinceYear != null.
-              assertEquals(testParameters.isCfRuntime() ? 5 : 2, ifzCount);
+              // One after Iterator#hasNext, and another in the filter predicate: sinceYear != null.
+              assertEquals(2, ifzCount);
               assertEquals(0, paramNullCheckCount);
             });
   }
diff --git a/src/test/java/com/android/tools/r8/resolution/PublicFieldInnerClassTest.java b/src/test/java/com/android/tools/r8/resolution/PublicFieldInnerClassTest.java
index 4fb3d8b..11f560d 100644
--- a/src/test/java/com/android/tools/r8/resolution/PublicFieldInnerClassTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/PublicFieldInnerClassTest.java
@@ -6,7 +6,6 @@
 import com.android.tools.r8.CompilationMode;
 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.utils.StringUtils;
 import org.junit.Test;
@@ -75,7 +74,7 @@
 
   @Parameterized.Parameters(name = "Backend: {0}")
   public static TestParametersCollection data() {
-    return TestParametersBuilder.builder().withAllRuntimesAndApiLevels().build();
+    return TestParameters.builder().withAllRuntimesAndApiLevels().build();
   }
 
   public PublicFieldInnerClassTest(TestParameters parameters) {
diff --git a/src/test/java/com/android/tools/r8/resolution/SingleTargetExecutionTest.java b/src/test/java/com/android/tools/r8/resolution/SingleTargetExecutionTest.java
index 1446e76..4726fe4 100644
--- a/src/test/java/com/android/tools/r8/resolution/SingleTargetExecutionTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/SingleTargetExecutionTest.java
@@ -84,6 +84,10 @@
   }
 
   private String getExpectedOutput() {
+    String icceOrNot =
+        enableInliningAnnotations || !parameters.canUseDefaultAndStaticInterfaceMethods()
+            ? "ICCE"
+            : "InterfaceWithDefault";
     return StringUtils.lines(
         "SubSubClassOne",
         "SubSubClassOne",
@@ -93,7 +97,7 @@
         "com.android.tools.r8.resolution.singletarget.one.AbstractSubClass",
         "InterfaceWithDefault",
         "InterfaceWithDefault",
-        "ICCE",
+        icceOrNot,
         "com.android.tools.r8.resolution.singletarget.one.SubSubClassTwo",
         "com.android.tools.r8.resolution.singletarget.one.SubSubClassTwo",
         "AbstractTopClass",
@@ -105,7 +109,7 @@
         "InterfaceWithDefault",
         "InterfaceWithDefault",
         "InterfaceWithDefault",
-        "ICCE",
+        icceOrNot,
         "InterfaceWithDefault",
         "com.android.tools.r8.resolution.singletarget.one.SubSubClassTwo",
         "InterfaceWithDefault",
diff --git a/src/test/java/com/android/tools/r8/resolution/b123730538/B123730538.java b/src/test/java/com/android/tools/r8/resolution/b123730538/B123730538.java
index c61d7fa..029fc63 100644
--- a/src/test/java/com/android/tools/r8/resolution/b123730538/B123730538.java
+++ b/src/test/java/com/android/tools/r8/resolution/b123730538/B123730538.java
@@ -11,7 +11,6 @@
 
 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 com.android.tools.r8.resolution.b123730538.runner.PublicClassExtender;
@@ -40,7 +39,7 @@
 
   @Parameterized.Parameters(name = "Backend: {0}")
   public static TestParametersCollection data() {
-    return TestParametersBuilder.builder().withAllRuntimesAndApiLevels().build();
+    return TestParameters.builder().withAllRuntimesAndApiLevels().build();
   }
 
   public B123730538(TestParameters parameters) {
diff --git a/src/test/java/com/android/tools/r8/resource/KeepDirectoriesTest.java b/src/test/java/com/android/tools/r8/resource/KeepDirectoriesTest.java
index 416835f..fedfb29 100644
--- a/src/test/java/com/android/tools/r8/resource/KeepDirectoriesTest.java
+++ b/src/test/java/com/android/tools/r8/resource/KeepDirectoriesTest.java
@@ -16,7 +16,6 @@
 import com.android.tools.r8.DiagnosticsHandler;
 import com.android.tools.r8.R8Command;
 import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.TestParametersBuilder;
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.naming.ClassNameMapper;
@@ -55,7 +54,7 @@
     return buildParameters(
         ToolHelper.getBackends(),
         BooleanUtils.values(),
-        TestParametersBuilder.builder().withNoneRuntime().build());
+        TestParameters.builder().withNoneRuntime().build());
   }
 
   public KeepDirectoriesTest(Backend backend, boolean minify, TestParameters parameters) {
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
index 4dcd86a..9196e6a 100644
--- a/src/test/java/com/android/tools/r8/retrace/api/RetraceApiTestBase.java
+++ b/src/test/java/com/android/tools/r8/retrace/api/RetraceApiTestBase.java
@@ -6,7 +6,6 @@
 
 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 org.junit.Test;
 import org.junit.runner.RunWith;
@@ -18,7 +17,7 @@
 
   @Parameters(name = "{0}")
   public static TestParametersCollection data() {
-    return TestParametersBuilder.builder().withNoneRuntime().build();
+    return TestParameters.builder().withNoneRuntime().build();
   }
 
   public RetraceApiTestBase(TestParameters parameters) {
diff --git a/src/test/java/com/android/tools/r8/rewrite/switchmaps/RewriteSwitchMapsTest.java b/src/test/java/com/android/tools/r8/rewrite/switchmaps/RewriteSwitchMapsTest.java
index cfffbac..c6c5cc2 100644
--- a/src/test/java/com/android/tools/r8/rewrite/switchmaps/RewriteSwitchMapsTest.java
+++ b/src/test/java/com/android/tools/r8/rewrite/switchmaps/RewriteSwitchMapsTest.java
@@ -8,7 +8,6 @@
 
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.TestParametersBuilder;
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.utils.AndroidApiLevel;
 import com.google.common.collect.ImmutableList;
@@ -26,7 +25,7 @@
   @Parameterized.Parameters(name = "{0}, backend: {1}")
   public static List<Object[]> data() {
     return buildParameters(
-        TestParametersBuilder.builder().withNoneRuntime().build(), ToolHelper.getBackends());
+        TestParameters.builder().withNoneRuntime().build(), ToolHelper.getBackends());
   }
 
   public RewriteSwitchMapsTest(TestParameters parameters, Backend backend) {
diff --git a/src/test/java/com/android/tools/r8/shaking/TreeShakingSpecificTest.java b/src/test/java/com/android/tools/r8/shaking/TreeShakingSpecificTest.java
index f42443d..eacb522 100644
--- a/src/test/java/com/android/tools/r8/shaking/TreeShakingSpecificTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/TreeShakingSpecificTest.java
@@ -14,7 +14,6 @@
 import com.android.tools.r8.CompilationFailedException;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.TestParametersBuilder;
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.diagnostic.MissingDefinitionsDiagnostic;
 import com.android.tools.r8.utils.AndroidApiLevel;
@@ -43,8 +42,7 @@
 
   @Parameters(name = "Backend: {1}")
   public static List<Object[]> data() {
-    return buildParameters(
-        TestParametersBuilder.builder().withNoneRuntime().build(), Backend.values());
+    return buildParameters(TestParameters.builder().withNoneRuntime().build(), Backend.values());
   }
 
   public TreeShakingSpecificTest(TestParameters parameters, Backend backend) {
diff --git a/src/test/java/com/android/tools/r8/shaking/keptgraph/KeptByReachableSubclassTest.java b/src/test/java/com/android/tools/r8/shaking/keptgraph/KeptByReachableSubclassTest.java
index b848b1c..ae37674 100644
--- a/src/test/java/com/android/tools/r8/shaking/keptgraph/KeptByReachableSubclassTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/keptgraph/KeptByReachableSubclassTest.java
@@ -11,7 +11,6 @@
 import com.android.tools.r8.NoVerticalClassMerging;
 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.origin.Origin;
 import com.android.tools.r8.references.Reference;
@@ -33,7 +32,7 @@
 
   @Parameters(name = "{0}")
   public static TestParametersCollection data() {
-    return TestParametersBuilder.builder().withCfRuntimes().build();
+    return TestParameters.builder().withCfRuntimes().build();
   }
 
   public KeptByReachableSubclassTest(TestParameters parameters) {
diff --git a/src/test/java/com/android/tools/r8/shaking/keptgraph/KeptSingletonIsNotCyclicTest.java b/src/test/java/com/android/tools/r8/shaking/keptgraph/KeptSingletonIsNotCyclicTest.java
index b34d36d..f6f59dd 100644
--- a/src/test/java/com/android/tools/r8/shaking/keptgraph/KeptSingletonIsNotCyclicTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/keptgraph/KeptSingletonIsNotCyclicTest.java
@@ -14,7 +14,6 @@
 import com.android.tools.r8.NeverPropagateValue;
 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.origin.Origin;
 import com.android.tools.r8.references.ClassReference;
@@ -41,7 +40,7 @@
 
   @Parameters(name = "{0}")
   public static TestParametersCollection data() {
-    return TestParametersBuilder.builder().withCfRuntimes().build();
+    return TestParameters.builder().withCfRuntimes().build();
   }
 
   public KeptSingletonIsNotCyclicTest(TestParameters parameters) {
diff --git a/src/test/java/com/android/tools/r8/shaking/keptgraph/KeptSubclassKeepsSuperTest.java b/src/test/java/com/android/tools/r8/shaking/keptgraph/KeptSubclassKeepsSuperTest.java
index 12d1f35..cd34b55 100644
--- a/src/test/java/com/android/tools/r8/shaking/keptgraph/KeptSubclassKeepsSuperTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/keptgraph/KeptSubclassKeepsSuperTest.java
@@ -11,7 +11,6 @@
 import com.android.tools.r8.NoVerticalClassMerging;
 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.origin.Origin;
 import com.android.tools.r8.references.Reference;
@@ -33,7 +32,7 @@
 
   @Parameters(name = "{0}")
   public static TestParametersCollection data() {
-    return TestParametersBuilder.builder().withCfRuntimes().build();
+    return TestParameters.builder().withCfRuntimes().build();
   }
 
   public KeptSubclassKeepsSuperTest(TestParameters parameters) {
diff --git a/src/test/java/com/android/tools/r8/shaking/proxy/MockitoTest.java b/src/test/java/com/android/tools/r8/shaking/proxy/MockitoTest.java
index 1464e82..d31da79 100644
--- a/src/test/java/com/android/tools/r8/shaking/proxy/MockitoTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/proxy/MockitoTest.java
@@ -3,6 +3,7 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.shaking.proxy;
 
+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.isPresentAndNotRenamed;
 import static org.hamcrest.MatcherAssert.assertThat;
@@ -57,11 +58,8 @@
             .inspector();
     ClassSubject itf = inspector.clazz(M_I);
     assertThat(itf, isPresent());
-    // TODO(b/214496607): This could be removed as a result of devirtualization, but this only
-    //  happens if the call site is reprocessed, since we don't have knowledge of open/closed
-    //  interfaces until the second optimization pass.
     MethodSubject mtd = itf.uniqueMethodWithName("onEnterForeground");
-    assertThat(mtd, isPresent());
+    assertThat(mtd, isAbsent());
   }
 
   @Test
diff --git a/src/test/java/com/android/tools/r8/shaking/proxy/ProxiesTest.java b/src/test/java/com/android/tools/r8/shaking/proxy/ProxiesTest.java
index c850ae8..047a109 100644
--- a/src/test/java/com/android/tools/r8/shaking/proxy/ProxiesTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/proxy/ProxiesTest.java
@@ -139,8 +139,8 @@
   private void noInterfaceKept(CodeInspector inspector) {
     // Indirectly assert that method is inlined into x, y and z and that redundant field loads
     // remove invokes.
-    assertEquals(3, countInstructionInX(inspector, InstructionSubject::isInvokeInterface));
-    assertEquals(3, countInstructionInY(inspector, InstructionSubject::isInvokeInterface));
+    assertEquals(0, countInstructionInX(inspector, InstructionSubject::isInvokeInterface));
+    assertEquals(0, countInstructionInY(inspector, InstructionSubject::isInvokeInterface));
     assertEquals(0, countInstructionInZ(inspector, InstructionSubject::isInvokeVirtual));
   }
 
@@ -149,10 +149,7 @@
     runTest(
         ImmutableList.of(),
         this::noInterfaceKept,
-        "TestClass 1\nTestClass 1\nTestClass 1\nProxy\nProxy\nProxy\n"
-            + "TestClass 2\nTestClass 2\nTestClass 2\nProxy\nProxy\nProxy\n"
-            + "TestClass 3\nTestClass 3\nTestClass 3\n"
-            + "TestClass 4\nTestClass 4\nTestClass 4\nSUCCESS\n");
+        "TestClass 1\nTestClass 1\nTestClass 1\nEXCEPTION\n");
   }
 
   private void baseInterfaceKept(CodeInspector inspector) {
@@ -160,7 +157,7 @@
     assertEquals(3, countInstructionInX(inspector, InstructionSubject::isInvokeInterface));
     // Indirectly assert that method is inlined into y and z and that redundant field loads
     // remove invokes.
-    assertEquals(3, countInstructionInY(inspector, InstructionSubject::isInvokeInterface));
+    assertEquals(0, countInstructionInY(inspector, InstructionSubject::isInvokeInterface));
     assertEquals(0, countInstructionInZ(inspector, InstructionSubject::isInvokeVirtual));
     assertEquals(0, countInstructionInZSubClass(inspector, InstructionSubject::isInvokeVirtual));
   }
@@ -174,9 +171,7 @@
             "}"),
         this::baseInterfaceKept,
         "TestClass 1\nTestClass 1\nTestClass 1\nProxy\nProxy\nProxy\n"
-            + "TestClass 2\nTestClass 2\nTestClass 2\nProxy\nProxy\nProxy\n"
-            + "TestClass 3\nTestClass 3\nTestClass 3\n"
-            + "TestClass 4\nTestClass 4\nTestClass 4\nSUCCESS\n");
+            + "TestClass 2\nTestClass 2\nTestClass 2\nEXCEPTION\n");
   }
 
   private void subInterfaceKept(CodeInspector inspector) {
diff --git a/src/test/java/com/android/tools/r8/synthesis/SyntheticMarkerCfTest.java b/src/test/java/com/android/tools/r8/synthesis/SyntheticMarkerCfTest.java
new file mode 100644
index 0000000..706ace9
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/synthesis/SyntheticMarkerCfTest.java
@@ -0,0 +1,261 @@
+// Copyright (c) 2022, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.synthesis;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThrows;
+
+import com.android.tools.r8.ByteDataView;
+import com.android.tools.r8.ClassFileConsumer;
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.D8TestBuilder;
+import com.android.tools.r8.DiagnosticsHandler;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.transformers.ClassTransformer;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.Box;
+import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.StringUtils;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.objectweb.asm.Attribute;
+import org.objectweb.asm.ByteVector;
+import org.objectweb.asm.ClassReader;
+import org.objectweb.asm.ClassVisitor;
+import org.objectweb.asm.ClassWriter;
+import org.objectweb.asm.Label;
+
+@RunWith(Parameterized.class)
+public class SyntheticMarkerCfTest extends TestBase {
+
+  static final String EXPECTED = StringUtils.lines("Hello, world");
+
+  private final TestParameters parameters;
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters()
+        .withDefaultCfRuntime()
+        .withApiLevel(AndroidApiLevel.B)
+        .enableApiLevelsForCf()
+        .build();
+  }
+
+  public SyntheticMarkerCfTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  /**
+   * Mirror of the initial D8 synthetic marker format.
+   *
+   * <p>The legacy marker just had the synthetic kind id as payload.
+   */
+  private static class SyntheticMarkerV1 extends Attribute {
+    static final SyntheticMarkerV1 PROTO = new SyntheticMarkerV1((short) 0);
+
+    final short kindId;
+
+    public SyntheticMarkerV1(short kindId) {
+      super("com.android.tools.r8.SynthesizedClass");
+      this.kindId = kindId;
+    }
+
+    @Override
+    protected Attribute read(
+        ClassReader classReader,
+        int offset,
+        int length,
+        char[] charBuffer,
+        int codeAttributeOffset,
+        Label[] labels) {
+      short id = classReader.readShort(offset);
+      return new SyntheticMarkerV1(id);
+    }
+
+    @Override
+    protected ByteVector write(
+        ClassWriter classWriter, byte[] code, int codeLength, int maxStack, int maxLocals) {
+      ByteVector byteVector = new ByteVector();
+      byteVector.putShort(kindId);
+      return byteVector;
+    }
+  }
+
+  /**
+   * Format of the current synthetic marker.
+   *
+   * <p>The marker is distinguished by a new attribute type name.
+   *
+   * <p>The payload is the kind id, version hash length and then version hash bytes.
+   */
+  private static class SyntheticMarkerV2 extends Attribute {
+    static final SyntheticMarkerV2 PROTO = new SyntheticMarkerV2((short) 0, null);
+
+    final short kindId;
+    final byte[] versionHash;
+
+    public SyntheticMarkerV2(short kindId, byte[] versionHash) {
+      super("com.android.tools.r8.SynthesizedClassV2");
+      this.versionHash = versionHash;
+      this.kindId = kindId;
+    }
+
+    @Override
+    protected Attribute read(
+        ClassReader classReader,
+        int offset,
+        int length,
+        char[] charBuffer,
+        int codeAttributeOffset,
+        Label[] labels) {
+      short kindId = classReader.readShort(offset);
+      offset += 2;
+      short versionLength = classReader.readShort(offset);
+      offset += 2;
+      byte[] versionBytes = new byte[versionLength];
+      for (int i = 0; i < versionLength; i++) {
+        versionBytes[i] = (byte) classReader.readByte(offset++);
+      }
+      return new SyntheticMarkerV2(kindId, versionBytes);
+    }
+
+    @Override
+    protected ByteVector write(
+        ClassWriter classWriter, byte[] code, int codeLength, int maxStack, int maxLocals) {
+      ByteVector byteVector = new ByteVector();
+      byteVector.putShort(kindId);
+      byteVector.putShort(versionHash.length);
+      byteVector.putByteArray(versionHash, 0, versionHash.length);
+      return byteVector;
+    }
+  }
+
+  private static List<Attribute> readAttributes(byte[] bytes) {
+    List<Attribute> attributes = new ArrayList<>();
+    ClassReader reader = new ClassReader(bytes);
+    reader.accept(
+        new ClassVisitor(InternalOptions.ASM_VERSION) {
+          @Override
+          public void visit(
+              int version,
+              int access,
+              String name,
+              String signature,
+              String superName,
+              String[] interfaces) {
+            super.visit(version, access, name, signature, superName, interfaces);
+          }
+
+          @Override
+          public void visitAttribute(Attribute attribute) {
+            attributes.add(attribute);
+          }
+        },
+        new Attribute[] {SyntheticMarkerV1.PROTO, SyntheticMarkerV2.PROTO},
+        ClassReader.SKIP_CODE | ClassReader.SKIP_FRAMES | ClassReader.SKIP_DEBUG);
+    return attributes;
+  }
+
+  private byte[] getTestClassWithMarker(Attribute marker) throws IOException {
+    return transformer(TestClass.class)
+        .addClassTransformer(
+            new ClassTransformer() {
+              @Override
+              public void visit(
+                  int version,
+                  int access,
+                  String name,
+                  String signature,
+                  String superName,
+                  String[] interfaces) {
+                super.visit(version, access, name, signature, superName, interfaces);
+                super.visitAttribute(marker);
+              }
+            })
+        .transform();
+  }
+
+  /** Test that reads the correct marker from a compilation unit and fails if then manipulated. */
+  @Test
+  public void testInvalidMarkerFailsCompilation() throws Exception {
+    Box<SyntheticMarkerV2> currentCompilerMarker = new Box<>();
+    testForD8(parameters.getBackend())
+        .addProgramClasses(TestClass.class)
+        .setMinApi(parameters.getApiLevel())
+        .setIntermediate(true)
+        .setProgramConsumer(
+            new ClassFileConsumer() {
+              private final List<Attribute> attributes = new ArrayList<>();
+
+              @Override
+              public void accept(ByteDataView data, String descriptor, DiagnosticsHandler handler) {
+                attributes.addAll(readAttributes(data.copyByteData()));
+              }
+
+              @Override
+              public void finished(DiagnosticsHandler handler) {
+                assertEquals(1, attributes.size());
+                assertEquals(SyntheticMarkerV2.PROTO.type, attributes.get(0).type);
+                currentCompilerMarker.set((SyntheticMarkerV2) attributes.get(0));
+              }
+            })
+        .compile()
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutput(EXPECTED);
+
+    // Test that if a "current" marker with invalid content is given to the compiler it will
+    // cause a failure. This test ensures that we can witness the markers being ignored in the
+    // tests below.
+    D8TestBuilder builder =
+        testForD8(parameters.getBackend())
+            .setMinApi(parameters.getApiLevel())
+            .addProgramClassFileData(
+                getTestClassWithMarker(
+                    new SyntheticMarkerV2(
+                        Short.MAX_VALUE, currentCompilerMarker.get().versionHash)));
+    assertThrows(CompilationFailedException.class, builder::compile);
+  }
+
+  @Test
+  public void testIgnoreV1Markers() throws Exception {
+    // Test that inputs with a legacy marker will be ignored.
+    // We do so by injecting an old marker and put in a non-valid ID which would cause the compiler
+    // to fail if it was read.
+    testForD8(parameters.getBackend())
+        .setMinApi(parameters.getApiLevel())
+        .addProgramClassFileData(getTestClassWithMarker(new SyntheticMarkerV1(Short.MAX_VALUE)))
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutput(EXPECTED);
+  }
+
+  @Test
+  public void testIgnorePreviousV2Markers() throws Exception {
+    // Test that inputs with a V2 marker from a previous compiler version are ignored.
+    // We do so by injecting an old marker and put in a non-valid ID which would cause the compiler
+    // to fail if it was read.
+    testForD8(parameters.getBackend())
+        .setMinApi(parameters.getApiLevel())
+        .addProgramClassFileData(
+            getTestClassWithMarker(new SyntheticMarkerV2(Short.MAX_VALUE, new byte[0])))
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutput(EXPECTED);
+  }
+
+  public static class TestClass {
+
+    public static void run(Runnable r) {
+      r.run();
+    }
+
+    public static void main(String[] args) {
+      run(() -> System.out.println("Hello, world"));
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/synthesis/SyntheticMarkerDexTest.java b/src/test/java/com/android/tools/r8/synthesis/SyntheticMarkerDexTest.java
new file mode 100644
index 0000000..5b00775
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/synthesis/SyntheticMarkerDexTest.java
@@ -0,0 +1,89 @@
+// Copyright (c) 2022, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.synthesis;
+
+import static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.graph.DexAnnotation;
+import com.android.tools.r8.graph.DexEncodedAnnotation;
+import com.android.tools.r8.references.Reference;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import java.nio.file.Path;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class SyntheticMarkerDexTest extends TestBase {
+
+  static final String EXPECTED = StringUtils.lines("Hello, world");
+
+  private final TestParameters parameters;
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withDefaultDexRuntime().withApiLevel(AndroidApiLevel.B).build();
+  }
+
+  public SyntheticMarkerDexTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void test() throws Exception {
+    Path out =
+        testForD8(parameters.getBackend())
+            .setMinApi(parameters.getApiLevel())
+            .setIntermediate(true)
+            .addProgramClasses(TestClass.class)
+            .compile()
+            .inspect(this::checkSyntheticClassIsMarked)
+            .writeToZip();
+    testForD8(parameters.getBackend())
+        .setMinApi(parameters.getApiLevel())
+        .addProgramFiles(out)
+        // Use intermediate again to preserve synthetics.
+        .setIntermediate(true)
+        // Disable desugaring so we are sure the lambda synthetic is created in the first round.
+        .disableDesugaring()
+        .compile()
+        .inspect(this::checkSyntheticClassIsMarked)
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutput(EXPECTED);
+  }
+
+  private void checkSyntheticClassIsMarked(CodeInspector inspector) {
+    // Compilation gives rise to the main class plus one lambda.
+    assertEquals(2, inspector.allClasses().size());
+    inspector.forAllClasses(
+        clazz -> {
+          if (!clazz.getFinalReference().equals(Reference.classFromClass(TestClass.class))) {
+            // This should be the lambda class.
+            DexAnnotation[] annotations = clazz.getDexProgramClass().annotations().annotations;
+            assertEquals(1, annotations.length);
+            DexEncodedAnnotation annotation = annotations[0].annotation;
+            assertEquals(2, annotation.elements.length);
+            assertEquals(
+                "com.android.tools.r8.annotations.SynthesizedClassV2",
+                annotation.type.toSourceString());
+          }
+        });
+  }
+
+  public static class TestClass {
+
+    public static void run(Runnable r) {
+      r.run();
+    }
+
+    public static void main(String[] args) {
+      run(() -> System.out.println("Hello, world"));
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/utils/TestParametersTest.java b/src/test/java/com/android/tools/r8/utils/TestParametersTest.java
index 7d26b86..a217b8c 100644
--- a/src/test/java/com/android/tools/r8/utils/TestParametersTest.java
+++ b/src/test/java/com/android/tools/r8/utils/TestParametersTest.java
@@ -31,7 +31,7 @@
     assumeFalse(
         "Test is only valid when no runtimes property is set",
         TestParametersBuilder.isRuntimesPropertySet());
-    TestParametersCollection params = TestParametersBuilder.builder().withNoneRuntime().build();
+    TestParametersCollection params = TestParameters.builder().withNoneRuntime().build();
     assertTrue(params.stream().anyMatch(TestParameters::isNoneRuntime));
   }
 
@@ -40,7 +40,7 @@
     assumeFalse(
         "Test is only valid when no runtimes property is set",
         TestParametersBuilder.isRuntimesPropertySet());
-    TestParametersCollection params = TestParametersBuilder.builder().withAllRuntimes().build();
+    TestParametersCollection params = TestParameters.builder().withAllRuntimes().build();
     assertTrue(params.stream().noneMatch(TestParameters::isNoneRuntime));
     assertTrue(params.stream().anyMatch(TestParameters::isDexRuntime));
     assertTrue(params.stream().anyMatch(TestParameters::isCfRuntime));
@@ -53,7 +53,7 @@
         TestParametersBuilder.isRuntimesPropertySet());
     // This test may also fail once the tests can be configured for with API levels to run.
     TestParametersCollection params =
-        TestParametersBuilder.builder().withAllRuntimesAndApiLevels().build();
+        TestParameters.builder().withAllRuntimesAndApiLevels().build();
     assertTrue(params.stream().noneMatch(TestParameters::isNoneRuntime));
     assertTrue(params.stream().anyMatch(p -> p.isCfRuntime() && p.getApiLevel() == null));
     // Default API levels are min and max for each DEX VM.
@@ -79,12 +79,9 @@
   public void testJdk9Presence() {
     assumeTrue(!TestParametersBuilder.isRuntimesPropertySet()
         || TestParametersBuilder.getRuntimesProperty().contains("jdk9"));
-    assertTrue(TestParametersBuilder
-        .builder()
-        .withAllRuntimesAndApiLevels()
-        .build()
-        .stream()
-        .anyMatch(parameter -> parameter.getRuntime().equals(TestRuntime.getCheckedInJdk9())));
+    assertTrue(
+        TestParameters.builder().withAllRuntimesAndApiLevels().build().stream()
+            .anyMatch(parameter -> parameter.getRuntime().equals(TestRuntime.getCheckedInJdk9())));
   }
 
   @Test
@@ -92,12 +89,9 @@
     assumeTrue(ToolHelper.isLinux());
     assumeTrue(!TestParametersBuilder.isRuntimesPropertySet()
         || TestParametersBuilder.getRuntimesProperty().contains("dex-default"));
-    assertTrue(TestParametersBuilder
-        .builder()
-        .withAllRuntimesAndApiLevels()
-        .build()
-        .stream()
-        .anyMatch(parameter -> parameter.getRuntime().name().equals("dex-default")));
+    assertTrue(
+        TestParameters.builder().withAllRuntimesAndApiLevels().build().stream()
+            .anyMatch(parameter -> parameter.getRuntime().name().equals("dex-default")));
   }
 
   @Test
@@ -105,11 +99,8 @@
     assumeTrue(ToolHelper.isLinux());
     assumeTrue(!TestParametersBuilder.isRuntimesPropertySet()
         || TestParametersBuilder.getRuntimesProperty().contains("dex-4.4.4"));
-    assertTrue(TestParametersBuilder
-        .builder()
-        .withAllRuntimesAndApiLevels()
-        .build()
-        .stream()
-        .anyMatch(parameter -> parameter.getRuntime().name().equals("dex-4.4.4")));
+    assertTrue(
+        TestParameters.builder().withAllRuntimesAndApiLevels().build().stream()
+            .anyMatch(parameter -> parameter.getRuntime().name().equals("dex-4.4.4")));
   }
 }
diff --git a/third_party/r8-releases/3.2.54.tar.gz.sha1 b/third_party/r8-releases/3.2.54.tar.gz.sha1
new file mode 100644
index 0000000..dac3b33
--- /dev/null
+++ b/third_party/r8-releases/3.2.54.tar.gz.sha1
@@ -0,0 +1 @@
+f1019609a854b348981e65b34b401c15d93eb91f
\ No newline at end of file
diff --git a/tools/internal_test.py b/tools/internal_test.py
index df4174f..8e4caa5 100755
--- a/tools/internal_test.py
+++ b/tools/internal_test.py
@@ -111,6 +111,8 @@
 
 # TODO(b/210982978): Enable testing of min xmx again
 TEST_COMMANDS = [
+    # Make sure we have a clean build to not be polluted by old test files
+    ['tools/gradle.py', 'clean'],
     # Run test.py internal testing.
     ['tools/test.py', '--only_internal', '--slow_tests',
      '--java_max_memory_size=8G'],
diff --git a/tools/jdk.py b/tools/jdk.py
index 23cacc8..ea6be86 100755
--- a/tools/jdk.py
+++ b/tools/jdk.py
@@ -3,14 +3,18 @@
 # for details. All rights reserved. Use of this source code is governed by a
 # BSD-style license that can be found in the LICENSE file.
 
-import defines
 import os
 import sys
 
+import defines
+
 JDK_DIR = os.path.join(defines.THIRD_PARTY, 'openjdk')
 
 def GetJdkHome():
-  root = os.path.join(JDK_DIR, 'openjdk-9.0.4')
+  return GetJdk9Home()
+
+def GetJdk11Home():
+  root = os.path.join(JDK_DIR, 'jdk-11')
   if defines.IsLinux():
     return os.path.join(root, 'linux')
   elif defines.IsOsX():
@@ -20,8 +24,8 @@
   else:
     return os.environ['JAVA_HOME']
 
-def GetJdk11Home():
-  root = os.path.join(JDK_DIR, 'jdk-11')
+def GetJdk9Home():
+  root = os.path.join(JDK_DIR, 'openjdk-9.0.4')
   if defines.IsLinux():
     return os.path.join(root, 'linux')
   elif defines.IsOsX():
diff --git a/tools/trigger.py b/tools/trigger.py
index 2b9816a..d467a81 100755
--- a/tools/trigger.py
+++ b/tools/trigger.py
@@ -13,7 +13,7 @@
 import subprocess
 import sys
 import urllib
-
+from urllib.request import urlopen
 import utils
 
 LUCI_SCHEDULE = os.path.join(utils.REPO_ROOT, 'infra', 'config', 'global',
@@ -47,7 +47,7 @@
   with open(LUCI_SCHEDULE, 'r') as fp:
     lines = fp.readlines()
     for line in lines:
-      if 'branch-gitiles-trigger' in line:
+      if 'branch-gitiles' in line:
         is_release = True
       if 'main-gitiles-trigger' in line:
         is_release = False
@@ -66,7 +66,7 @@
   return (main_builders, release_builders)
 
 def sanity_check_url(url):
-  a = urllib.urlopen(url)
+  a = urlopen(url)
   if a.getcode() != 200:
     raise Exception('Url: %s \n returned %s' % (url, a.getcode()))
 
@@ -85,15 +85,15 @@
 def Main():
   (options, args) = ParseOptions()
   if len(args) != 1 and not options.cl and not options.desugar:
-    print 'Takes exactly one argument, the commit to run'
+    print('Takes exactly one argument, the commit to run')
     return 1
 
   if options.cl and options.release:
-    print 'You can\'t run cls on the release bots'
+    print('You can\'t run cls on the release bots')
     return 1
 
   if options.cl and options.desugar:
-    print 'You can\'t run cls on the desugar bot'
+    print('You can\'t run cls on the desugar bot')
     return 1
 
   commit = None if (options.cl or options.desugar)  else args[0]