Merge commit '00173804646ce1c429d934373d61d7c033a802f9' into dev-release
diff --git a/PRESUBMIT.py b/PRESUBMIT.py
index 87fd416..b916f47 100644
--- a/PRESUBMIT.py
+++ b/PRESUBMIT.py
@@ -111,6 +111,7 @@
 def CheckChange(input_api, output_api):
   branch = (
       check_output(['git', 'cl', 'upstream'])
+          .decode('utf-8')
           .strip()
           .replace('refs/heads/', ''))
   results = []
diff --git a/build.gradle b/build.gradle
index ea3f498..5df3bb4 100644
--- a/build.gradle
+++ b/build.gradle
@@ -33,7 +33,6 @@
     espressoVersion = '3.0.0'
     fastutilVersion = '7.2.1'
     guavaVersion = '31.1-jre'
-    joptSimpleVersion = '4.6'
     gsonVersion = '2.7'
     junitVersion = '4.13-beta-2'
     mockitoVersion = '2.10.0'
@@ -141,9 +140,9 @@
             srcDirs = ['src/test/examplesJava17']
         }
     }
-    examplesJava18 {
+    examplesJava20 {
         java {
-            srcDirs = ['src/test/examplesJava18']
+            srcDirs = ['src/test/examplesJava20']
         }
     }
     examplesTestNGRunner {
@@ -214,7 +213,6 @@
 }
 
 dependencies {
-    implementation "net.sf.jopt-simple:jopt-simple:$joptSimpleVersion"
     implementation "com.google.code.gson:gson:$gsonVersion"
     // Include all of guava when compiling the code, but exclude annotations that we don't
     // need from the packaging.
@@ -233,7 +231,6 @@
     implementation group: 'org.ow2.asm', name: 'asm-analysis', version: asmVersion
     implementation group: 'org.ow2.asm', name: 'asm-util', version: asmVersion
 
-    main17Implementation "net.sf.jopt-simple:jopt-simple:$joptSimpleVersion"
     main17Implementation "com.google.code.gson:gson:$gsonVersion"
     // Include all of guava when compiling the code, but exclude annotations that we don't
     // need from the packaging.
@@ -405,20 +402,20 @@
                                 "openjdk/jdk8/linux-x86",
                                 "openjdk/jdk-11/linux",
                                 "openjdk/jdk-17/linux",
-                                "openjdk/jdk-18/linux"],
+                                "openjdk/jdk-20/linux"],
         ],
         osx: [
                 "third_party": ["openjdk/openjdk-9.0.4/osx",
                                 "openjdk/jdk8/darwin-x86",
                                 "openjdk/jdk-11/osx",
                                 "openjdk/jdk-17/osx",
-                                "openjdk/jdk-18/osx"],
+                                "openjdk/jdk-20/osx"],
         ],
         windows: [
                 "third_party": ["openjdk/openjdk-9.0.4/windows",
                                 "openjdk/jdk-11/windows",
                                 "openjdk/jdk-17/windows",
-                                "openjdk/jdk-18/windows"],
+                                "openjdk/jdk-20/windows"],
         ],
 ]
 
@@ -487,16 +484,6 @@
     }
 }
 
-task downloadProguard {
-    cloudDependencies.each { entry ->
-        entry.value.each { entryFile ->
-            if (entryFile.contains("proguard")) {
-                dependsOn "${getDownloadDepsTaskName(entry.key, entryFile)}"
-            }
-        }
-    }
-}
-
 task downloadOpenJDKrt {
    cloudDependencies.each { entry ->
         entry.value.each { entryFile ->
@@ -652,8 +639,8 @@
         JavaVersion.VERSION_17,
         false)
 setJdkCompilationWithCompatibility(
-        sourceSets.examplesJava18.compileJavaTaskName,
-        'jdk-18',
+        sourceSets.examplesJava20.compileJavaTaskName,
+        'jdk-20',
         // TODO(b/218293990): Update Gradle to get JavaVersion.VERSION_18.
         JavaVersion.VERSION_17,
         false)
@@ -876,8 +863,6 @@
                 "--map",
                 "com.google.thirdparty->com.android.tools.r8.com.google.thirdparty",
                 "--map",
-                "joptsimple->com.android.tools.r8.joptsimple",
-                "--map",
                 "org.objectweb.asm->com.android.tools.r8.org.objectweb.asm",
                 "--map",
                 "it.unimi.dsi.fastutil->com.android.tools.r8.it.unimi.dsi.fastutil",
@@ -1342,14 +1327,7 @@
 
 
 task buildExampleJars {
-    dependsOn downloadProguard
     def examplesDir = file("src/test/examples")
-    def proguardScript
-    if (OperatingSystem.current().isWindows()) {
-        proguardScript = "third_party/proguard/proguard5.2.1/bin/proguard.bat"
-    } else {
-        proguardScript = "third_party/proguard/proguard5.2.1/bin/proguard.sh"
-    }
     task extractExamplesRuntime(type: Sync) {
         dependsOn configurations.examplesRuntime
         from { configurations.examplesRuntime.collect { zipTree(it) } }
@@ -1402,76 +1380,29 @@
         dependsOn "jar_example_${name}_debuginfo_none"
         dependsOn "extractExamplesRuntime"
         def runtimeDependencies = copySpec { }
-        // The "throwing" test verifies debugging/stack info on the post-proguarded output.
-        def proguardConfigPath = "${dir}/proguard.cfg"
-        if (new File(proguardConfigPath).exists()) {
-            task "pre_proguard_example_${name}"(type: Jar, dependsOn: "compile_examples") {
-                archiveName = "${name}_pre_proguard.jar"
-                destinationDir = exampleOutputDir
-                from "build/test/examples/classes"
-                include name + "/**/*.class"
-                with runtimeDependencies
-                includeEmptyDirs false
-            }
-            def jarPath = files(tasks.getByPath("pre_proguard_example_${name}")).files.first();
-            def proguardJarPath = "${exampleOutputDir}/${jarName}"
-            def proguardMapPath = "${exampleOutputDir}/${name}/${name}.map"
-            task "jar_example_${name}"(type: Exec, dependsOn: "pre_proguard_example_${name}") {
-                inputs.files files(
-                        tasks.getByPath("pre_proguard_example_${name}"),
-                        proguardConfigPath)
-                // Enable these to get stdout and stderr redirected to files...
-                // standardOutput = new FileOutputStream('proguard.stdout')
-                // errorOutput = new FileOutputStream('proguard.stderr')
-                def proguardArguments = "-verbose -dontwarn java.** -injars ${jarPath}" +
-                        " -outjars ${proguardJarPath}" +
-                        " -include ${proguardConfigPath}" +
-                        " -printmapping ${proguardMapPath}"
-                if (OperatingSystem.current().isWindows()) {
-                    executable "${proguardScript}"
-                    args "${proguardArguments}"
-                } else {
-                    executable "bash"
-                    args "-c", "${proguardScript} '${proguardArguments}'"
-                }
-                outputs.file proguardJarPath
-            }
-            // TODO: Consider performing distinct proguard compilations.
-            task "jar_example_${name}_debuginfo_all"(type: Copy, dependsOn: "jar_example_${name}") {
-                from "${exampleOutputDir}/${name}.jar"
-                into "${exampleOutputDir}"
-                rename(".*", "${name}_debuginfo_all.jar")
-            }
-            task "jar_example_${name}_debuginfo_none"(type: Copy, dependsOn: "jar_example_${name}") {
-                from "${exampleOutputDir}/${name}.jar"
-                into "${exampleOutputDir}"
-                rename(".*", "${name}_debuginfo_none.jar")
-            }
-        } else {
-            task "jar_example_${name}"(type: Jar, dependsOn: "compile_examples") {
-                archiveName = "${name}.jar"
-                destinationDir = exampleOutputDir
-                from "build/test/examples/classes"
-                include name + "/**/*"
-                with runtimeDependencies
-                includeEmptyDirs true
-            }
-            task "jar_example_${name}_debuginfo_all"(type: Jar, dependsOn: "compile_examples_debuginfo_all") {
-                archiveName = "${name}_debuginfo_all.jar"
-                destinationDir = exampleOutputDir
-                from "build/test/examples/classes_debuginfo_all"
-                include name + "/**/*.class"
-                with runtimeDependencies
-                includeEmptyDirs false
-            }
-            task "jar_example_${name}_debuginfo_none"(type: Jar, dependsOn: "compile_examples_debuginfo_none") {
-                archiveName = "${name}_debuginfo_none.jar"
-                destinationDir = exampleOutputDir
-                from "build/test/examples/classes_debuginfo_none"
-                include name + "/**/*.class"
-                with runtimeDependencies
-                includeEmptyDirs false
-            }
+        task "jar_example_${name}"(type: Jar, dependsOn: "compile_examples") {
+            archiveName = "${name}.jar"
+            destinationDir = exampleOutputDir
+            from "build/test/examples/classes"
+            include name + "/**/*"
+            with runtimeDependencies
+            includeEmptyDirs true
+        }
+        task "jar_example_${name}_debuginfo_all"(type: Jar, dependsOn: "compile_examples_debuginfo_all") {
+            archiveName = "${name}_debuginfo_all.jar"
+            destinationDir = exampleOutputDir
+            from "build/test/examples/classes_debuginfo_all"
+            include name + "/**/*.class"
+            with runtimeDependencies
+            includeEmptyDirs false
+        }
+        task "jar_example_${name}_debuginfo_none"(type: Jar, dependsOn: "compile_examples_debuginfo_none") {
+            archiveName = "${name}_debuginfo_none.jar"
+            destinationDir = exampleOutputDir
+            from "build/test/examples/classes_debuginfo_none"
+            include name + "/**/*.class"
+            with runtimeDependencies
+            includeEmptyDirs false
         }
     }
 }
@@ -1631,7 +1562,7 @@
 buildExampleJarsCreateTask("Java10", sourceSets.examplesJava10)
 buildExampleJarsCreateTask("Java11", sourceSets.examplesJava11)
 buildExampleJarsCreateTask("Java17", sourceSets.examplesJava17)
-buildExampleJarsCreateTask("Java18", sourceSets.examplesJava18)
+buildExampleJarsCreateTask("Java20", sourceSets.examplesJava20)
 
 task provideArtFrameworksDependencies {
     cloudDependencies.tools.forEach({ art ->
@@ -1693,7 +1624,7 @@
     dependsOn buildExampleJava10Jars
     dependsOn buildExampleJava11Jars
     dependsOn buildExampleJava17Jars
-    dependsOn buildExampleJava18Jars
+    dependsOn buildExampleJava20Jars
     dependsOn buildExampleAndroidApi
     def examplesDir = file("src/test/examples")
     examplesDir.eachDir { dir ->
diff --git a/scripts/add-openjdk.sh b/scripts/add-openjdk.sh
index e8714f3..4aac626 100755
--- a/scripts/add-openjdk.sh
+++ b/scripts/add-openjdk.sh
@@ -18,7 +18,7 @@
 
 # Now run script with fingers crossed!
 
-JDK_VERSION="18"
+JDK_VERSION="20.0.1"
 JDK_VERSION_FULL=${JDK_VERSION}
 # For ea versions the full version name has a postfix.
 # JDK_VERSION_FULL="${JDK_VERSION}-ea+33"
diff --git a/src/main/java/com/android/tools/r8/BaseCommand.java b/src/main/java/com/android/tools/r8/BaseCommand.java
index 893bc64..dd6a740 100644
--- a/src/main/java/com/android/tools/r8/BaseCommand.java
+++ b/src/main/java/com/android/tools/r8/BaseCommand.java
@@ -89,7 +89,7 @@
     }
   }
 
-  private static class LibraryInputOrigin extends InputFileOrigin {
+  static class LibraryInputOrigin extends InputFileOrigin {
 
     public LibraryInputOrigin(Path file) {
       super("library input", file);
diff --git a/src/main/java/com/android/tools/r8/GlobalSyntheticsGenerator.java b/src/main/java/com/android/tools/r8/GlobalSyntheticsGenerator.java
index 2efea81..e3a2cbb 100644
--- a/src/main/java/com/android/tools/r8/GlobalSyntheticsGenerator.java
+++ b/src/main/java/com/android/tools/r8/GlobalSyntheticsGenerator.java
@@ -3,13 +3,65 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8;
 
+import static com.android.tools.r8.utils.ExceptionUtils.unwrapExecutionException;
+
+import com.android.tools.r8.ProgramResource.Kind;
+import com.android.tools.r8.androidapi.AndroidApiLevelCompute;
+import com.android.tools.r8.androidapi.AndroidApiLevelDatabaseHelper;
+import com.android.tools.r8.androidapi.AndroidApiUnknownReferenceDiagnosticHelper;
+import com.android.tools.r8.androidapi.ApiReferenceStubber;
+import com.android.tools.r8.androidapi.ApiReferenceStubberEventConsumer;
+import com.android.tools.r8.androidapi.ComputedApiLevel.KnownApiLevel;
+import com.android.tools.r8.dex.ApplicationReader;
+import com.android.tools.r8.dex.ApplicationWriter;
+import com.android.tools.r8.errors.CompilationError;
+import com.android.tools.r8.graph.AppInfo;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.ClassAccessFlags;
+import com.android.tools.r8.graph.DexAnnotationSet;
+import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexEncodedField;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexLibraryClass;
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.DexTypeList;
+import com.android.tools.r8.graph.DirectMappedDexApplication;
+import com.android.tools.r8.graph.EnclosingMethodAttribute;
+import com.android.tools.r8.graph.GenericSignature.ClassSignature;
+import com.android.tools.r8.graph.MethodCollection.MethodCollectionFactory;
+import com.android.tools.r8.graph.NestHostClassAttribute;
+import com.android.tools.r8.graph.ProgramDefinition;
+import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.graph.ThrowExceptionCode;
+import com.android.tools.r8.ir.conversion.IRConverter;
+import com.android.tools.r8.ir.desugar.TypeRewriter;
+import com.android.tools.r8.ir.desugar.records.RecordDesugaring;
+import com.android.tools.r8.ir.desugar.varhandle.VarHandleDesugaring;
+import com.android.tools.r8.ir.desugar.varhandle.VarHandleDesugaringEventConsumer;
+import com.android.tools.r8.naming.RecordRewritingNamingLens;
+import com.android.tools.r8.naming.VarHandleDesugaringRewritingNamingLens;
 import com.android.tools.r8.origin.CommandLineOrigin;
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.shaking.MainDexInfo;
+import com.android.tools.r8.synthesis.SyntheticFinalization;
+import com.android.tools.r8.synthesis.SyntheticItems.GlobalSyntheticsStrategy;
+import com.android.tools.r8.synthesis.SyntheticNaming;
+import com.android.tools.r8.synthesis.SyntheticNaming.SyntheticKind;
 import com.android.tools.r8.utils.AndroidApp;
 import com.android.tools.r8.utils.ExceptionUtils;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.SelfRetraceTest;
 import com.android.tools.r8.utils.StringUtils;
 import com.android.tools.r8.utils.ThreadUtils;
+import com.android.tools.r8.utils.Timing;
+import com.google.common.collect.ImmutableSet;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.ExecutionException;
 import java.util.concurrent.ExecutorService;
 
 /**
@@ -18,6 +70,18 @@
  */
 public class GlobalSyntheticsGenerator {
 
+  private static boolean ensureAllGlobalSyntheticsModeled(SyntheticNaming naming) {
+    for (SyntheticKind kind : naming.kinds()) {
+      assert !kind.isGlobal()
+          || !kind.isMayOverridesNonProgramType()
+          || kind == naming.RECORD_TAG
+          || kind == naming.API_MODEL_STUB
+          || kind == naming.METHOD_HANDLES_LOOKUP
+          || kind == naming.VAR_HANDLE;
+    }
+    return true;
+  }
+
   /**
    * Main API entry for the global synthetics generator.
    *
@@ -48,16 +112,205 @@
   private static void run(AndroidApp app, InternalOptions options, ExecutorService executorService)
       throws CompilationFailedException {
     try {
-      ExceptionUtils.withD8CompilationHandler(
+      ExceptionUtils.withCompilationHandler(
           options.reporter,
           () -> {
-            throw new RuntimeException("Implement GlobalSyntheticsGenerator");
+            Timing timing = Timing.create("GlobalSyntheticsGenerator " + Version.LABEL, options);
+            try {
+              timing.begin("Read input app");
+              AppView<AppInfo> appView = readApp(app, options, executorService, timing);
+              timing.end();
+
+              timing.begin("Create global synthetics");
+              createGlobalSynthetics(appView, timing, executorService);
+              timing.end();
+
+              ApplicationWriter.create(appView, options.getMarker()).write(executorService, app);
+            } catch (ExecutionException e) {
+              throw unwrapExecutionException(e);
+            } catch (IOException e) {
+              throw new CompilationError(e.getMessage(), e);
+            } finally {
+              options.signalFinishedToConsumers();
+              // Dump timings.
+              if (options.printTimes) {
+                timing.report();
+              }
+            }
           });
     } finally {
       executorService.shutdown();
     }
   }
 
+  private static AppView<AppInfo> readApp(
+      AndroidApp inputApp, InternalOptions options, ExecutorService executor, Timing timing)
+      throws IOException {
+    timing.begin("Application read");
+    ApplicationReader applicationReader = new ApplicationReader(inputApp, options, timing);
+    DirectMappedDexApplication app = applicationReader.read(executor).toDirect();
+    timing.end();
+    TypeRewriter typeRewriter = options.getTypeRewriter();
+    AppInfo appInfo =
+        timing.time(
+            "Create app-info",
+            () ->
+                AppInfo.createInitialAppInfo(
+                    app, GlobalSyntheticsStrategy.forSingleOutputMode(), MainDexInfo.none()));
+    // Now that the dex-application is fully loaded, close any internal archive providers.
+    inputApp.closeInternalArchiveProviders();
+    return timing.time("Create app-view", () -> AppView.createForD8(appInfo, typeRewriter, timing));
+  }
+
+  private static void createGlobalSynthetics(
+      AppView<AppInfo> appView, Timing timing, ExecutorService executorService)
+      throws ExecutionException, IOException {
+    assert ensureAllGlobalSyntheticsModeled(appView.getSyntheticItems().getNaming());
+    Set<DexProgramClass> synthesizingContext =
+        ImmutableSet.of(createSynthesizingContext(appView.dexItemFactory()));
+
+    List<ProgramMethod> methodsToProcess = new ArrayList<>();
+    // Add global synthetic class for records.
+    RecordDesugaring.ensureRecordClassHelper(
+        appView,
+        synthesizingContext,
+        recordTagClass -> recordTagClass.programMethods().forEach(methodsToProcess::add));
+
+    VarHandleDesugaringEventConsumer varHandleEventConsumer =
+        new VarHandleDesugaringEventConsumer() {
+          @Override
+          public void acceptVarHandleDesugaringClass(DexProgramClass clazz) {
+            clazz.programMethods().forEach(methodsToProcess::add);
+          }
+
+          @Override
+          public void acceptVarHandleDesugaringClassContext(
+              DexProgramClass clazz, ProgramDefinition context) {}
+        };
+
+    // Add global synthetic class for var handles.
+    VarHandleDesugaring.ensureVarHandleClass(appView, varHandleEventConsumer, synthesizingContext);
+
+    // Add global synthetic class for method handles lookup.
+    VarHandleDesugaring.ensureMethodHandlesLookupClass(
+        appView, varHandleEventConsumer, synthesizingContext);
+
+    IRConverter converter = new IRConverter(appView);
+    converter.processSimpleSynthesizeMethods(methodsToProcess, executorService);
+
+    appView
+        .withoutClassHierarchy()
+        .setAppInfo(
+            new AppInfo(
+                appView.appInfo().getSyntheticItems().commit(appView.app()),
+                appView.appInfo().getMainDexInfo()));
+
+    timing.time(
+        "Finalize synthetics",
+        () -> SyntheticFinalization.finalize(appView, timing, executorService));
+
+    appView.setNamingLens(RecordRewritingNamingLens.createRecordRewritingNamingLens(appView));
+    appView.setNamingLens(
+        VarHandleDesugaringRewritingNamingLens.createVarHandleDesugaringRewritingNamingLens(
+            appView));
+
+    // Add global synthetic classes for api stubs.
+    createAllApiStubs(appView, synthesizingContext, executorService);
+
+    appView
+        .withoutClassHierarchy()
+        .setAppInfo(
+            new AppInfo(
+                appView.appInfo().getSyntheticItems().commit(appView.app()),
+                appView.appInfo().getMainDexInfo()));
+  }
+
+  private static DexProgramClass createSynthesizingContext(DexItemFactory factory) {
+    return new DexProgramClass(
+        factory.createType("Lcom/android/tools/r8/GlobalSynthetics$$SynthesizingContext;"),
+        Kind.CF,
+        Origin.unknown(),
+        ClassAccessFlags.fromCfAccessFlags(1057),
+        factory.objectType,
+        DexTypeList.empty(),
+        factory.createString("GlobalSynthetics$$SynthesizingContext.java"),
+        NestHostClassAttribute.none(),
+        Collections.emptyList(),
+        Collections.emptyList(),
+        Collections.emptyList(),
+        EnclosingMethodAttribute.none(),
+        Collections.emptyList(),
+        ClassSignature.noSignature(),
+        DexAnnotationSet.empty(),
+        DexEncodedField.EMPTY_ARRAY,
+        DexEncodedField.EMPTY_ARRAY,
+        MethodCollectionFactory.empty(),
+        factory.getSkipNameValidationForTesting(),
+        DexProgramClass::invalidChecksumRequest);
+  }
+
+  private static void createAllApiStubs(
+      AppView<?> appView, Set<DexProgramClass> synthesizingContext, ExecutorService executorService)
+      throws ExecutionException {
+    AndroidApiLevelCompute apiLevelCompute = appView.apiLevelCompute();
+
+    Set<String> notModeledTypes = AndroidApiLevelDatabaseHelper.notModeledTypes();
+
+    DexItemFactory factory = appView.dexItemFactory();
+    ThrowExceptionCode throwExceptionCode =
+        ThrowExceptionCode.create(appView.dexItemFactory().noClassDefFoundErrorType);
+    ApiReferenceStubberEventConsumer apiReferenceStubberEventConsumer =
+        ApiReferenceStubberEventConsumer.empty();
+    ThreadUtils.processItems(
+        appView.app().asDirect().libraryClasses(),
+        libraryClass -> {
+          if (notModeledTypes.contains(libraryClass.getClassReference().getTypeName())) {
+            return;
+          }
+          if (ApiReferenceStubber.isJavaType(libraryClass.getType(), factory)) {
+            return;
+          }
+          KnownApiLevel knownApiLevel =
+              apiLevelCompute
+                  .computeApiLevelForLibraryReference(libraryClass.getReference())
+                  .asKnownApiLevel();
+          if (knownApiLevel == null) {
+            appView
+                .reporter()
+                .warning(
+                    AndroidApiUnknownReferenceDiagnosticHelper.createInternal(
+                        libraryClass.getReference()));
+            return;
+          }
+          if (knownApiLevel.getApiLevel().isLessThanOrEqualTo(appView.options().getMinApiLevel())) {
+            return;
+          }
+          if (libraryClass.isFinal() && !isExceptionType(appView, libraryClass)) {
+            return;
+          }
+          ApiReferenceStubber.mockMissingLibraryClass(
+              appView,
+              ignored -> synthesizingContext,
+              libraryClass,
+              throwExceptionCode,
+              apiReferenceStubberEventConsumer);
+        },
+        executorService);
+  }
+
+  private static boolean isExceptionType(AppView<?> appView, DexLibraryClass libraryClass) {
+    DexType throwableType = appView.dexItemFactory().throwableType;
+    DexType currentType = libraryClass.getType();
+    while (currentType != null) {
+      if (currentType == throwableType) {
+        return true;
+      }
+      DexClass superClass = appView.appInfo().definitionForWithoutExistenceAssert(currentType);
+      currentType = superClass == null ? null : superClass.getSuperType();
+    }
+    return false;
+  }
+
   private static void run(String[] args) throws CompilationFailedException {
     GlobalSyntheticsGeneratorCommand command =
         GlobalSyntheticsGeneratorCommand.parse(args, CommandLineOrigin.INSTANCE).build();
@@ -67,7 +320,7 @@
       return;
     }
     if (command.isPrintVersion()) {
-      System.out.println("GlobalSynthetics " + Version.getVersionString());
+      System.out.println("GlobalSyntheticsGenerator " + Version.getVersionString());
       return;
     }
     run(command);
@@ -77,7 +330,7 @@
    * Command-line entry to GlobalSynthetics.
    *
    * <p>See {@link GlobalSyntheticsGeneratorCommandParser#getUsageMessage()} or run {@code
-   * globalsynthetics --help} for usage information.
+   * globalsyntheticsgenerator --help} for usage information.
    */
   public static void main(String[] args) {
     if (args.length == 0) {
diff --git a/src/main/java/com/android/tools/r8/GlobalSyntheticsGeneratorCommand.java b/src/main/java/com/android/tools/r8/GlobalSyntheticsGeneratorCommand.java
index 2187b67..4ae00bd 100644
--- a/src/main/java/com/android/tools/r8/GlobalSyntheticsGeneratorCommand.java
+++ b/src/main/java/com/android/tools/r8/GlobalSyntheticsGeneratorCommand.java
@@ -3,53 +3,74 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8;
 
+import com.android.tools.r8.BaseCommand.LibraryInputOrigin;
+import com.android.tools.r8.dex.Marker.Tool;
+import com.android.tools.r8.errors.CompilationError;
 import com.android.tools.r8.errors.DexFileOverflowDiagnostic;
-import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.utils.AbortException;
 import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.AndroidApp;
+import com.android.tools.r8.utils.ExceptionDiagnostic;
 import com.android.tools.r8.utils.FileUtils;
 import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.InternalOptions.DesugarState;
 import com.android.tools.r8.utils.Reporter;
 import com.android.tools.r8.utils.StringDiagnostic;
 import java.nio.file.Path;
+import java.util.Arrays;
 import java.util.Collection;
 
 /**
  * Immutable command structure for an invocation of the {@link GlobalSyntheticsGenerator} compiler.
  */
-public final class GlobalSyntheticsGeneratorCommand extends BaseCommand {
+public final class GlobalSyntheticsGeneratorCommand {
 
   private final ProgramConsumer programConsumer;
-  private final StringConsumer classNameConsumer;
   private final Reporter reporter;
   private final int minApiLevel;
 
+  private final boolean printHelp;
+  private final boolean printVersion;
+
+  private final AndroidApp inputApp;
+
   private final DexItemFactory factory = new DexItemFactory();
 
   private GlobalSyntheticsGeneratorCommand(
-      AndroidApp androidApp,
-      ProgramConsumer programConsumer,
-      StringConsumer ClassNameConsumer,
-      Reporter reporter,
-      int minApiLevel) {
-    super(androidApp);
+      AndroidApp inputApp, ProgramConsumer programConsumer, Reporter reporter, int minApiLevel) {
+    this.inputApp = inputApp;
     this.programConsumer = programConsumer;
-    this.classNameConsumer = ClassNameConsumer;
     this.minApiLevel = minApiLevel;
     this.reporter = reporter;
+    this.printHelp = false;
+    this.printVersion = false;
   }
 
   private GlobalSyntheticsGeneratorCommand(boolean printHelp, boolean printVersion) {
-    super(printHelp, printVersion);
+    this.printHelp = printHelp;
+    this.printVersion = printVersion;
+
+    this.inputApp = null;
     this.programConsumer = null;
-    this.classNameConsumer = null;
     this.minApiLevel = AndroidApiLevel.B.getLevel();
 
     reporter = new Reporter();
   }
 
+  public AndroidApp getInputApp() {
+    return inputApp;
+  }
+
+  public boolean isPrintHelp() {
+    return printHelp;
+  }
+
+  public boolean isPrintVersion() {
+    return printVersion;
+  }
+
   /**
    * Parse the GlobalSyntheticsGenerator command-line.
    *
@@ -102,7 +123,6 @@
     return new Builder(diagnosticsHandler);
   }
 
-  @Override
   InternalOptions getInternalOptions() {
     InternalOptions internal = new InternalOptions(factory, reporter);
     assert !internal.debug;
@@ -117,6 +137,12 @@
     assert !internal.isMinifying();
     assert !internal.passthroughDexCode;
 
+    internal.tool = Tool.GlobalSyntheticsGenerator;
+    internal.desugarState = DesugarState.ON;
+    internal.enableVarHandleDesugaring = true;
+
+    internal.getArtProfileOptions().setEnableCompletenessCheckForTesting(false);
+
     return internal;
   }
 
@@ -125,13 +151,14 @@
    *
    * <p>A builder is obtained by calling {@link GlobalSyntheticsGeneratorCommand#builder}.
    */
-  public static class Builder
-      extends BaseCommand.Builder<GlobalSyntheticsGeneratorCommand, Builder> {
+  public static class Builder {
 
     private ProgramConsumer programConsumer = null;
-    private StringConsumer globalSyntheticClassesListConsumer = null;
-    private Reporter reporter;
+    private final Reporter reporter;
     private int minApiLevel = AndroidApiLevel.B.getLevel();
+    private boolean printHelp = false;
+    private boolean printVersion = false;
+    private final AndroidApp.Builder appBuilder = AndroidApp.builder();
 
     private Builder() {
       this(new DefaultR8DiagnosticsHandler());
@@ -141,55 +168,46 @@
       this.reporter = new Reporter(diagnosticsHandler);
     }
 
-    @Override
-    Builder self() {
+    /** Set the min api level. */
+    public Builder setMinApiLevel(int minApiLevel) {
+      this.minApiLevel = minApiLevel;
       return this;
     }
 
-    public Builder setReporter(Reporter reporter) {
-      this.reporter = reporter;
-      return self();
+    /** Set the value of the print-help flag. */
+    public Builder setPrintHelp(boolean printHelp) {
+      this.printHelp = printHelp;
+      return this;
     }
 
-    public Builder setMinApiLevel(int minApiLevel) {
-      this.minApiLevel = minApiLevel;
-      return self();
+    /** Set the value of the print-version flag. */
+    public Builder setPrintVersion(boolean printVersion) {
+      this.printVersion = printVersion;
+      return this;
     }
 
-    @Override
-    void validate() {
-      if (isPrintHelp() || isPrintVersion()) {
-        return;
-      }
-      if (!(programConsumer instanceof DexIndexedConsumer)) {
-        reporter.error("G8 does not support compiling to dex per class or class files");
-      }
+    /** Add library file resources. */
+    public Builder addLibraryFiles(Path... files) {
+      addLibraryFiles(Arrays.asList(files));
+      return this;
     }
 
-    @Override
-    public GlobalSyntheticsGeneratorCommand makeCommand() {
-      if (isPrintHelp() || isPrintVersion()) {
-        return new GlobalSyntheticsGeneratorCommand(isPrintHelp(), isPrintVersion());
-      }
-      validate();
-      return new GlobalSyntheticsGeneratorCommand(
-          getAppBuilder().build(),
-          programConsumer,
-          globalSyntheticClassesListConsumer,
-          reporter,
-          minApiLevel);
+    /** Add library file resources. */
+    public Builder addLibraryFiles(Collection<Path> files) {
+      guard(
+          () -> {
+            for (Path path : files) {
+              try {
+                appBuilder.addLibraryFile(path);
+              } catch (CompilationError e) {
+                error(new LibraryInputOrigin(path), e);
+              }
+            }
+          });
+      return this;
     }
 
-    public Builder setGlobalSyntheticClassesListOutput(Path path) {
-      return setGlobalSyntheticClassesListConsumer(new StringConsumer.FileConsumer(path));
-    }
-
-    public Builder setGlobalSyntheticClassesListConsumer(
-        StringConsumer globalSyntheticClassesListOutput) {
-      this.globalSyntheticClassesListConsumer = globalSyntheticClassesListOutput;
-      return self();
-    }
-
+    /** Set an output path to consume the resulting program. */
     public Builder setProgramConsumerOutput(Path path) {
       return setProgramConsumer(
           FileUtils.isArchive(path)
@@ -197,64 +215,54 @@
               : new DexIndexedConsumer.DirectoryConsumer(path, false));
     }
 
+    /** Set a consumer for obtaining the resulting program. */
     public Builder setProgramConsumer(ProgramConsumer programConsumer) {
       this.programConsumer = programConsumer;
-      return self();
+      return this;
     }
 
-    @Override
-    public Builder addProgramFiles(Collection<Path> files) {
-      throw new Unreachable("Should not be used for global synthetics generation");
+    public GlobalSyntheticsGeneratorCommand build() {
+      validate();
+      if (isPrintHelpOrPrintVersion()) {
+        return new GlobalSyntheticsGeneratorCommand(printHelp, printVersion);
+      }
+      return new GlobalSyntheticsGeneratorCommand(
+          appBuilder.build(), programConsumer, reporter, minApiLevel);
     }
 
-    @Override
-    public Builder addProgramResourceProvider(ProgramResourceProvider programProvider) {
-      throw new Unreachable("Should not be used for global synthetics generation");
+    private boolean isPrintHelpOrPrintVersion() {
+      return printHelp || printVersion;
     }
 
-    @Override
-    public Builder addClasspathFiles(Path... files) {
-      throw new Unreachable("Should not be used for global synthetics generation");
+    private void validate() {
+      if (isPrintHelpOrPrintVersion()) {
+        return;
+      }
+      if (!(programConsumer instanceof DexIndexedConsumer)) {
+        reporter.error(
+            "GlobalSyntheticsGenerator does not support compiling to dex per class or class files");
+      }
     }
 
-    @Override
-    public Builder addClasspathFiles(Collection<Path> files) {
-      throw new Unreachable("Should not be used for global synthetics generation");
+    // Helper to guard and handle exceptions.
+    private void guard(Runnable action) {
+      try {
+        action.run();
+      } catch (CompilationError e) {
+        reporter.error(e.toStringDiagnostic());
+      } catch (AbortException e) {
+        // Error was reported and exception will be thrown by build.
+      }
     }
 
-    @Override
-    public Builder addClasspathResourceProvider(ClassFileResourceProvider provider) {
-      throw new Unreachable("Should not be used for global synthetics generation");
+    /** Signal an error. */
+    public void error(Diagnostic diagnostic) {
+      reporter.error(diagnostic);
     }
 
-    @Override
-    public Builder addClassProgramData(byte[] data, Origin origin) {
-      throw new Unreachable("Should not be used for global synthetics generation");
-    }
-
-    @Override
-    Builder addDexProgramData(byte[] data, Origin origin) {
-      throw new Unreachable("Should not be used for global synthetics generation");
-    }
-
-    @Override
-    public Builder addMainDexListFiles(Path... files) {
-      throw new Unreachable("Should not be used for global synthetics generation");
-    }
-
-    @Override
-    public Builder addMainDexListFiles(Collection<Path> files) {
-      throw new Unreachable("Should not be used for global synthetics generation");
-    }
-
-    @Override
-    public Builder addMainDexClasses(String... classes) {
-      throw new Unreachable("Should not be used for global synthetics generation");
-    }
-
-    @Override
-    public Builder addMainDexClasses(Collection<String> classes) {
-      throw new Unreachable("Should not be used for global synthetics generation");
+    // Helper to signify an error.
+    public void error(Origin origin, Throwable throwable) {
+      reporter.error(new ExceptionDiagnostic(throwable, origin));
     }
   }
 }
diff --git a/src/main/java/com/android/tools/r8/GlobalSyntheticsGeneratorCommandParser.java b/src/main/java/com/android/tools/r8/GlobalSyntheticsGeneratorCommandParser.java
index 36e5bbd..9022e28 100644
--- a/src/main/java/com/android/tools/r8/GlobalSyntheticsGeneratorCommandParser.java
+++ b/src/main/java/com/android/tools/r8/GlobalSyntheticsGeneratorCommandParser.java
@@ -19,7 +19,7 @@
 
 public class GlobalSyntheticsGeneratorCommandParser {
 
-  private static final String LOWER_CASE_NAME = "globalsynthetics";
+  private static final String LOWER_CASE_NAME = "globalsyntheticsgenerator";
   private static final String MIN_API_FLAG = "--min-api";
 
   private static final String USAGE_MESSAGE =
@@ -30,9 +30,6 @@
         .add(ParseFlagInfoImpl.getMinApi())
         .add(ParseFlagInfoImpl.getLib())
         .add(ParseFlagInfoImpl.flag1("--output", "<dex-file>", "Output result in <dex-file>."))
-        .add(
-            ParseFlagInfoImpl.flag1(
-                "--classes-list-output", "<file>", "Output list of generated classes in <file>"))
         .add(ParseFlagInfoImpl.getVersion(LOWER_CASE_NAME))
         .add(ParseFlagInfoImpl.getHelp())
         .build();
@@ -46,7 +43,7 @@
   }
 
   private static final Set<String> OPTIONS_WITH_ONE_PARAMETER =
-      ImmutableSet.of("--output", "--lib", MIN_API_FLAG, "---classes-list-output");
+      ImmutableSet.of("--output", "--lib", MIN_API_FLAG);
 
   public static GlobalSyntheticsGeneratorCommand.Builder parse(String[] args, Origin origin) {
     return new GlobalSyntheticsGeneratorCommandParser()
@@ -101,8 +98,6 @@
         }
       } else if (arg.equals("--lib")) {
         builder.addLibraryFiles(Paths.get(nextArg));
-      } else if (arg.equals("--classes-list-output")) {
-        builder.setGlobalSyntheticClassesListOutput(Paths.get(nextArg));
       } else if (arg.startsWith("--")) {
         builder.error(new StringDiagnostic("Unknown option: " + arg, origin));
       }
diff --git a/src/main/java/com/android/tools/r8/JarSizeCompare.java b/src/main/java/com/android/tools/r8/JarSizeCompare.java
index 0be3461..8e8388b 100644
--- a/src/main/java/com/android/tools/r8/JarSizeCompare.java
+++ b/src/main/java/com/android/tools/r8/JarSizeCompare.java
@@ -61,7 +61,6 @@
           .put("com.google.common", "com.android.tools.r8.com.google.common")
           .put("com.google.gson", "com.android.tools.r8.com.google.gson")
           .put("com.google.thirdparty", "com.android.tools.r8.com.google.thirdparty")
-          .put("joptsimple", "com.android.tools.r8.joptsimple")
           .put("org.apache.commons", "com.android.tools.r8.org.apache.commons")
           .put("org.objectweb.asm", "com.android.tools.r8.org.objectweb.asm")
           .put("it.unimi.dsi.fastutil", "com.android.tools.r8.it.unimi.dsi.fastutil")
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java
index 79d8121..d44a39b 100644
--- a/src/main/java/com/android/tools/r8/R8.java
+++ b/src/main/java/com/android/tools/r8/R8.java
@@ -68,6 +68,7 @@
 import com.android.tools.r8.optimize.MemberRebindingIdentityLens;
 import com.android.tools.r8.optimize.MemberRebindingIdentityLensFactory;
 import com.android.tools.r8.optimize.bridgehoisting.BridgeHoisting;
+import com.android.tools.r8.optimize.fields.FieldFinalizer;
 import com.android.tools.r8.optimize.interfaces.analysis.CfOpenClosedInterfacesAnalysis;
 import com.android.tools.r8.optimize.proto.ProtoNormalizer;
 import com.android.tools.r8.optimize.redundantbridgeremoval.RedundantBridgeRemover;
@@ -316,21 +317,13 @@
                     appView.getSyntheticItems().commit(appView.appInfo().app())));
       }
 
-      List<ProguardConfigurationRule> synthesizedProguardRules = new ArrayList<>();
       timing.begin("Strip unused code");
       timing.begin("Before enqueuer");
       RuntimeTypeCheckInfo.Builder classMergingEnqueuerExtensionBuilder =
           new RuntimeTypeCheckInfo.Builder(appView);
+      List<ProguardConfigurationRule> synthesizedProguardRules;
       try {
-        // Add synthesized -assumenosideeffects from min api if relevant.
-        if (options.isGeneratingDex()) {
-          if (!ProguardConfigurationUtils.hasExplicitAssumeValuesOrAssumeNoSideEffectsRuleForMinSdk(
-              options.itemFactory, options.getProguardConfiguration().getRules())) {
-            synthesizedProguardRules.add(
-                ProguardConfigurationUtils.buildAssumeNoSideEffectsRuleForApiLevel(
-                    options.itemFactory, options.getMinApiLevel()));
-          }
-        }
+        synthesizedProguardRules = ProguardConfigurationUtils.synthesizeRules(appView);
         ProfileCollectionAdditions profileCollectionAdditions =
             ProfileCollectionAdditions.create(appView);
         AssumeInfoCollection.Builder assumeInfoCollectionBuilder = AssumeInfoCollection.builder();
@@ -640,6 +633,9 @@
 
             // Synthesize fields for triggering class initializers.
             new ClassInitFieldSynthesizer(appViewWithLiveness).run(executorService);
+
+            // Finalize fields.
+            FieldFinalizer.run(appViewWithLiveness, executorService, timing);
           }
         } finally {
           timing.end();
diff --git a/src/main/java/com/android/tools/r8/androidapi/AndroidApiLevelDatabaseHelper.java b/src/main/java/com/android/tools/r8/androidapi/AndroidApiLevelDatabaseHelper.java
index 546b40b..58c1ae3 100644
--- a/src/main/java/com/android/tools/r8/androidapi/AndroidApiLevelDatabaseHelper.java
+++ b/src/main/java/com/android/tools/r8/androidapi/AndroidApiLevelDatabaseHelper.java
@@ -9,9 +9,21 @@
 import com.android.tools.r8.graph.DexReference;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.utils.AndroidApiLevel;
+import java.util.HashSet;
+import java.util.Set;
 import java.util.function.BiConsumer;
 
-class AndroidApiLevelDatabaseHelper {
+public class AndroidApiLevelDatabaseHelper {
+
+  public static Set<String> notModeledTypes() {
+    // The below types are known not to be modeled by any api-versions.
+    Set<String> notModeledTypes = new HashSet<>();
+    notModeledTypes.add("androidx.annotation.RecentlyNullable");
+    notModeledTypes.add("androidx.annotation.RecentlyNonNull");
+    notModeledTypes.add("android.annotation.Nullable");
+    notModeledTypes.add("android.annotation.NonNull");
+    return notModeledTypes;
+  }
 
   static void visitAdditionalKnownApiReferences(
       DexItemFactory factory, BiConsumer<DexReference, AndroidApiLevel> apiLevelConsumer) {
diff --git a/src/main/java/com/android/tools/r8/androidapi/AndroidApiUnknownReferenceDiagnosticHelper.java b/src/main/java/com/android/tools/r8/androidapi/AndroidApiUnknownReferenceDiagnosticHelper.java
new file mode 100644
index 0000000..dc8015c
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/androidapi/AndroidApiUnknownReferenceDiagnosticHelper.java
@@ -0,0 +1,14 @@
+// Copyright (c) 2023, 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.androidapi;
+
+import com.android.tools.r8.graph.DexReference;
+
+public class AndroidApiUnknownReferenceDiagnosticHelper {
+
+  public static AndroidApiUnknownReferenceDiagnostic createInternal(DexReference reference) {
+    return new AndroidApiUnknownReferenceDiagnostic(reference);
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/androidapi/ApiReferenceStubber.java b/src/main/java/com/android/tools/r8/androidapi/ApiReferenceStubber.java
index c42f8c6..07261a9 100644
--- a/src/main/java/com/android/tools/r8/androidapi/ApiReferenceStubber.java
+++ b/src/main/java/com/android/tools/r8/androidapi/ApiReferenceStubber.java
@@ -18,12 +18,15 @@
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexLibraryClass;
 import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.DexString;
 import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.LibraryClass;
 import com.android.tools.r8.graph.MethodAccessFlags;
 import com.android.tools.r8.graph.ThrowExceptionCode;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.synthesis.CommittedItems;
 import com.android.tools.r8.synthesis.SyntheticItems;
+import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.ListUtils;
 import com.android.tools.r8.utils.ThreadUtils;
 import com.android.tools.r8.utils.WorkList;
@@ -35,6 +38,7 @@
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.ExecutorService;
+import java.util.function.Function;
 
 /**
  * The only instructions we do not outline is constant classes, instance-of/checkcast and exception
@@ -75,16 +79,19 @@
       libraryClassesToMock.forEach(
           clazz ->
               mockMissingLibraryClass(
+                  appView,
+                  referencingContexts::get,
                   clazz,
                   ThrowExceptionCode.create(appView.dexItemFactory().noClassDefFoundErrorType),
                   eventConsumer));
       // Commit the synthetic items.
-      CommittedItems committedItems = appView.getSyntheticItems().commit(appView.appInfo().app());
       if (appView.hasLiveness()) {
+        CommittedItems committedItems = appView.getSyntheticItems().commit(appView.appInfo().app());
         AppView<AppInfoWithLiveness> appInfoWithLivenessAppView = appView.withLiveness();
         appInfoWithLivenessAppView.setAppInfo(
             appInfoWithLivenessAppView.appInfo().rebuildWithLiveness(committedItems));
       } else if (appView.hasClassHierarchy()) {
+        CommittedItems committedItems = appView.getSyntheticItems().commit(appView.appInfo().app());
         appView
             .withClassHierarchy()
             .setAppInfo(
@@ -116,9 +123,13 @@
     // We cannot reliably create a stub that will have the same throwing behavior for all VMs.
     // Only create stubs for exceptions to allow them being present in catch handlers and super
     // types of existing program classes. See b/258270051 and b/259076765 for more information.
-    clazz
-        .allImmediateSupertypes()
-        .forEach(superType -> findReferencedLibraryClasses(superType, clazz));
+    // Also, for L devices we can have verification issues if there are super invokes to missing
+    // members on stubbed classes. See b/279780940 for more information.
+    if (appView.options().getMinApiLevel().isGreaterThan(AndroidApiLevel.L)) {
+      clazz
+          .allImmediateSupertypes()
+          .forEach(superType -> findReferencedLibraryClasses(superType, clazz));
+    }
     clazz.forEachProgramMethodMatching(
         DexEncodedMethod::hasCode,
         method -> {
@@ -136,7 +147,7 @@
   }
 
   private void findReferencedLibraryClasses(DexType type, DexProgramClass context) {
-    if (!type.isClassType() || isJavaType(type)) {
+    if (!type.isClassType() || isJavaType(type, appView.dexItemFactory())) {
       return;
     }
     WorkList.newIdentityWorkList(type, seenTypes)
@@ -160,18 +171,25 @@
             });
   }
 
-  private boolean isJavaType(DexType type) {
-    return type == appView.dexItemFactory().objectType
-        || type.getDescriptor().startsWith(appView.dexItemFactory().javaDescriptorPrefix);
+  public static boolean isJavaType(DexType type, DexItemFactory factory) {
+    DexString typeDescriptor = type.getDescriptor();
+    return type == factory.objectType
+        || typeDescriptor.startsWith(factory.comSunDescriptorPrefix)
+        || typeDescriptor.startsWith(factory.javaDescriptorPrefix)
+        || typeDescriptor.startsWith(factory.javaxDescriptorPrefix)
+        || typeDescriptor.startsWith(factory.jdkDescriptorPrefix)
+        || typeDescriptor.startsWith(factory.sunDescriptorPrefix);
   }
 
-  private void mockMissingLibraryClass(
+  public static void mockMissingLibraryClass(
+      AppView<?> appView,
+      Function<LibraryClass, Set<DexProgramClass>> referencingContextSupplier,
       DexLibraryClass libraryClass,
       ThrowExceptionCode throwExceptionCode,
       ApiReferenceStubberEventConsumer eventConsumer) {
     DexItemFactory factory = appView.dexItemFactory();
     // Do not stub the anything starting with java (including the object type).
-    if (isJavaType(libraryClass.getType())) {
+    if (isJavaType(libraryClass.getType(), factory)) {
       return;
     }
     // Check if desugared library will bridge the type.
@@ -181,7 +199,7 @@
         .isSupported(libraryClass.getType())) {
       return;
     }
-    Set<DexProgramClass> contexts = referencingContexts.get(libraryClass);
+    Set<DexProgramClass> contexts = referencingContextSupplier.apply(libraryClass);
     if (contexts == null) {
       throw new Unreachable("Attempt to create a global synthetic with no contexts");
     }
diff --git a/src/main/java/com/android/tools/r8/bisect/Bisect.java b/src/main/java/com/android/tools/r8/bisect/Bisect.java
index c4c8170..9bfe7c3 100644
--- a/src/main/java/com/android/tools/r8/bisect/Bisect.java
+++ b/src/main/java/com/android/tools/r8/bisect/Bisect.java
@@ -209,7 +209,7 @@
       options = BisectOptions.parse(args);
     } catch (CompilationError e) {
       System.err.println(e.getMessage());
-      BisectOptions.printHelp(System.err);
+      BisectOptions.printHelp();
       return;
     }
     if (options == null) {
diff --git a/src/main/java/com/android/tools/r8/bisect/BisectOptions.java b/src/main/java/com/android/tools/r8/bisect/BisectOptions.java
index 4f59900..0b38e3a 100644
--- a/src/main/java/com/android/tools/r8/bisect/BisectOptions.java
+++ b/src/main/java/com/android/tools/r8/bisect/BisectOptions.java
@@ -5,23 +5,20 @@
 
 import com.android.tools.r8.errors.CompilationError;
 import java.io.IOException;
-import java.io.OutputStream;
 import java.nio.file.Files;
 import java.nio.file.Path;
 import java.nio.file.Paths;
-import joptsimple.OptionParser;
-import joptsimple.OptionSet;
-import joptsimple.OptionSpec;
 
 public class BisectOptions {
-  private static final String HELP_FLAG = "help";
-  public static final String BUILD_GOOD_FLAG = "good";
-  public static final String BUILD_BAD_FLAG = "bad";
-  public static final String RESULT_GOOD_FLAG = "result-good";
-  public static final String RESULT_BAD_FLAG = "result-bad";
-  public static final String STATE_FLAG = "state";
-  public static final String OUTPUT_FLAG = "output";
-  public static final String COMMAND_FLAG = "command";
+
+  private static final String HELP_FLAG = "--help";
+  public static final String BUILD_GOOD_FLAG = "--good";
+  public static final String BUILD_BAD_FLAG = "--bad";
+  public static final String RESULT_GOOD_FLAG = "--result-good";
+  public static final String RESULT_BAD_FLAG = "--result-bad";
+  public static final String STATE_FLAG = "--state";
+  public static final String OUTPUT_FLAG = "--output";
+  public static final String COMMAND_FLAG = "--command";
 
   public final Path goodBuild;
   public final Path badBuild;
@@ -30,52 +27,10 @@
   public final Path output;
   public final Result result;
 
-  public enum Result { UNKNOWN, GOOD, BAD }
-
-  private static class ParserSpec {
-    OptionSpec<String> goodBuild;
-    OptionSpec<String> badBuild;
-    OptionSpec<String> command;
-    OptionSpec<String> stateFile;
-    OptionSpec<String> output;
-    OptionSpec<Void> resultGood;
-    OptionSpec<Void> resultBad;
-    OptionSpec<Void> help;
-
-    void init(OptionParser parser) {
-      help = parser.accepts(HELP_FLAG).forHelp();
-      resultGood = parser.accepts(RESULT_GOOD_FLAG, "Bisect again assuming previous run was good.");
-      resultBad = parser.accepts(RESULT_BAD_FLAG, "Bisect again assuming previous run was bad.");
-      goodBuild = parser.accepts(BUILD_GOOD_FLAG, "Known good APK.")
-          .withRequiredArg()
-          .describedAs("apk");
-      badBuild = parser.accepts(BUILD_BAD_FLAG, "Known bad APK.")
-          .withRequiredArg()
-          .describedAs("apk");
-      stateFile = parser.accepts(STATE_FLAG, "Bisection state.")
-          .requiredIf(resultGood, resultBad)
-          .withRequiredArg()
-          .describedAs("file");
-      output = parser.accepts(OUTPUT_FLAG, "Output directory.")
-          .withRequiredArg()
-          .describedAs("dir");
-      command = parser.accepts(COMMAND_FLAG, "Command to run after each bisection.")
-          .requiredUnless(stateFile)
-          .withRequiredArg()
-          .describedAs("file");
-    }
-
-    OptionSet parse(String[] args) {
-      OptionParser parser = new OptionParser();
-      init(parser);
-      return parser.parse(args);
-    }
-
-    static void printHelp(OutputStream out) throws IOException {
-      OptionParser parser = new OptionParser();
-      new ParserSpec().init(parser);
-      parser.printHelpOn(out);
-    }
+  public enum Result {
+    UNKNOWN,
+    GOOD,
+    BAD
   }
 
   private BisectOptions(
@@ -89,65 +44,100 @@
   }
 
   public static BisectOptions parse(String[] args) throws IOException {
-    ParserSpec parser = new ParserSpec();
-    OptionSet options = parser.parse(args);
-    if (options.has(parser.help)) {
-      printHelp(System.out);
-      return null;
-    }
-    Path goodBuild = exists(require(options, parser.goodBuild, BUILD_GOOD_FLAG), BUILD_GOOD_FLAG);
-    Path badBuild = exists(require(options, parser.badBuild, BUILD_BAD_FLAG), BUILD_BAD_FLAG);
+    Path badBuild = null;
+    Path goodBuild = null;
     Path stateFile = null;
-    if (options.valueOf(parser.stateFile) != null) {
-      stateFile = exists(options.valueOf(parser.stateFile), STATE_FLAG);
-    }
     Path command = null;
-    if (options.valueOf(parser.command) != null) {
-      command = exists(options.valueOf(parser.command), COMMAND_FLAG);
-    }
     Path output = null;
-    if (options.valueOf(parser.output) != null) {
-      output = directoryExists(options.valueOf(parser.output), OUTPUT_FLAG);
-    }
     Result result = Result.UNKNOWN;
-    if (options.has(parser.resultGood)) {
-      result = Result.GOOD;
-    }
-    if (options.has(parser.resultBad)) {
-      if (result == Result.GOOD) {
-        throw new CompilationError("Cannot specify --" + RESULT_GOOD_FLAG
-            + " and --" + RESULT_BAD_FLAG + " simultaneously");
+
+    for (int i = 0; i < args.length; i++) {
+      String arg = args[i].trim();
+      if (arg.equals(HELP_FLAG)) {
+        printHelp();
+        return null;
+      } else if (arg.equals(BUILD_BAD_FLAG)) {
+        i = nextArg(i, args, BUILD_BAD_FLAG);
+        badBuild = Paths.get(args[i]);
+      } else if (arg.equals(BUILD_GOOD_FLAG)) {
+        i = nextArg(i, args, BUILD_GOOD_FLAG);
+        goodBuild = Paths.get(args[i]);
+      } else if (arg.equals(COMMAND_FLAG)) {
+        i = nextArg(i, args, COMMAND_FLAG);
+        command = Paths.get(args[i]);
+      } else if (arg.equals(OUTPUT_FLAG)) {
+        i = nextArg(i, args, OUTPUT_FLAG);
+        output = Paths.get(args[i]);
+      } else if (arg.equals(RESULT_BAD_FLAG)) {
+        result = checkSingleResult(result, Result.BAD);
+      } else if (arg.equals(RESULT_GOOD_FLAG)) {
+        result = checkSingleResult(result, Result.GOOD);
+      } else if (arg.equals(STATE_FLAG)) {
+        i = nextArg(i, args, STATE_FLAG);
+        stateFile = Paths.get(args[i]);
       }
-      result = Result.BAD;
     }
+    exists(require(badBuild, BUILD_BAD_FLAG), BUILD_BAD_FLAG);
+    exists(require(goodBuild, BUILD_GOOD_FLAG), BUILD_GOOD_FLAG);
+    if (stateFile != null) {
+      exists(stateFile, STATE_FLAG);
+    }
+    if (command != null) {
+      exists(command, COMMAND_FLAG);
+    }
+    if (output != null) {
+      directoryExists(output, OUTPUT_FLAG);
+    }
+
     return new BisectOptions(goodBuild, badBuild, stateFile, command, output, result);
   }
 
-  private static <T> T require(OptionSet options, OptionSpec<T> option, String flag) {
-    T value = options.valueOf(option);
-    if (value != null) {
-      return value;
+  private static int nextArg(int index, String[] args, String flag) {
+    if (args.length == index + 1) {
+      throw new CompilationError("Missing argument for: " + flag);
     }
-    throw new CompilationError("Missing required option: --" + flag);
+    return index + 1;
   }
 
-  private static Path exists(String path, String flag) {
-    Path file = Paths.get(path);
-    if (Files.exists(file)) {
-      return file;
+  private static Path require(Path value, String flag) {
+    if (value == null) {
+      throw new CompilationError("Missing required option: " + flag);
     }
-    throw new CompilationError("File --" + flag + ": " + file + " does not exist");
+    return value;
   }
 
-  private static Path directoryExists(String path, String flag) {
-    Path file = Paths.get(path);
-    if (Files.exists(file) && Files.isDirectory(file)) {
-      return file;
+  private static Path exists(Path path, String flag) {
+    if (Files.exists(path)) {
+      return path;
     }
-    throw new CompilationError("File --" + flag + ": " + file + " is not a valid directory");
+    throw new CompilationError("File " + flag + ": " + path + " does not exist");
   }
 
-  public static void printHelp(OutputStream out) throws IOException {
-    ParserSpec.printHelp(out);
+  private static Path directoryExists(Path path, String flag) {
+    if (Files.exists(path) && Files.isDirectory(path)) {
+      return path;
+    }
+    throw new CompilationError("File " + flag + ": " + path + " is not a valid directory");
+  }
+
+  private static Result checkSingleResult(Result current, Result result) {
+    if (current != Result.UNKNOWN) {
+      throw new CompilationError(
+          "Cannot specify " + RESULT_GOOD_FLAG + " and " + RESULT_BAD_FLAG + " simultaneously");
+    }
+    return result;
+  }
+
+  public static void printHelp() throws IOException {
+    System.out.println("--bad <apk>       Known bad APK.");
+    System.out.println("--command <file>  Command to run after each bisection.");
+    System.out.println("--good <apk>      Known good APK.");
+    System.out.println("--help");
+    System.out.println("--output <dir>    Output directory.");
+    System.out.println(
+        "--result-bad      Bisect again assuming previous run was\n" + "        bad.");
+    System.out.println(
+        "--result-good     Bisect again assuming previous run was\n" + "        good.");
+    System.out.println("--state <file>    Bisection state.");
   }
 }
diff --git a/src/main/java/com/android/tools/r8/dex/FileWriter.java b/src/main/java/com/android/tools/r8/dex/FileWriter.java
index 8c6f33a..686d327 100644
--- a/src/main/java/com/android/tools/r8/dex/FileWriter.java
+++ b/src/main/java/com/android/tools/r8/dex/FileWriter.java
@@ -40,6 +40,7 @@
 import com.android.tools.r8.graph.DexTypeList;
 import com.android.tools.r8.graph.DexValue;
 import com.android.tools.r8.graph.DexWritableCode;
+import com.android.tools.r8.graph.DexWritableCode.DexWritableCacheKey;
 import com.android.tools.r8.graph.IndexedDexItem;
 import com.android.tools.r8.graph.ObjectToOffsetMapping;
 import com.android.tools.r8.graph.ParameterAnnotationsList;
@@ -56,6 +57,7 @@
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.IterableUtils;
 import com.android.tools.r8.utils.LebUtils;
+import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Sets;
 import it.unimi.dsi.fastutil.objects.Object2IntLinkedOpenHashMap;
 import it.unimi.dsi.fastutil.objects.Object2IntMap;
@@ -67,6 +69,7 @@
 import java.util.Collection;
 import java.util.Collections;
 import java.util.Comparator;
+import java.util.HashMap;
 import java.util.HashSet;
 import java.util.IdentityHashMap;
 import java.util.List;
@@ -223,7 +226,8 @@
     Collection<ProgramMethod> codes = mixedSectionLayoutStrategy.getCodeLayout();
 
     // Output the debug_info_items first, as they have no dependencies.
-    dest.moveTo(layout.getCodesOffset() + sizeOfCodeItems(codes));
+    SizeAndCount sizeAndCountOfCodeItems = sizeAndCountOfCodeItems(codes);
+    dest.moveTo(layout.getCodesOffset() + sizeAndCountOfCodeItems.size);
     if (mixedSectionOffsets.getDebugInfos().isEmpty()) {
       layout.setDebugInfosOffset(0);
     } else {
@@ -245,7 +249,25 @@
     // Now output the code.
     dest.moveTo(layout.getCodesOffset());
     assert dest.isAligned(4);
-    writeItems(codes, layout::alreadySetOffset, this::writeCodeItem, 4);
+    Map<DexWritableCacheKey, Integer> offsetCache = new HashMap<>();
+    for (ProgramMethod method : codes) {
+      DexWritableCode dexWritableCode = method.getDefinition().getCode().asDexWritableCode();
+      if (!options.canUseCanonicalizedCodeObjects()) {
+        writeCodeItem(method, dexWritableCode);
+      } else {
+        DexWritableCacheKey cacheLookupKey =
+            dexWritableCode.getCacheLookupKey(method, appView.dexItemFactory());
+        Integer offsetOrNull = offsetCache.get(cacheLookupKey);
+        if (offsetOrNull != null) {
+          mixedSectionOffsets.setOffsetFor(method.getDefinition(), offsetOrNull);
+        } else {
+          offsetCache.put(cacheLookupKey, writeCodeItem(method, dexWritableCode));
+        }
+      }
+    }
+    assert sizeAndCountOfCodeItems.getCount()
+        == ImmutableSet.copyOf(mixedSectionOffsets.codes.values()).size();
+    layout.setCodeCount(sizeAndCountOfCodeItems.getCount());
     assert layout.getDebugInfosOffset() == 0 || dest.position() == layout.getDebugInfosOffset();
 
     // Now the type lists and rest.
@@ -434,13 +456,32 @@
     }
   }
 
-  private int sizeOfCodeItems(Iterable<ProgramMethod> methods) {
-    int size = 0;
-    for (ProgramMethod method : methods) {
-      size = alignSize(4, size);
-      size += sizeOfCodeItem(method.getDefinition().getCode().asDexWritableCode());
+  static class SizeAndCount {
+
+    private int size = 0;
+    private int count = 0;
+
+    public int getCount() {
+      return count;
     }
-    return size;
+
+    public int getSize() {
+      return size;
+    }
+  }
+
+  private SizeAndCount sizeAndCountOfCodeItems(Iterable<ProgramMethod> methods) {
+    SizeAndCount sizeAndCount = new SizeAndCount();
+    Set<DexWritableCacheKey> cache = new HashSet<>();
+    for (ProgramMethod method : methods) {
+      DexWritableCode code = method.getDefinition().getCode().asDexWritableCode();
+      if (!options.canUseCanonicalizedCodeObjects()
+          || cache.add(code.getCacheLookupKey(method, appView.dexItemFactory()))) {
+        sizeAndCount.count++;
+        sizeAndCount.size = alignSize(4, sizeAndCount.size) + sizeOfCodeItem(code);
+      }
+    }
+    return sizeAndCount;
   }
 
   private int sizeOfCodeItem(DexWritableCode code) {
@@ -525,12 +566,9 @@
     dest.putBytes(new DebugBytecodeWriter(debugInfo, mapping, graphLens).generate());
   }
 
-  private void writeCodeItem(ProgramMethod method) {
-    writeCodeItem(method, method.getDefinition().getCode().asDexWritableCode());
-  }
-
-  private void writeCodeItem(ProgramMethod method, DexWritableCode code) {
-    mixedSectionOffsets.setOffsetFor(method.getDefinition(), code, dest.align(4));
+  private int writeCodeItem(ProgramMethod method, DexWritableCode code) {
+    int codeOffset = dest.align(4);
+    mixedSectionOffsets.setOffsetFor(method.getDefinition(), codeOffset);
     // Fixed size header information.
     dest.putShort((short) code.getRegisterSize(method));
     dest.putShort((short) code.getIncomingRegisterSize(method));
@@ -580,6 +618,7 @@
       // And move to the end.
       dest.moveTo(endOfCodeOffset);
     }
+    return codeOffset;
   }
 
   private void writeTypeList(DexTypeList list) {
@@ -943,6 +982,7 @@
     private int encodedArraysOffset = NOT_SET;
     private int mapOffset = NOT_SET;
     private int endOfFile = NOT_SET;
+    private int codeCount = NOT_SET;
 
     private Layout(
         int headerOffset,
@@ -1024,6 +1064,15 @@
       this.codesOffset = codesOffset;
     }
 
+    public void setCodeCount(int codeCount) {
+      assert this.codeCount == NOT_SET;
+      this.codeCount = codeCount;
+    }
+
+    public int getCodeCount() {
+      return codeCount;
+    }
+
     public int getDebugInfosOffset() {
       assert isValidOffset(debugInfosOffset, false);
       return debugInfosOffset;
@@ -1185,11 +1234,7 @@
               Constants.TYPE_METHOD_HANDLE_ITEM,
               methodHandleIdsOffset,
               fileWriter.mapping.getMethodHandles().size()));
-      mapItems.add(
-          new MapItem(
-              Constants.TYPE_CODE_ITEM,
-              getCodesOffset(),
-              fileWriter.mixedSectionOffsets.getCodes().size()));
+      mapItems.add(new MapItem(Constants.TYPE_CODE_ITEM, getCodesOffset(), codeCount));
       mapItems.add(
           new MapItem(
               Constants.TYPE_DEBUG_INFO_ITEM,
@@ -1601,7 +1646,7 @@
       setOffsetFor(debugInfo, offset, debugInfos);
     }
 
-    void setOffsetFor(DexEncodedMethod method, DexWritableCode code, int offset) {
+    void setOffsetFor(DexEncodedMethod method, int offset) {
       setOffsetFor(method, offset, codes);
     }
 
diff --git a/src/main/java/com/android/tools/r8/dex/Marker.java b/src/main/java/com/android/tools/r8/dex/Marker.java
index 4367a38..4470baf 100644
--- a/src/main/java/com/android/tools/r8/dex/Marker.java
+++ b/src/main/java/com/android/tools/r8/dex/Marker.java
@@ -26,13 +26,13 @@
   public static final String BACKEND = "backend";
   public static final String PG_MAP_ID = "pg-map-id";
   public static final String R8_MODE = "r8-mode";
-  private static final String NO_LIBRARY_DESUGARING = "<no-library-desugaring>";
   private static final String ANDROID_PLATFORM_BUILD = "platform";
 
   public enum Tool {
     D8,
-    R8,
+    GlobalSyntheticsGenerator,
     L8,
+    R8,
     Relocator,
     TraceReferences;
 
diff --git a/src/main/java/com/android/tools/r8/graph/DefaultInstanceInitializerCode.java b/src/main/java/com/android/tools/r8/graph/DefaultInstanceInitializerCode.java
index 06fd92e..b0e0aba 100644
--- a/src/main/java/com/android/tools/r8/graph/DefaultInstanceInitializerCode.java
+++ b/src/main/java/com/android/tools/r8/graph/DefaultInstanceInitializerCode.java
@@ -236,7 +236,8 @@
     return getMaxLocals(method);
   }
 
-  static DexMethod getParentConstructor(DexClassAndMethod method, DexItemFactory dexItemFactory) {
+  public static DexMethod getParentConstructor(
+      DexClassAndMethod method, DexItemFactory dexItemFactory) {
     return dexItemFactory.createInstanceInitializer(method.getHolder().getSuperType());
   }
 
@@ -397,6 +398,15 @@
     return toString();
   }
 
+  @Override
+  public DexWritableCacheKey getCacheLookupKey(ProgramMethod method, DexItemFactory factory) {
+    return new AmendedDexWritableCodeKey<DexMethod>(
+        this,
+        getParentConstructor(method, factory),
+        getIncomingRegisterSize(method),
+        getRegisterSize(method));
+  }
+
   static class DefaultInstanceInitializerSourceCode extends SyntheticStraightLineSourceCode {
 
     DefaultInstanceInitializerSourceCode(DexMethod method) {
diff --git a/src/main/java/com/android/tools/r8/graph/Definition.java b/src/main/java/com/android/tools/r8/graph/Definition.java
index 05ad2d1..e03b8e8 100644
--- a/src/main/java/com/android/tools/r8/graph/Definition.java
+++ b/src/main/java/com/android/tools/r8/graph/Definition.java
@@ -155,4 +155,12 @@
   default ProgramMethod asProgramMethod() {
     return null;
   }
+
+  default boolean isSamePackage(Definition definition) {
+    return isSamePackage(definition.getReference());
+  }
+
+  default boolean isSamePackage(DexReference reference) {
+    return getReference().isSamePackage(reference);
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/graph/DexClass.java b/src/main/java/com/android/tools/r8/graph/DexClass.java
index c79cb1b..1d7c8cf 100644
--- a/src/main/java/com/android/tools/r8/graph/DexClass.java
+++ b/src/main/java/com/android/tools/r8/graph/DexClass.java
@@ -31,6 +31,7 @@
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
+import java.util.Collections;
 import java.util.Iterator;
 import java.util.List;
 import java.util.ListIterator;
@@ -817,8 +818,8 @@
   }
 
   public boolean validInterfaceSignatures() {
-    return getClassSignature().superInterfaceSignatures().isEmpty()
-        || interfaces.values.length == getClassSignature().superInterfaceSignatures.size();
+    return getClassSignature().getSuperInterfaceSignatures().isEmpty()
+        || interfaces.values.length == getClassSignature().getSuperInterfaceSignatures().size();
   }
 
   public void forEachImmediateInterfaceWithSignature(
@@ -826,7 +827,7 @@
     assert validInterfaceSignatures();
 
     // If there is no generic signature information don't pass any type arguments.
-    if (getClassSignature().superInterfaceSignatures().isEmpty()) {
+    if (getClassSignature().getSuperInterfaceSignatures().isEmpty()) {
       forEachImmediateInterface(
           superInterface ->
               consumer.accept(superInterface, new ClassTypeSignature(superInterface)));
@@ -835,7 +836,7 @@
 
     Iterator<DexType> interfaceIterator = Arrays.asList(interfaces.values).iterator();
     Iterator<ClassTypeSignature> interfaceSignatureIterator =
-        getClassSignature().superInterfaceSignatures().iterator();
+        getClassSignature().getSuperInterfaceSignatures().iterator();
 
     while (interfaceIterator.hasNext()) {
       assert interfaceSignatureIterator.hasNext();
@@ -846,9 +847,9 @@
   }
 
   public void forEachImmediateSupertypeWithSignature(
-      BiConsumer<DexType, ClassTypeSignature> consumer) {
+      DexItemFactory factory, BiConsumer<DexType, ClassTypeSignature> consumer) {
     if (superType != null) {
-      consumer.accept(superType, classSignature.superClassSignature);
+      consumer.accept(superType, classSignature.getSuperClassSignatureOrObject(factory));
     }
     forEachImmediateInterfaceWithSignature(consumer);
   }
@@ -859,7 +860,7 @@
     assert validInterfaceSignatures();
 
     // If there is no generic signature information don't pass any type arguments.
-    if (getClassSignature().superInterfaceSignatures().size() == 0) {
+    if (getClassSignature().getSuperInterfaceSignatures().isEmpty()) {
       forEachImmediateInterface(
           superInterface -> consumer.accept(superInterface, ImmutableList.of()));
       return;
@@ -867,7 +868,7 @@
 
     Iterator<DexType> interfaceIterator = Arrays.asList(interfaces.values).iterator();
     Iterator<ClassTypeSignature> interfaceSignatureIterator =
-        getClassSignature().superInterfaceSignatures().iterator();
+        getClassSignature().getSuperInterfaceSignatures().iterator();
 
     while (interfaceIterator.hasNext()) {
       assert interfaceSignatureIterator.hasNext();
@@ -890,17 +891,18 @@
       BiConsumer<DexType, List<FieldTypeSignature>> consumer) {
     if (superType != null) {
       consumer.accept(
-          superType, applyTypeArguments(getClassSignature().superClassSignature, typeArguments));
+          superType,
+          applyTypeArguments(getClassSignature().getSuperClassSignatureOrNull(), typeArguments));
     }
     forEachImmediateInterfaceWithAppliedTypeArguments(typeArguments, consumer);
   }
 
   private List<FieldTypeSignature> applyTypeArguments(
       ClassTypeSignature superInterfaceSignatures, List<FieldTypeSignature> appliedTypeArguments) {
-    ImmutableList.Builder<FieldTypeSignature> superTypeArgumentsBuilder = ImmutableList.builder();
-    if (superInterfaceSignatures.type.toSourceString().equals("java.util.Map")) {
-      System.currentTimeMillis();
+    if (superInterfaceSignatures == null) {
+      return Collections.emptyList();
     }
+    ImmutableList.Builder<FieldTypeSignature> superTypeArgumentsBuilder = ImmutableList.builder();
     superInterfaceSignatures
         .typeArguments()
         .forEach(
@@ -1150,11 +1152,11 @@
     return fieldCollection.hasInstanceFields();
   }
 
-  public List<DexEncodedField> getDirectAndIndirectInstanceFields(AppView<?> appView) {
-    List<DexEncodedField> result = new ArrayList<>();
+  public List<DexClassAndField> getDirectAndIndirectInstanceFields(AppView<?> appView) {
+    List<DexClassAndField> result = new ArrayList<>();
     DexClass current = this;
     while (current != null && current.type != appView.dexItemFactory().objectType) {
-      result.addAll(current.instanceFields());
+      current.forEachClassFieldMatching(DexEncodedField::isInstance, result::add);
       current = appView.definitionFor(current.superType);
     }
     return result;
diff --git a/src/main/java/com/android/tools/r8/graph/DexClassAndField.java b/src/main/java/com/android/tools/r8/graph/DexClassAndField.java
index 30179b3..1dc176f 100644
--- a/src/main/java/com/android/tools/r8/graph/DexClassAndField.java
+++ b/src/main/java/com/android/tools/r8/graph/DexClassAndField.java
@@ -6,6 +6,7 @@
 
 import com.android.tools.r8.ir.optimize.info.FieldOptimizationInfo;
 import com.android.tools.r8.references.FieldReference;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
 
 public abstract class DexClassAndField extends DexClassAndMember<DexEncodedField, DexField> {
 
@@ -59,4 +60,13 @@
   public DexClassAndField asMember() {
     return this;
   }
+
+  public final boolean isFinalOrEffectivelyFinal(AppView<?> appView) {
+    return getAccessFlags().isFinal()
+        || (appView.hasLiveness() && isEffectivelyFinal(appView.withLiveness()));
+  }
+
+  public boolean isEffectivelyFinal(AppView<AppInfoWithLiveness> appView) {
+    return false;
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/graph/DexCode.java b/src/main/java/com/android/tools/r8/graph/DexCode.java
index ae96c5e..039a0f0 100644
--- a/src/main/java/com/android/tools/r8/graph/DexCode.java
+++ b/src/main/java/com/android/tools/r8/graph/DexCode.java
@@ -22,6 +22,7 @@
 import com.android.tools.r8.graph.DexDebugEvent.SetPositionFrame;
 import com.android.tools.r8.graph.DexDebugEvent.StartLocal;
 import com.android.tools.r8.graph.DexDebugInfo.EventBasedDebugInfo;
+import com.android.tools.r8.graph.DexWritableCode.DexWritableCacheKey;
 import com.android.tools.r8.graph.bytecodemetadata.BytecodeInstructionMetadata;
 import com.android.tools.r8.graph.bytecodemetadata.BytecodeMetadata;
 import com.android.tools.r8.graph.lens.GraphLens;
@@ -62,7 +63,8 @@
 import java.util.function.Consumer;
 
 // DexCode corresponds to code item in dalvik/dex-format.html
-public class DexCode extends Code implements DexWritableCode, StructuralItem<DexCode> {
+public class DexCode extends Code
+    implements DexWritableCode, StructuralItem<DexCode>, DexWritableCacheKey {
 
   public static final String FAKE_THIS_PREFIX = "_";
   public static final String FAKE_THIS_SUFFIX = "this";
@@ -283,6 +285,7 @@
     if (debugInfoForWriting != null) {
       debugInfoForWriting = null;
     }
+    flushCachedValues();
   }
 
   public DexDebugInfo debugInfoWithFakeThisParameter(DexItemFactory factory) {
@@ -858,6 +861,11 @@
     }
   }
 
+  @Override
+  public DexWritableCacheKey getCacheLookupKey(ProgramMethod method, DexItemFactory factory) {
+    return this;
+  }
+
   public static class Try extends DexItem implements StructuralItem<Try> {
 
     public static final Try[] EMPTY_ARRAY = new Try[0];
diff --git a/src/main/java/com/android/tools/r8/graph/DexDefinitionSupplier.java b/src/main/java/com/android/tools/r8/graph/DexDefinitionSupplier.java
index fca76e5..22985dc 100644
--- a/src/main/java/com/android/tools/r8/graph/DexDefinitionSupplier.java
+++ b/src/main/java/com/android/tools/r8/graph/DexDefinitionSupplier.java
@@ -91,6 +91,11 @@
     return definitionFor(type) != null;
   }
 
+  default DexClassAndField definitionFor(DexField field) {
+    DexClass holder = definitionFor(field.getHolderType());
+    return holder != null ? holder.lookupClassField(field) : null;
+  }
+
   default DexClassAndMethod definitionFor(DexMethod method) {
     DexClass holder = definitionFor(method.getHolderType());
     return holder != null ? holder.lookupClassMethod(method) : null;
diff --git a/src/main/java/com/android/tools/r8/graph/DexEncodedField.java b/src/main/java/com/android/tools/r8/graph/DexEncodedField.java
index c6f2634..5d63bdc 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedField.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedField.java
@@ -4,7 +4,6 @@
 package com.android.tools.r8.graph;
 
 import static com.android.tools.r8.graph.DexProgramClass.asProgramClassOrNull;
-import static com.android.tools.r8.ir.analysis.type.Nullability.maybeNull;
 import static com.android.tools.r8.kotlin.KotlinMetadataUtils.getNoKotlinInfo;
 
 import com.android.tools.r8.androidapi.ComputedApiLevel;
@@ -12,18 +11,12 @@
 import com.android.tools.r8.graph.GenericSignature.FieldTypeSignature;
 import com.android.tools.r8.ir.analysis.type.DynamicType;
 import com.android.tools.r8.ir.analysis.type.TypeElement;
-import com.android.tools.r8.ir.analysis.value.AbstractValue;
-import com.android.tools.r8.ir.analysis.value.SingleValue;
-import com.android.tools.r8.ir.code.IRCode;
-import com.android.tools.r8.ir.code.Instruction;
-import com.android.tools.r8.ir.code.TypeAndLocalInfoSupplier;
 import com.android.tools.r8.ir.optimize.info.DefaultFieldOptimizationInfo;
 import com.android.tools.r8.ir.optimize.info.FieldOptimizationInfo;
 import com.android.tools.r8.ir.optimize.info.MutableFieldOptimizationInfo;
 import com.android.tools.r8.ir.optimize.info.OptimizationFeedbackSimple;
 import com.android.tools.r8.kotlin.KotlinFieldLevelInfo;
 import com.android.tools.r8.kotlin.KotlinMetadataUtils;
-import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.utils.ConsumerUtils;
 import com.android.tools.r8.utils.structural.StructuralItem;
 import com.android.tools.r8.utils.structural.StructuralMapping;
@@ -193,6 +186,15 @@
     return fieldConsumer.apply(this);
   }
 
+  public DexClassAndField asClassField(DexDefinitionSupplier definitions) {
+    assert getHolderType().isClassType();
+    DexProgramClass clazz = asProgramClassOrNull(definitions.definitionForHolder(getReference()));
+    if (clazz != null) {
+      return DexClassAndField.create(clazz, this);
+    }
+    return null;
+  }
+
   public ProgramField asProgramField(DexDefinitionSupplier definitions) {
     assert getHolderType().isClassType();
     DexProgramClass clazz = asProgramClassOrNull(definitions.definitionForHolder(getReference()));
@@ -210,6 +212,10 @@
     return accessFlags.isFinal();
   }
 
+  public boolean isInstance() {
+    return !isStatic();
+  }
+
   @Override
   public boolean isStatic() {
     return accessFlags.isStatic();
@@ -257,51 +263,6 @@
     return staticValue == null ? DexValue.defaultForType(getReference().type) : staticValue;
   }
 
-  /**
-   * Returns a const instructions if this field is a compile time final const.
-   *
-   * <p>NOTE: It is the responsibility of the caller to check if this field is pinned or not.
-   */
-  public Instruction valueAsConstInstruction(
-      IRCode code, DebugLocalInfo local, AppView<AppInfoWithLiveness> appView) {
-    boolean isWritten = appView.appInfo().isFieldWrittenByFieldPutInstruction(this);
-    if (!isWritten) {
-      // Since the field is not written, we can simply return the default value for the type.
-      DexValue value = isStatic() ? getStaticValue() : DexValue.defaultForType(getReference().type);
-      return value.asConstInstruction(appView, code, local);
-    }
-
-    // Check if we have a single value for the field according to the field optimization info.
-    AbstractValue abstractValue = getOptimizationInfo().getAbstractValue();
-    if (abstractValue.isSingleValue()) {
-      SingleValue singleValue = abstractValue.asSingleValue();
-      if (singleValue.isSingleFieldValue()
-          && singleValue.asSingleFieldValue().getField() == getReference()) {
-        return null;
-      }
-      if (singleValue.isMaterializableInContext(appView, code.context())) {
-        TypeElement type = TypeElement.fromDexType(getReference().type, maybeNull(), appView);
-        return singleValue.createMaterializingInstruction(
-            appView, code, TypeAndLocalInfoSupplier.create(type, local));
-      }
-    }
-
-    // The only way to figure out whether the static value contains the final value is ensure the
-    // value is not the default or check that <clinit> is not present.
-    if (accessFlags.isFinal() && isStatic()) {
-      DexClass clazz = appView.definitionFor(getReference().holder);
-      if (clazz == null || clazz.hasClassInitializer()) {
-        return null;
-      }
-      DexValue staticValue = getStaticValue();
-      if (!staticValue.isDefault(getReference().type)) {
-        return staticValue.asConstInstruction(appView, code, local);
-      }
-    }
-
-    return null;
-  }
-
   public DexEncodedField toTypeSubstitutedField(AppView<?> appView, DexField field) {
     return toTypeSubstitutedField(appView, field, ConsumerUtils.emptyConsumer());
   }
@@ -442,14 +403,6 @@
       return this;
     }
 
-    public Builder setAbstractValue(
-        AbstractValue abstractValue, AppView<AppInfoWithLiveness> appView) {
-      return addBuildConsumer(
-          fixedUpField ->
-              OptimizationFeedbackSimple.getInstance()
-                  .recordFieldHasAbstractValue(fixedUpField, appView, abstractValue));
-    }
-
     public Builder clearDynamicType() {
       return addBuildConsumer(
           fixedUpField ->
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 164797a..41ca6f5 100644
--- a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
+++ b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
@@ -159,7 +159,11 @@
   public final DexString shortDescriptor = createString("S");
   public final DexString voidDescriptor = createString("V");
   public final DexString descriptorSeparator = createString("/");
+  public final DexString comSunDescriptorPrefix = createString("Lcom/sun/");
   public final DexString javaDescriptorPrefix = createString("Ljava/");
+  public final DexString javaxDescriptorPrefix = createString("Ljavax/");
+  public final DexString jdkDescriptorPrefix = createString("Ljdk/");
+  public final DexString sunDescriptorPrefix = createString("Lsun/");
   public final DexString jDollarDescriptorPrefix = createString("Lj$/");
 
   private final DexString booleanArrayDescriptor = createString("[Z");
@@ -1990,26 +1994,28 @@
       return field == nameField || field == ordinalField;
     }
 
-    public boolean isEnumFieldCandidate(DexEncodedField staticField) {
-      assert staticField.isStatic();
-      return staticField.isEnum() && staticField.isFinal();
+    public boolean isEnumFieldCandidate(DexClassAndField staticField) {
+      FieldAccessFlags accessFlags = staticField.getAccessFlags();
+      assert accessFlags.isStatic();
+      return accessFlags.isEnum() && accessFlags.isFinal();
     }
 
     // In some case, the enum field may be respecialized to an enum subtype. In this case, one
     // can pass the encoded field as well as the field with the super enum type for the checks.
     public boolean isEnumField(
-        DexEncodedField staticField, DexType enumType, Set<DexType> subtypes) {
-      assert staticField.isStatic();
+        DexClassAndField staticField, DexType enumType, Set<DexType> subtypes) {
+      assert staticField.getAccessFlags().isStatic();
       return (staticField.getType() == enumType || subtypes.contains(staticField.getType()))
           && isEnumFieldCandidate(staticField);
     }
 
-    public boolean isValuesFieldCandidate(DexEncodedField staticField, DexType enumType) {
-      assert staticField.isStatic();
+    public boolean isValuesFieldCandidate(DexClassAndField staticField, DexType enumType) {
+      FieldAccessFlags accessFlags = staticField.getAccessFlags();
+      assert accessFlags.isStatic();
       return staticField.getType().isArrayType()
           && staticField.getType().toArrayElementType(DexItemFactory.this) == enumType
-          && staticField.isSynthetic()
-          && staticField.isFinal();
+          && accessFlags.isSynthetic()
+          && accessFlags.isFinal();
     }
   }
 
diff --git a/src/main/java/com/android/tools/r8/graph/DexProgramClass.java b/src/main/java/com/android/tools/r8/graph/DexProgramClass.java
index fc4c4a6..1aa866f 100644
--- a/src/main/java/com/android/tools/r8/graph/DexProgramClass.java
+++ b/src/main/java/com/android/tools/r8/graph/DexProgramClass.java
@@ -27,7 +27,6 @@
 import com.android.tools.r8.utils.structural.StructuralItem;
 import com.android.tools.r8.utils.structural.StructuralMapping;
 import com.android.tools.r8.utils.structural.StructuralSpecification;
-import com.google.common.collect.ImmutableList;
 import com.google.common.collect.Iterables;
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -35,6 +34,7 @@
 import java.util.Iterator;
 import java.util.List;
 import java.util.Set;
+import java.util.function.BiFunction;
 import java.util.function.Consumer;
 import java.util.function.Function;
 import java.util.function.Predicate;
@@ -406,7 +406,13 @@
 
   public TraversalContinuation<?, ?> traverseProgramFields(
       Function<? super ProgramField, TraversalContinuation<?, ?>> fn) {
-    return traverseFields(field -> fn.apply(new ProgramField(this, field)));
+    return getFieldCollection().traverse(field -> fn.apply(field.asProgramField()));
+  }
+
+  public <BT, CT> TraversalContinuation<BT, CT> traverseProgramFields(
+      BiFunction<? super ProgramField, CT, TraversalContinuation<BT, CT>> fn, CT initialValue) {
+    return getFieldCollection()
+        .traverse((field, value) -> fn.apply(field.asProgramField(), value), initialValue);
   }
 
   public TraversalContinuation<?, ?> traverseProgramMethods(
@@ -489,7 +495,7 @@
     if (hasMethodsOrFields()) {
       collector.add(this);
       methodCollection.forEachMethod(m -> m.collectMixedSectionItems(collector));
-      fieldCollection.forEachField(f -> f.collectMixedSectionItems(collector));
+      fieldCollection.forEachField(f -> f.getDefinition().collectMixedSectionItems(collector));
     }
     annotations().collectMixedSectionItems(collector);
     if (interfaces != null) {
@@ -732,12 +738,12 @@
     methodCollection.replaceVirtualMethod(virtualMethod, replacement);
   }
 
-  public void addExtraInterfaces(List<ClassTypeSignature> extraInterfaces) {
+  public void addExtraInterfaces(List<ClassTypeSignature> extraInterfaces, DexItemFactory factory) {
     if (extraInterfaces.isEmpty()) {
       return;
     }
     addExtraInterfacesToInterfacesArray(extraInterfaces);
-    addExtraInterfacesToSignatureIfPresent(extraInterfaces);
+    addExtraInterfacesToSignatureIfPresent(extraInterfaces, factory);
   }
 
   private void addExtraInterfacesToInterfacesArray(List<ClassTypeSignature> extraInterfaces) {
@@ -749,21 +755,20 @@
     interfaces = new DexTypeList(newInterfaces);
   }
 
-  private void addExtraInterfacesToSignatureIfPresent(List<ClassTypeSignature> extraInterfaces) {
+  private void addExtraInterfacesToSignatureIfPresent(
+      List<ClassTypeSignature> extraInterfaces, DexItemFactory factory) {
+    assert !extraInterfaces.isEmpty();
     // We introduce the extra interfaces to the generic signature.
-    if (classSignature.hasNoSignature() || extraInterfaces.isEmpty()) {
+    if (classSignature.hasNoSignature()) {
       return;
     }
-    ImmutableList.Builder<ClassTypeSignature> interfacesBuilder =
-        ImmutableList.<ClassTypeSignature>builder().addAll(classSignature.superInterfaceSignatures);
-    for (ClassTypeSignature extraInterface : extraInterfaces) {
-      interfacesBuilder.add(extraInterface);
-    }
     classSignature =
-        new ClassSignature(
-            classSignature.formalTypeParameters,
-            classSignature.superClassSignature,
-            interfacesBuilder.build());
+        ClassSignature.builder()
+            .addSuperInterfaceSignatures(classSignature.getSuperInterfaceSignatures())
+            .addSuperInterfaceSignatures(extraInterfaces)
+            .setSuperClassSignature(classSignature.getSuperClassSignatureOrNull())
+            .addFormalTypeParameters(classSignature.getFormalTypeParameters())
+            .build(factory);
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/graph/DexReference.java b/src/main/java/com/android/tools/r8/graph/DexReference.java
index 6956170..1e84335 100644
--- a/src/main/java/com/android/tools/r8/graph/DexReference.java
+++ b/src/main/java/com/android/tools/r8/graph/DexReference.java
@@ -83,6 +83,10 @@
     return null;
   }
 
+  public boolean isSamePackage(DexReference reference) {
+    return getContextType().isSamePackage(reference.getContextType());
+  }
+
   public int referenceTypeOrder() {
     if (isDexType()) {
       return 1;
diff --git a/src/main/java/com/android/tools/r8/graph/DexWritableCode.java b/src/main/java/com/android/tools/r8/graph/DexWritableCode.java
index 1e8856c..99f8679 100644
--- a/src/main/java/com/android/tools/r8/graph/DexWritableCode.java
+++ b/src/main/java/com/android/tools/r8/graph/DexWritableCode.java
@@ -92,6 +92,8 @@
     return null;
   }
 
+  DexWritableCacheKey getCacheLookupKey(ProgramMethod method, DexItemFactory factory);
+
   /** Rewrites the code to have JumboString bytecode if required by mapping. */
   DexWritableCode rewriteCodeWithJumboStrings(
       ProgramMethod method, ObjectToOffsetMapping mapping, DexItemFactory factory, boolean force);
@@ -105,4 +107,65 @@
       GraphLens codeLens,
       LensCodeRewriterUtils lensCodeRewriter,
       ObjectToOffsetMapping mapping);
+
+  interface DexWritableCacheKey {}
+
+  class DexWritableCodeKey implements DexWritableCacheKey {
+
+    private final DexWritableCode code;
+    private final int incomingRegisterSize;
+    private final int registerSize;
+
+    public DexWritableCodeKey(DexWritableCode code, int incomingRegisterSize, int registerSize) {
+      this.code = code;
+      this.incomingRegisterSize = incomingRegisterSize;
+      this.registerSize = registerSize;
+    }
+
+    @Override
+    public int hashCode() {
+      return code.hashCode() + incomingRegisterSize * 13 + registerSize * 17;
+    }
+
+    @Override
+    public boolean equals(Object other) {
+      if (this == other) {
+        return true;
+      }
+      if (!(other instanceof DexWritableCodeKey)) {
+        return false;
+      }
+      DexWritableCodeKey that = (DexWritableCodeKey) other;
+      return code.equals(that.code)
+          && incomingRegisterSize == that.incomingRegisterSize
+          && registerSize == that.registerSize;
+    }
+  }
+
+  class AmendedDexWritableCodeKey<S> extends DexWritableCodeKey {
+    private final S extra;
+
+    public AmendedDexWritableCodeKey(
+        DexWritableCode code, S extra, int incomingRegisterSize, int registerSize) {
+      super(code, incomingRegisterSize, registerSize);
+      this.extra = extra;
+    }
+
+    @Override
+    public int hashCode() {
+      return super.hashCode() + extra.hashCode() * 7;
+    }
+
+    @Override
+    public boolean equals(Object other) {
+      if (this == other) {
+        return true;
+      }
+      if (!(other instanceof AmendedDexWritableCodeKey)) {
+        return false;
+      }
+      AmendedDexWritableCodeKey that = (AmendedDexWritableCodeKey) other;
+      return super.equals(other) && extra.equals(that.extra);
+    }
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/graph/FieldArrayBacking.java b/src/main/java/com/android/tools/r8/graph/FieldArrayBacking.java
index 4b782b3..dee7446 100644
--- a/src/main/java/com/android/tools/r8/graph/FieldArrayBacking.java
+++ b/src/main/java/com/android/tools/r8/graph/FieldArrayBacking.java
@@ -17,6 +17,7 @@
 import java.util.List;
 import java.util.Objects;
 import java.util.Set;
+import java.util.function.BiFunction;
 import java.util.function.Consumer;
 import java.util.function.Function;
 import java.util.function.Predicate;
@@ -69,18 +70,48 @@
   }
 
   @Override
-  TraversalContinuation<?, ?> traverse(Function<DexEncodedField, TraversalContinuation<?, ?>> fn) {
-    for (int i = 0; i < staticFields.length; i++) {
-      if (fn.apply(staticFields[i]).shouldBreak()) {
-        return TraversalContinuation.doBreak();
+  <BT, CT> TraversalContinuation<BT, CT> traverse(
+      DexClass holder, Function<? super DexClassAndField, TraversalContinuation<BT, CT>> fn) {
+    TraversalContinuation<BT, CT> traversalContinuation = TraversalContinuation.doContinue();
+    for (DexEncodedField definition : staticFields) {
+      DexClassAndField field = DexClassAndField.create(holder, definition);
+      traversalContinuation = fn.apply(field);
+      if (traversalContinuation.shouldBreak()) {
+        return traversalContinuation;
       }
     }
-    for (int i = 0; i < instanceFields.length; i++) {
-      if (fn.apply(instanceFields[i]).shouldBreak()) {
-        return TraversalContinuation.doBreak();
+    for (DexEncodedField definition : instanceFields) {
+      DexClassAndField field = DexClassAndField.create(holder, definition);
+      traversalContinuation = fn.apply(field);
+      if (traversalContinuation.shouldBreak()) {
+        return traversalContinuation;
       }
     }
-    return TraversalContinuation.doContinue();
+    return traversalContinuation;
+  }
+
+  @Override
+  <BT, CT> TraversalContinuation<BT, CT> traverse(
+      DexClass holder,
+      BiFunction<? super DexClassAndField, ? super CT, TraversalContinuation<BT, CT>> fn,
+      CT initialValue) {
+    TraversalContinuation<BT, CT> traversalContinuation =
+        TraversalContinuation.doContinue(initialValue);
+    for (DexEncodedField definition : staticFields) {
+      DexClassAndField field = DexClassAndField.create(holder, definition);
+      traversalContinuation = fn.apply(field, traversalContinuation.asContinue().getValue());
+      if (traversalContinuation.shouldBreak()) {
+        return traversalContinuation;
+      }
+    }
+    for (DexEncodedField definition : instanceFields) {
+      DexClassAndField field = DexClassAndField.create(holder, definition);
+      traversalContinuation = fn.apply(field, traversalContinuation.asContinue().getValue());
+      if (traversalContinuation.shouldBreak()) {
+        return traversalContinuation;
+      }
+    }
+    return traversalContinuation;
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/graph/FieldCollection.java b/src/main/java/com/android/tools/r8/graph/FieldCollection.java
index e2cf122..729b238 100644
--- a/src/main/java/com/android/tools/r8/graph/FieldCollection.java
+++ b/src/main/java/com/android/tools/r8/graph/FieldCollection.java
@@ -8,6 +8,7 @@
 import java.util.Collection;
 import java.util.Comparator;
 import java.util.List;
+import java.util.function.BiFunction;
 import java.util.function.Consumer;
 import java.util.function.Function;
 import java.util.function.Predicate;
@@ -49,8 +50,8 @@
     return backing.size();
   }
 
-  public void forEachField(Consumer<DexEncodedField> fn) {
-    backing.traverse(
+  public void forEachField(Consumer<DexClassAndField> fn) {
+    traverse(
         field -> {
           fn.accept(field);
           return TraversalContinuation.doContinue();
@@ -61,6 +62,17 @@
     return backing.fields(predicate);
   }
 
+  public <BT, CT> TraversalContinuation<BT, CT> traverse(
+      Function<? super DexClassAndField, TraversalContinuation<BT, CT>> fn) {
+    return backing.traverse(holder, fn);
+  }
+
+  public <BT, CT> TraversalContinuation<BT, CT> traverse(
+      BiFunction<? super DexClassAndField, ? super CT, TraversalContinuation<BT, CT>> fn,
+      CT initialValue) {
+    return backing.traverse(holder, fn, initialValue);
+  }
+
   public boolean verify() {
     forEachField(
         field -> {
@@ -70,6 +82,11 @@
     return true;
   }
 
+  private boolean verifyCorrectnessOfFieldHolder(DexClassAndField field) {
+    assert verifyCorrectnessOfFieldHolder(field.getDefinition());
+    return true;
+  }
+
   private boolean verifyCorrectnessOfFieldHolder(DexEncodedField field) {
     assert field.getHolderType() == holder.type
         : "Expected field `"
@@ -163,18 +180,13 @@
 
   public List<DexEncodedField> allFieldsSorted() {
     List<DexEncodedField> sorted = new ArrayList<>(size());
-    forEachField(sorted::add);
+    forEachField(field -> sorted.add(field.getDefinition()));
     sorted.sort(Comparator.comparing(DexEncodedMember::getReference));
     return sorted;
   }
 
   public boolean hasAnnotations() {
-    return backing
-        .traverse(
-            field ->
-                field.hasAnnotations()
-                    ? TraversalContinuation.doBreak()
-                    : TraversalContinuation.doContinue())
+    return traverse(field -> TraversalContinuation.breakIf(field.getDefinition().hasAnnotations()))
         .shouldBreak();
   }
 }
diff --git a/src/main/java/com/android/tools/r8/graph/FieldCollectionBacking.java b/src/main/java/com/android/tools/r8/graph/FieldCollectionBacking.java
index 570996f..522748e 100644
--- a/src/main/java/com/android/tools/r8/graph/FieldCollectionBacking.java
+++ b/src/main/java/com/android/tools/r8/graph/FieldCollectionBacking.java
@@ -6,6 +6,7 @@
 import com.android.tools.r8.utils.TraversalContinuation;
 import java.util.Collection;
 import java.util.List;
+import java.util.function.BiFunction;
 import java.util.function.Function;
 import java.util.function.Predicate;
 
@@ -25,8 +26,13 @@
 
   // Traversal methods.
 
-  abstract TraversalContinuation<?, ?> traverse(
-      Function<DexEncodedField, TraversalContinuation<?, ?>> fn);
+  abstract <BT, CT> TraversalContinuation<BT, CT> traverse(
+      DexClass holder, Function<? super DexClassAndField, TraversalContinuation<BT, CT>> fn);
+
+  abstract <BT, CT> TraversalContinuation<BT, CT> traverse(
+      DexClass holder,
+      BiFunction<? super DexClassAndField, ? super CT, TraversalContinuation<BT, CT>> fn,
+      CT initialValue);
 
   // Collection methods.
 
diff --git a/src/main/java/com/android/tools/r8/graph/FieldMapBacking.java b/src/main/java/com/android/tools/r8/graph/FieldMapBacking.java
index 3bac4e0..6b74673 100644
--- a/src/main/java/com/android/tools/r8/graph/FieldMapBacking.java
+++ b/src/main/java/com/android/tools/r8/graph/FieldMapBacking.java
@@ -11,6 +11,7 @@
 import java.util.Collections;
 import java.util.List;
 import java.util.SortedMap;
+import java.util.function.BiFunction;
 import java.util.function.Function;
 import java.util.function.Predicate;
 
@@ -46,14 +47,34 @@
   }
 
   @Override
-  TraversalContinuation<?, ?> traverse(Function<DexEncodedField, TraversalContinuation<?, ?>> fn) {
-    for (DexEncodedField field : fieldMap.values()) {
-      TraversalContinuation<?, ?> result = fn.apply(field);
-      if (result.shouldBreak()) {
-        return result;
+  <BT, CT> TraversalContinuation<BT, CT> traverse(
+      DexClass holder, Function<? super DexClassAndField, TraversalContinuation<BT, CT>> fn) {
+    TraversalContinuation<BT, CT> traversalContinuation = TraversalContinuation.doContinue();
+    for (DexEncodedField definition : fieldMap.values()) {
+      DexClassAndField field = DexClassAndField.create(holder, definition);
+      traversalContinuation = fn.apply(field);
+      if (traversalContinuation.shouldBreak()) {
+        return traversalContinuation;
       }
     }
-    return TraversalContinuation.doContinue();
+    return traversalContinuation;
+  }
+
+  @Override
+  <BT, CT> TraversalContinuation<BT, CT> traverse(
+      DexClass holder,
+      BiFunction<? super DexClassAndField, ? super CT, TraversalContinuation<BT, CT>> fn,
+      CT initialValue) {
+    TraversalContinuation<BT, CT> traversalContinuation =
+        TraversalContinuation.doContinue(initialValue);
+    for (DexEncodedField definition : fieldMap.values()) {
+      DexClassAndField field = DexClassAndField.create(holder, definition);
+      traversalContinuation = fn.apply(field, traversalContinuation.asContinue().getValue());
+      if (traversalContinuation.shouldBreak()) {
+        return traversalContinuation;
+      }
+    }
+    return traversalContinuation;
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/graph/GenericSignature.java b/src/main/java/com/android/tools/r8/graph/GenericSignature.java
index f1122b8..d669d502 100644
--- a/src/main/java/com/android/tools/r8/graph/GenericSignature.java
+++ b/src/main/java/com/android/tools/r8/graph/GenericSignature.java
@@ -9,6 +9,7 @@
 
 import com.android.tools.r8.DiagnosticsHandler;
 import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.graph.GenericSignature.ClassSignature.ClassSignatureBuilder;
 import com.android.tools.r8.naming.NamingLens;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.utils.DescriptorUtils;
@@ -17,6 +18,7 @@
 import java.nio.CharBuffer;
 import java.util.ArrayList;
 import java.util.List;
+import java.util.function.Consumer;
 import java.util.function.Predicate;
 
 /**
@@ -207,35 +209,44 @@
   public static class ClassSignature implements DexDefinitionSignature<DexClass> {
 
     private static final ClassSignature NO_CLASS_SIGNATURE =
-        new ClassSignature(EMPTY_TYPE_PARAMS, NO_FIELD_TYPE_SIGNATURE, EMPTY_SUPER_INTERFACES);
+        new ClassSignature(EMPTY_TYPE_PARAMS, null, EMPTY_SUPER_INTERFACES);
 
-    final List<FormalTypeParameter> formalTypeParameters;
-    final ClassTypeSignature superClassSignature;
-    final List<ClassTypeSignature> superInterfaceSignatures;
+    public static ClassSignature noSignature() {
+      return NO_CLASS_SIGNATURE;
+    }
+
+    private final List<FormalTypeParameter> formalTypeParameters;
+    private final ClassTypeSignature superClassSignatureOrNullForObject;
+    private final List<ClassTypeSignature> superInterfaceSignatures;
 
     ClassSignature(
         List<FormalTypeParameter> formalTypeParameters,
         ClassTypeSignature superClassSignature,
         List<ClassTypeSignature> superInterfaceSignatures) {
       assert formalTypeParameters != null;
-      assert superClassSignature != null;
       assert superInterfaceSignatures != null;
       this.formalTypeParameters = formalTypeParameters;
-      this.superClassSignature = superClassSignature;
+      this.superClassSignatureOrNullForObject = superClassSignature;
       this.superInterfaceSignatures = superInterfaceSignatures;
     }
 
-    public ClassTypeSignature superClassSignature() {
-      return superClassSignature;
+    public ClassTypeSignature getSuperClassSignatureOrNull() {
+      return superClassSignatureOrNullForObject;
     }
 
-    public List<ClassTypeSignature> superInterfaceSignatures() {
+    public ClassTypeSignature getSuperClassSignatureOrObject(DexItemFactory factory) {
+      return superClassSignatureOrNullForObject != null
+          ? superClassSignatureOrNullForObject
+          : new ClassTypeSignature(factory.objectType);
+    }
+
+    public List<ClassTypeSignature> getSuperInterfaceSignatures() {
       return superInterfaceSignatures;
     }
 
     @Override
     public boolean hasSignature() {
-      return this != NO_CLASS_SIGNATURE;
+      return this != noSignature();
     }
 
     @Override
@@ -258,21 +269,32 @@
       return formalTypeParameters;
     }
 
-    public ClassSignature visit(GenericSignatureVisitor visitor) {
+    public ClassSignature visit(GenericSignatureVisitor visitor, DexItemFactory factory) {
       if (hasNoSignature()) {
         return this;
       }
       List<FormalTypeParameter> rewrittenParameters =
           visitor.visitFormalTypeParameters(formalTypeParameters);
-      ClassTypeSignature rewrittenSuperClass = visitor.visitSuperClass(superClassSignature);
+      ClassTypeSignature rewrittenSuperClass =
+          visitor.visitSuperClass(superClassSignatureOrNullForObject);
       List<ClassTypeSignature> rewrittenInterfaces =
           visitor.visitSuperInterfaces(superInterfaceSignatures);
       if (formalTypeParameters == rewrittenParameters
-          && superClassSignature == rewrittenSuperClass
+          && superClassSignatureOrNullForObject == rewrittenSuperClass
           && superInterfaceSignatures == rewrittenInterfaces) {
         return this;
       }
-      return new ClassSignature(rewrittenParameters, rewrittenSuperClass, rewrittenInterfaces);
+      return ClassSignature.builder()
+          .addFormalTypeParameters(rewrittenParameters)
+          .setSuperClassSignature(rewrittenSuperClass)
+          .addSuperInterfaceSignatures(rewrittenInterfaces)
+          .build(factory);
+    }
+
+    public void visitWithoutRewrite(GenericSignatureVisitor visitor) {
+      visitor.visitFormalTypeParameters(formalTypeParameters);
+      visitor.visitSuperClass(superClassSignatureOrNullForObject);
+      visitor.visitSuperInterfaces(superInterfaceSignatures);
     }
 
     public String toRenamedString(NamingLens namingLens, Predicate<DexType> isTypeMissing) {
@@ -290,14 +312,12 @@
       return toRenamedString(NamingLens.getIdentityLens(), alwaysTrue());
     }
 
-    public static ClassSignature noSignature() {
-      return NO_CLASS_SIGNATURE;
-    }
-
-    public List<FieldTypeSignature> getGenericArgumentsToSuperType(DexType type) {
+    public List<FieldTypeSignature> getGenericArgumentsToSuperType(
+        DexType type, DexItemFactory factory) {
       assert hasSignature();
-      if (superClassSignature.type == type) {
-        return superClassSignature.typeArguments;
+      ClassTypeSignature superClassSig = getSuperClassSignatureOrObject(factory);
+      if (superClassSig.type == type) {
+        return superClassSig.typeArguments;
       }
       for (ClassTypeSignature superInterfaceSignature : superInterfaceSignatures) {
         if (superInterfaceSignature.type == type) {
@@ -319,6 +339,11 @@
 
       private ClassSignatureBuilder() {}
 
+      public ClassSignatureBuilder addFormalTypeParameter(FormalTypeParameter formal) {
+        formalTypeParameters.add(formal);
+        return this;
+      }
+
       public ClassSignatureBuilder addFormalTypeParameters(List<FormalTypeParameter> formals) {
         formalTypeParameters.addAll(formals);
         return this;
@@ -329,12 +354,32 @@
         return this;
       }
 
-      public ClassSignatureBuilder addInterface(ClassTypeSignature iface) {
+      public ClassSignatureBuilder addSuperInterfaceSignature(ClassTypeSignature iface) {
         superInterfaceSignatures.add(iface);
         return this;
       }
 
-      public ClassSignature build() {
+      public ClassSignatureBuilder addSuperInterfaceSignatures(List<ClassTypeSignature> ifaces) {
+        superInterfaceSignatures.addAll(ifaces);
+        return this;
+      }
+
+      public ClassSignature build(DexItemFactory factory) {
+        // Any trivial super class signature is always represented by the null value.
+        if (superClassSignature != null) {
+          if (superClassSignature.type() == factory.objectType) {
+            assert !superClassSignature.hasTypeVariableArguments();
+            superClassSignature = null;
+          } else if (superClassSignature.hasNoSignature()) {
+            superClassSignature = null;
+          }
+        }
+        // Any trivial class signature is represented by the "no signature" singleton.
+        if (superClassSignature == null
+            && formalTypeParameters.isEmpty()
+            && superInterfaceSignatures.isEmpty()) {
+          return ClassSignature.noSignature();
+        }
         return new ClassSignature(
             formalTypeParameters, superClassSignature, superInterfaceSignatures);
       }
@@ -346,7 +391,7 @@
     private final String genericSignatureString;
 
     InvalidClassSignature(String genericSignatureString) {
-      super(EMPTY_TYPE_PARAMS, NO_FIELD_TYPE_SIGNATURE, EMPTY_SUPER_INTERFACES);
+      super(EMPTY_TYPE_PARAMS, null, EMPTY_SUPER_INTERFACES);
       this.genericSignatureString = genericSignatureString;
     }
 
@@ -367,12 +412,17 @@
     }
 
     @Override
-    public ClassSignature visit(GenericSignatureVisitor visitor) {
+    public ClassSignature visit(GenericSignatureVisitor visitor, DexItemFactory factory) {
       assert false : "Should not visit an invalid signature";
       return this;
     }
 
     @Override
+    public void visitWithoutRewrite(GenericSignatureVisitor visitor) {
+      assert false : "Should not visit an invalid signature";
+    }
+
+    @Override
     public boolean isInvalid() {
       return true;
     }
@@ -943,7 +993,7 @@
       DexItemFactory factory,
       DiagnosticsHandler diagnosticsHandler) {
     if (signature == null || signature.isEmpty()) {
-      return ClassSignature.NO_CLASS_SIGNATURE;
+      return ClassSignature.noSignature();
     }
     Parser parser = new Parser(factory);
     try {
@@ -951,7 +1001,7 @@
     } catch (GenericSignatureFormatError e) {
       diagnosticsHandler.warning(
           GenericSignatureFormatDiagnostic.invalidClassSignature(signature, className, origin, e));
-      return ClassSignature.NO_CLASS_SIGNATURE;
+      return ClassSignature.noSignature();
     }
   }
 
@@ -1100,34 +1150,28 @@
 
     private ClassSignature parseClassSignature() {
       // ClassSignature ::= FormalTypeParameters? SuperclassSignature SuperinterfaceSignature*.
-
-      List<FormalTypeParameter> formalTypeParameters = parseOptFormalTypeParameters();
-
+      ClassSignatureBuilder signatureBuilder = ClassSignature.builder();
+      parseOptFormalTypeParameters(signatureBuilder::addFormalTypeParameter);
       // SuperclassSignature ::= ClassTypeSignature.
-      ClassTypeSignature superClassSignature = parseClassTypeSignature();
-
-      ImmutableList.Builder<ClassTypeSignature> builder = ImmutableList.builder();
+      signatureBuilder.setSuperClassSignature(parseClassTypeSignature());
       while (symbol > 0) {
         // SuperinterfaceSignature ::= ClassTypeSignature.
-        builder.add(parseClassTypeSignature());
+        signatureBuilder.addSuperInterfaceSignature(parseClassTypeSignature());
       }
-
-      return new ClassSignature(formalTypeParameters, superClassSignature, builder.build());
+      return signatureBuilder.build(factory);
     }
 
-    private List<FormalTypeParameter> parseOptFormalTypeParameters() {
+    private void parseOptFormalTypeParameters(Consumer<FormalTypeParameter> consumer) {
       // FormalTypeParameters ::= "<" FormalTypeParameter+ ">".
       if (symbol != '<') {
-        return EMPTY_TYPE_PARAMS;
+        return;
       }
       scanSymbol();
 
-      ImmutableList.Builder<FormalTypeParameter> builder = ImmutableList.builder();
       while ((symbol != '>') && (symbol > 0)) {
-        builder.add(updateFormalTypeParameter());
+        consumer.accept(updateFormalTypeParameter());
       }
       expect('>');
-      return builder.build();
     }
 
     private FormalTypeParameter updateFormalTypeParameter() {
@@ -1288,7 +1332,8 @@
     private MethodTypeSignature parseMethodTypeSignature() {
       // MethodTypeSignature ::=
       //     FormalTypeParameters? "(" TypeSignature* ")" ReturnType ThrowsSignature*.
-      List<FormalTypeParameter> formalTypeParameters = parseOptFormalTypeParameters();
+      ImmutableList.Builder<FormalTypeParameter> formalsBuilder = ImmutableList.builder();
+      parseOptFormalTypeParameters(formalsBuilder::add);
 
       expect('(');
 
@@ -1315,7 +1360,7 @@
       }
 
       return new MethodTypeSignature(
-          formalTypeParameters,
+          formalsBuilder.build(),
           parameterSignatureBuilder.build(),
           returnType,
           throwsSignatureBuilder.build());
diff --git a/src/main/java/com/android/tools/r8/graph/GenericSignatureCorrectnessHelper.java b/src/main/java/com/android/tools/r8/graph/GenericSignatureCorrectnessHelper.java
index 7864ee8..8bac211 100644
--- a/src/main/java/com/android/tools/r8/graph/GenericSignatureCorrectnessHelper.java
+++ b/src/main/java/com/android/tools/r8/graph/GenericSignatureCorrectnessHelper.java
@@ -250,26 +250,24 @@
         return VALID;
       }
       SignatureEvaluationResult signatureEvaluationResult =
-          evaluateFormalTypeParameters(classSignature.formalTypeParameters, typeParameterContext);
+          evaluateFormalTypeParameters(
+              classSignature.getFormalTypeParameters(), typeParameterContext);
       if (signatureEvaluationResult.isInvalid()) {
         return signatureEvaluationResult;
       }
-      if (context.superType == appView.dexItemFactory().objectType
-          && classSignature.superClassSignature().hasNoSignature()) {
-        // We represent no signature as object.
-      } else if (context.superType != classSignature.superClassSignature().type()) {
+      ClassTypeSignature superClassSignature =
+          classSignature.getSuperClassSignatureOrObject(appView.dexItemFactory());
+      if (context.superType != superClassSignature.type()) {
         assert mode.doNotVerify() : "Super type inconsistency in generic signature";
         return INVALID_SUPER_TYPE;
       }
       signatureEvaluationResult =
           evaluateTypeArgumentsAppliedToType(
-              classSignature.superClassSignature().typeArguments(),
-              context.superType,
-              typeParameterContext);
+              superClassSignature.typeArguments(), context.superType, typeParameterContext);
       if (signatureEvaluationResult.isInvalid()) {
         return signatureEvaluationResult;
       }
-      List<ClassTypeSignature> superInterfaces = classSignature.superInterfaceSignatures();
+      List<ClassTypeSignature> superInterfaces = classSignature.getSuperInterfaceSignatures();
       if (context.interfaces.size() != superInterfaces.size()) {
         assert mode.doNotVerify();
         return INVALID_INTERFACE_COUNT;
diff --git a/src/main/java/com/android/tools/r8/graph/GenericSignaturePartialTypeArgumentApplier.java b/src/main/java/com/android/tools/r8/graph/GenericSignaturePartialTypeArgumentApplier.java
index 12ca041..503000f 100644
--- a/src/main/java/com/android/tools/r8/graph/GenericSignaturePartialTypeArgumentApplier.java
+++ b/src/main/java/com/android/tools/r8/graph/GenericSignaturePartialTypeArgumentApplier.java
@@ -70,7 +70,7 @@
     if (classSignature.hasNoSignature() || classSignature.isInvalid()) {
       return classSignature;
     }
-    return classSignature.visit(this);
+    return classSignature.visit(this, appView.dexItemFactory());
   }
 
   @Override
@@ -206,8 +206,11 @@
   }
 
   @Override
-  public ClassTypeSignature visitSuperClass(ClassTypeSignature classTypeSignature) {
-    return classTypeSignature.visit(this);
+  public ClassTypeSignature visitSuperClass(ClassTypeSignature classTypeSignatureOrNullForObject) {
+    if (classTypeSignatureOrNullForObject == null) {
+      return classTypeSignatureOrNullForObject;
+    }
+    return classTypeSignatureOrNullForObject.visit(this);
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/graph/GenericSignaturePrinter.java b/src/main/java/com/android/tools/r8/graph/GenericSignaturePrinter.java
index 9d54705..cff0d21 100644
--- a/src/main/java/com/android/tools/r8/graph/GenericSignaturePrinter.java
+++ b/src/main/java/com/android/tools/r8/graph/GenericSignaturePrinter.java
@@ -31,7 +31,8 @@
 
   @Override
   public ClassSignature visitClassSignature(ClassSignature classSignature) {
-    return classSignature.visit(this);
+    classSignature.visitWithoutRewrite(this);
+    return classSignature;
   }
 
   @Override
@@ -108,9 +109,13 @@
   }
 
   @Override
-  public ClassTypeSignature visitSuperClass(ClassTypeSignature classTypeSignature) {
-    printFieldTypeSignature(classTypeSignature, false);
-    return classTypeSignature;
+  public ClassTypeSignature visitSuperClass(ClassTypeSignature classTypeSignatureOrNullForObject) {
+    if (classTypeSignatureOrNullForObject == null) {
+      sb.append("Ljava/lang/Object;");
+    } else {
+      printFieldTypeSignature(classTypeSignatureOrNullForObject, false);
+    }
+    return classTypeSignatureOrNullForObject;
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/graph/GenericSignatureTypeRewriter.java b/src/main/java/com/android/tools/r8/graph/GenericSignatureTypeRewriter.java
index 7f224bb..cdbc19b 100644
--- a/src/main/java/com/android/tools/r8/graph/GenericSignatureTypeRewriter.java
+++ b/src/main/java/com/android/tools/r8/graph/GenericSignatureTypeRewriter.java
@@ -61,7 +61,7 @@
     if (classSignature.hasNoSignature() || classSignature.isInvalid()) {
       return classSignature;
     }
-    return new GenericSignatureRewriter().visitClassSignature(classSignature);
+    return new GenericSignatureRewriter(factory).visitClassSignature(classSignature);
   }
 
   public FieldTypeSignature rewrite(FieldTypeSignature fieldTypeSignature) {
@@ -69,7 +69,7 @@
       return fieldTypeSignature;
     }
     FieldTypeSignature rewrittenSignature =
-        new GenericSignatureRewriter().visitFieldTypeSignature(fieldTypeSignature);
+        new GenericSignatureRewriter(factory).visitFieldTypeSignature(fieldTypeSignature);
     return rewrittenSignature == null ? FieldTypeSignature.noSignature() : rewrittenSignature;
   }
 
@@ -77,20 +77,20 @@
     if (methodTypeSignature.hasNoSignature() || methodTypeSignature.isInvalid()) {
       return methodTypeSignature;
     }
-    return new GenericSignatureRewriter().visitMethodSignature(methodTypeSignature);
+    return new GenericSignatureRewriter(factory).visitMethodSignature(methodTypeSignature);
   }
 
   private class GenericSignatureRewriter implements GenericSignatureVisitor {
 
+    private final DexItemFactory factory;
+
+    GenericSignatureRewriter(DexItemFactory factory) {
+      this.factory = factory;
+    }
+
     @Override
     public ClassSignature visitClassSignature(ClassSignature classSignature) {
-      ClassSignature rewritten = classSignature.visit(this);
-      if (rewritten.getFormalTypeParameters().isEmpty()
-          && rewritten.superInterfaceSignatures.isEmpty()
-          && rewritten.superClassSignature.type == factory.objectType) {
-        return ClassSignature.noSignature();
-      }
-      return rewritten;
+      return classSignature.visit(this, factory);
     }
 
     @Override
@@ -142,14 +142,12 @@
     }
 
     @Override
-    public ClassTypeSignature visitSuperClass(ClassTypeSignature classTypeSignature) {
-      if (context.superType == factory.objectType) {
-        return classTypeSignature.type == factory.objectType
-            ? classTypeSignature
-            : objectTypeSignature;
+    public ClassTypeSignature visitSuperClass(
+        ClassTypeSignature classTypeSignatureOrNullForObject) {
+      if (classTypeSignatureOrNullForObject == null) {
+        return classTypeSignatureOrNullForObject;
       }
-      ClassTypeSignature rewritten = classTypeSignature.visit(this);
-      return rewritten == null ? objectTypeSignature : rewritten;
+      return classTypeSignatureOrNullForObject.visit(this);
     }
 
     @Override
diff --git a/src/main/java/com/android/tools/r8/graph/GenericSignatureTypeVisitor.java b/src/main/java/com/android/tools/r8/graph/GenericSignatureTypeVisitor.java
index a619129..66e9682 100644
--- a/src/main/java/com/android/tools/r8/graph/GenericSignatureTypeVisitor.java
+++ b/src/main/java/com/android/tools/r8/graph/GenericSignatureTypeVisitor.java
@@ -30,7 +30,8 @@
     if (classSignature.hasNoSignature()) {
       return classSignature;
     }
-    return classSignature.visit(this);
+    classSignature.visitWithoutRewrite(this);
+    return classSignature;
   }
 
   @Override
@@ -87,16 +88,16 @@
   }
 
   @Override
-  public ClassTypeSignature visitSuperClass(ClassTypeSignature classTypeSignature) {
-    return classTypeSignature.visit(this);
+  public ClassTypeSignature visitSuperClass(ClassTypeSignature classTypeSignatureOrNullForObject) {
+    if (classTypeSignatureOrNullForObject == null) {
+      return classTypeSignatureOrNullForObject;
+    }
+    return classTypeSignatureOrNullForObject.visit(this);
   }
 
   @Override
   public List<ClassTypeSignature> visitSuperInterfaces(
       List<ClassTypeSignature> interfaceSignatures) {
-    if (interfaceSignatures == null) {
-      return null;
-    }
     interfaceSignatures.forEach(this::visitSuperInterface);
     return interfaceSignatures;
   }
diff --git a/src/main/java/com/android/tools/r8/graph/GenericSignatureVisitor.java b/src/main/java/com/android/tools/r8/graph/GenericSignatureVisitor.java
index 37b3cbe..4072b82 100644
--- a/src/main/java/com/android/tools/r8/graph/GenericSignatureVisitor.java
+++ b/src/main/java/com/android/tools/r8/graph/GenericSignatureVisitor.java
@@ -49,7 +49,7 @@
     throw new Unreachable("Implement if visited");
   }
 
-  default ClassTypeSignature visitSuperClass(ClassTypeSignature classTypeSignature) {
+  default ClassTypeSignature visitSuperClass(ClassTypeSignature classTypeSignatureOrNullForObject) {
     throw new Unreachable("Implement if visited");
   }
 
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 8cb3ed0..067e0ae 100644
--- a/src/main/java/com/android/tools/r8/graph/JarClassFileReader.java
+++ b/src/main/java/com/android/tools/r8/graph/JarClassFileReader.java
@@ -899,7 +899,13 @@
         parameterNames = new ArrayList<>(parameterCount);
         parameterFlags = new ArrayList<>(parameterCount);
       }
-      parameterNames.add(new DexValueString(parent.application.getFactory().createString(name)));
+      if (name == null) {
+        // The JVM spec defines a null entry as valid and indicating no parameter name.
+        // https://docs.oracle.com/javase/specs/jvms/se9/html/jvms-4.html#jvms-4.7.24
+        parameterNames.add(DexValueNull.NULL);
+      } else {
+        parameterNames.add(new DexValueString(parent.application.getFactory().createString(name)));
+      }
       parameterFlags.add(DexValueInt.create(access));
       super.visitParameter(name, access);
     }
diff --git a/src/main/java/com/android/tools/r8/graph/ProgramField.java b/src/main/java/com/android/tools/r8/graph/ProgramField.java
index 85146d6..e564a79 100644
--- a/src/main/java/com/android/tools/r8/graph/ProgramField.java
+++ b/src/main/java/com/android/tools/r8/graph/ProgramField.java
@@ -6,6 +6,9 @@
 
 import com.android.tools.r8.dex.IndexedItemCollection;
 import com.android.tools.r8.kotlin.KotlinFieldLevelInfo;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.shaking.KeepFieldInfo;
+import com.android.tools.r8.utils.InternalOptions;
 
 public class ProgramField extends DexClassAndField
     implements ProgramMember<DexEncodedField, DexField> {
@@ -23,6 +26,24 @@
     }
   }
 
+  @Override
+  public boolean isEffectivelyFinal(AppView<AppInfoWithLiveness> appView) {
+    FieldAccessFlags accessFlags = getAccessFlags();
+    FieldAccessInfo accessInfo =
+        appView.appInfo().getFieldAccessInfoCollection().get(getReference());
+    KeepFieldInfo keepInfo = appView.getKeepInfo(this);
+    InternalOptions options = appView.options();
+    return keepInfo.isOptimizationAllowed(options)
+        && keepInfo.isShrinkingAllowed(options)
+        && !accessInfo.hasReflectiveWrite()
+        && !accessInfo.isWrittenFromMethodHandle()
+        && accessInfo.isWrittenOnlyInMethodSatisfying(
+            method ->
+                method.getDefinition().isInitializer()
+                    && method.getAccessFlags().isStatic() == accessFlags.isStatic()
+                    && method.getHolder() == getHolder());
+  }
+
   public boolean isStructurallyEqualTo(ProgramField other) {
     return getDefinition() == other.getDefinition() && getHolder() == other.getHolder();
   }
diff --git a/src/main/java/com/android/tools/r8/graph/ThrowExceptionCode.java b/src/main/java/com/android/tools/r8/graph/ThrowExceptionCode.java
index 383504d..19d78c1 100644
--- a/src/main/java/com/android/tools/r8/graph/ThrowExceptionCode.java
+++ b/src/main/java/com/android/tools/r8/graph/ThrowExceptionCode.java
@@ -235,6 +235,12 @@
   }
 
   @Override
+  public DexWritableCacheKey getCacheLookupKey(ProgramMethod method, DexItemFactory factory) {
+    return new AmendedDexWritableCodeKey<DexType>(
+        this, exceptionType, getIncomingRegisterSize(method), getRegisterSize(method));
+  }
+
+  @Override
   public String toString() {
     return "ThrowExceptionCode";
   }
diff --git a/src/main/java/com/android/tools/r8/graph/ThrowNullCode.java b/src/main/java/com/android/tools/r8/graph/ThrowNullCode.java
index 82f320c..707e45a 100644
--- a/src/main/java/com/android/tools/r8/graph/ThrowNullCode.java
+++ b/src/main/java/com/android/tools/r8/graph/ThrowNullCode.java
@@ -274,6 +274,12 @@
     return "ThrowNullCode";
   }
 
+  @Override
+  public DexWritableCacheKey getCacheLookupKey(ProgramMethod method, DexItemFactory factory) {
+    return new AmendedDexWritableCodeKey<DexWritableCode>(
+        this, this, getIncomingRegisterSize(method), getRegisterSize(method));
+  }
+
   static class ThrowNullSourceCode extends SyntheticStraightLineSourceCode {
 
     ThrowNullSourceCode(ProgramMethod method) {
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/ValueMayDependOnEnvironmentAnalysis.java b/src/main/java/com/android/tools/r8/ir/analysis/ValueMayDependOnEnvironmentAnalysis.java
index 33ab115..f15e404 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/ValueMayDependOnEnvironmentAnalysis.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/ValueMayDependOnEnvironmentAnalysis.java
@@ -8,8 +8,9 @@
 
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexClassAndField;
+import com.android.tools.r8.graph.DexClassAndMethod;
 import com.android.tools.r8.graph.DexEncodedField;
-import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexProgramClass;
@@ -32,6 +33,7 @@
 import com.android.tools.r8.ir.code.StaticPut;
 import com.android.tools.r8.ir.code.Value;
 import com.android.tools.r8.ir.optimize.info.initializer.InstanceInitializerInfo;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.SetUtils;
 import com.android.tools.r8.utils.WorkList;
@@ -79,12 +81,12 @@
  */
 public class ValueMayDependOnEnvironmentAnalysis {
 
-  private final AppView<?> appView;
+  private final AppView<AppInfoWithLiveness> appView;
   private final ProgramMethod context;
   private final DexItemFactory dexItemFactory;
   private final InternalOptions options;
 
-  public ValueMayDependOnEnvironmentAnalysis(AppView<?> appView, IRCode code) {
+  public ValueMayDependOnEnvironmentAnalysis(AppView<AppInfoWithLiveness> appView, IRCode code) {
     this.appView = appView;
     this.context = code.context();
     this.dexItemFactory = appView.dexItemFactory();
@@ -319,21 +321,31 @@
 
     // Find the single constructor invocation.
     InvokeDirect constructorInvoke = newInstance.getUniqueConstructorInvoke(dexItemFactory);
-    if (constructorInvoke == null || constructorInvoke.getInvokedMethod().holder != clazz.type) {
-      // Didn't find a (valid) constructor invocation, give up.
+    if (constructorInvoke == null) {
+      // Didn't find a constructor invocation, give up.
       return false;
     }
 
     // Check that it is a trivial initializer (otherwise, the constructor could do anything).
-    DexEncodedMethod constructor = clazz.lookupMethod(constructorInvoke.getInvokedMethod());
+    DexClassAndMethod constructor =
+        appView
+            .appInfo()
+            .resolveMethod(
+                constructorInvoke.getInvokedMethod(), constructorInvoke.getInterfaceBit())
+            .getResolutionPair();
     if (constructor == null) {
       return false;
     }
 
+    if (!options.canInitNewInstanceUsingSuperclassConstructor()
+        && constructor.getHolder() != clazz) {
+      return false;
+    }
+
     InstanceInitializerInfo initializerInfo =
         constructor.getOptimizationInfo().getInstanceInitializerInfo(constructorInvoke);
 
-    List<DexEncodedField> fields = clazz.getDirectAndIndirectInstanceFields(appView);
+    List<DexClassAndField> fields = clazz.getDirectAndIndirectInstanceFields(appView);
     if (!fields.isEmpty()) {
       if (initializerInfo.instanceFieldInitializationMayDependOnEnvironment()) {
         return false;
@@ -348,8 +360,8 @@
 
       // Mark this value as mutable if it has a non-final field.
       boolean hasNonFinalField = false;
-      for (DexEncodedField field : fields) {
-        if (!field.isFinal()) {
+      for (DexClassAndField field : fields) {
+        if (!field.getAccessFlags().isFinal()) {
           hasNonFinalField = true;
           break;
         }
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/FieldAssignmentTracker.java b/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/FieldAssignmentTracker.java
index 0db5825..0781787 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/FieldAssignmentTracker.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/FieldAssignmentTracker.java
@@ -77,7 +77,7 @@
   private final Map<DexEncodedField, FieldState> fieldStates = new ConcurrentHashMap<>();
 
   private final Map<DexProgramClass, Map<DexEncodedField, AbstractValue>>
-      abstractInstanceFieldValues = new ConcurrentHashMap<>();
+      abstractFinalInstanceFieldValues = new ConcurrentHashMap<>();
 
   FieldAssignmentTracker(AppView<AppInfoWithLiveness> appView) {
     this.abstractValueFactory = appView.abstractValueFactory();
@@ -97,8 +97,8 @@
    * For each class with known allocation sites, adds a mapping from clazz -> instance field ->
    * bottom.
    *
-   * <p>If an entry (clazz, instance field) is missing in {@link #abstractInstanceFieldValues}, it
-   * is interpreted as if we known nothing about the value of the field.
+   * <p>If an entry (clazz, instance field) is missing in {@link #abstractFinalInstanceFieldValues},
+   * it is interpreted as if we known nothing about the value of the field.
    */
   private void initializeAbstractInstanceFieldValues() {
     FieldAccessInfoCollection<?> fieldAccessInfos =
@@ -116,15 +116,21 @@
             // No instance fields to track.
             return;
           }
-          Map<DexEncodedField, AbstractValue> abstractInstanceFieldValuesForClass =
+          Map<DexEncodedField, AbstractValue> abstractFinalInstanceFieldValuesForClass =
               new IdentityHashMap<>();
-          for (DexEncodedField field : clazz.instanceFields()) {
-            FieldAccessInfo fieldAccessInfo = fieldAccessInfos.get(field.getReference());
-            if (fieldAccessInfo != null && !fieldAccessInfo.hasReflectiveAccess()) {
-              abstractInstanceFieldValuesForClass.put(field, BottomValue.getInstance());
-            }
+          clazz.forEachProgramInstanceField(
+              field -> {
+                if (field.isFinalOrEffectivelyFinal(appView)) {
+                  FieldAccessInfo fieldAccessInfo = fieldAccessInfos.get(field.getReference());
+                  if (fieldAccessInfo != null && !fieldAccessInfo.hasReflectiveAccess()) {
+                    abstractFinalInstanceFieldValuesForClass.put(
+                        field.getDefinition(), BottomValue.getInstance());
+                  }
+                }
+              });
+          if (!abstractFinalInstanceFieldValuesForClass.isEmpty()) {
+            abstractFinalInstanceFieldValues.put(clazz, abstractFinalInstanceFieldValuesForClass);
           }
-          abstractInstanceFieldValues.put(clazz, abstractInstanceFieldValuesForClass);
         });
     for (DexProgramClass clazz : appView.appInfo().classes()) {
       clazz.forEachProgramField(
@@ -237,7 +243,7 @@
 
   void recordAllocationSite(NewInstance instruction, DexProgramClass clazz, ProgramMethod context) {
     Map<DexEncodedField, AbstractValue> abstractInstanceFieldValuesForClass =
-        abstractInstanceFieldValues.get(clazz);
+        abstractFinalInstanceFieldValues.get(clazz);
     if (abstractInstanceFieldValuesForClass == null) {
       // We are not tracking the value of any of clazz' instance fields.
       return;
@@ -246,14 +252,14 @@
     InvokeDirect invoke = instruction.getUniqueConstructorInvoke(dexItemFactory);
     if (invoke == null) {
       // We just lost track.
-      abstractInstanceFieldValues.remove(clazz);
+      abstractFinalInstanceFieldValues.remove(clazz);
       return;
     }
 
     DexClassAndMethod singleTarget = invoke.lookupSingleTarget(appView, context);
     if (singleTarget == null) {
       // We just lost track.
-      abstractInstanceFieldValues.remove(clazz);
+      abstractFinalInstanceFieldValues.remove(clazz);
       return;
     }
 
@@ -349,11 +355,11 @@
         assert WideningUtils.widenDynamicNonReceiverType(appView, dynamicType, field.getType())
             == dynamicType;
         if (dynamicType.isNotNullType()) {
-          feedback.markFieldHasDynamicType(field.getDefinition(), dynamicType);
+          feedback.markFieldHasDynamicType(field, dynamicType);
         } else {
           DynamicTypeWithUpperBound staticType = field.getType().toDynamicType(appView);
           if (dynamicType.asDynamicTypeWithUpperBound().strictlyLessThan(staticType, appView)) {
-            feedback.markFieldHasDynamicType(field.getDefinition(), dynamicType);
+            feedback.markFieldHasDynamicType(field, dynamicType);
           }
         }
       }
@@ -408,7 +414,7 @@
   private void recordAllAllocationsSitesProcessed(
       DexProgramClass clazz, OptimizationFeedbackDelayed feedback) {
     Map<DexEncodedField, AbstractValue> abstractInstanceFieldValuesForClass =
-        abstractInstanceFieldValues.get(clazz);
+        abstractFinalInstanceFieldValues.get(clazz);
     if (abstractInstanceFieldValuesForClass == null) {
       return;
     }
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/AbstractFieldSet.java b/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/AbstractFieldSet.java
index 99707bf..c35c35e 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/AbstractFieldSet.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/AbstractFieldSet.java
@@ -5,6 +5,7 @@
 package com.android.tools.r8.ir.analysis.fieldvalueanalysis;
 
 import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexClassAndField;
 import com.android.tools.r8.graph.DexEncodedField;
 import com.android.tools.r8.graph.PrunedItems;
 import com.android.tools.r8.graph.lens.GraphLens;
@@ -45,7 +46,7 @@
     return null;
   }
 
-  public abstract boolean contains(DexEncodedField field);
+  public abstract boolean contains(DexClassAndField field);
 
   public abstract boolean isEmpty();
 
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/ConcreteMutableFieldSet.java b/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/ConcreteMutableFieldSet.java
index b18d02a..41ffebf 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/ConcreteMutableFieldSet.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/ConcreteMutableFieldSet.java
@@ -6,6 +6,7 @@
 
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexClassAndField;
 import com.android.tools.r8.graph.DexEncodedField;
 import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.graph.PrunedItems;
@@ -72,6 +73,11 @@
   }
 
   @Override
+  public boolean contains(DexClassAndField field) {
+    return contains(field.getDefinition());
+  }
+
+  @Override
   public AbstractFieldSet fixupReadSetAfterParametersChanged(
       AppView<AppInfoWithLiveness> appView, ArgumentInfoCollection argumentInfoCollection) {
     assert !isEmpty();
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/EmptyFieldSet.java b/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/EmptyFieldSet.java
index e4efd96..6151aa1 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/EmptyFieldSet.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/EmptyFieldSet.java
@@ -5,6 +5,7 @@
 package com.android.tools.r8.ir.analysis.fieldvalueanalysis;
 
 import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexClassAndField;
 import com.android.tools.r8.graph.DexEncodedField;
 import com.android.tools.r8.graph.PrunedItems;
 import com.android.tools.r8.graph.lens.GraphLens;
@@ -38,6 +39,11 @@
   }
 
   @Override
+  public boolean contains(DexClassAndField field) {
+    return false;
+  }
+
+  @Override
   public boolean isBottom() {
     return true;
   }
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/FieldValueAnalysis.java b/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/FieldValueAnalysis.java
index 9c09ef2..ab97348 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/FieldValueAnalysis.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/FieldValueAnalysis.java
@@ -5,6 +5,7 @@
 package com.android.tools.r8.ir.analysis.fieldvalueanalysis;
 
 import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexClassAndField;
 import com.android.tools.r8.graph.DexEncodedField;
 import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.graph.DexValue;
@@ -26,12 +27,13 @@
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.utils.DequeUtils;
 import com.android.tools.r8.utils.ListUtils;
+import com.android.tools.r8.utils.collections.DexClassAndFieldMap;
+import com.google.common.collect.Iterables;
 import java.util.ArrayList;
 import java.util.Deque;
 import java.util.IdentityHashMap;
 import java.util.List;
 import java.util.Map;
-import java.util.Map.Entry;
 
 public abstract class FieldValueAnalysis {
 
@@ -55,7 +57,8 @@
   private DominatorTree dominatorTree;
   private Map<BasicBlock, AbstractFieldSet> fieldsMaybeReadBeforeBlockInclusiveCache;
 
-  final Map<DexEncodedField, List<FieldInitializationInfo>> putsPerField = new IdentityHashMap<>();
+  final DexClassAndFieldMap<List<FieldInitializationInfo>> putsPerField =
+      DexClassAndFieldMap.create();
 
   FieldValueAnalysis(
       AppView<AppInfoWithLiveness> appView, IRCode code, OptimizationFeedback feedback) {
@@ -95,16 +98,16 @@
     return null;
   }
 
-  abstract boolean isSubjectToOptimizationIgnoringPinning(DexEncodedField field);
+  abstract boolean isSubjectToOptimizationIgnoringPinning(DexClassAndField field);
 
-  abstract boolean isSubjectToOptimization(DexEncodedField field);
+  abstract boolean isSubjectToOptimization(DexClassAndField field);
 
-  void recordFieldPut(DexEncodedField field, Instruction instruction) {
+  void recordFieldPut(DexClassAndField field, Instruction instruction) {
     recordFieldPut(field, instruction, UnknownInstanceFieldInitializationInfo.getInstance());
   }
 
   void recordFieldPut(
-      DexEncodedField field, Instruction instruction, InstanceFieldInitializationInfo info) {
+      DexClassAndField field, Instruction instruction, InstanceFieldInitializationInfo info) {
     putsPerField
         .computeIfAbsent(field, ignore -> new ArrayList<>())
         .add(new FieldInitializationInfo(instruction, info));
@@ -116,24 +119,19 @@
 
     // Find all the static-put instructions that assign a field in the enclosing class which is
     // guaranteed to be assigned only in the current initializer.
-    boolean isStraightLineCode = true;
-    for (BasicBlock block : code.blocks) {
-      if (block.getSuccessors().size() >= 2) {
-        isStraightLineCode = false;
-      }
+    for (BasicBlock block : code.getBlocks()) {
       for (Instruction instruction : block.getInstructions()) {
         if (instruction.isFieldPut()) {
           FieldInstruction fieldPut = instruction.asFieldInstruction();
-          DexField field = fieldPut.getField();
-          ProgramField programField = appInfo.resolveField(field).getProgramField();
-          if (programField != null) {
-            DexEncodedField encodedField = programField.getDefinition();
-            if (isSubjectToOptimization(encodedField)) {
-              recordFieldPut(encodedField, fieldPut);
+          DexField fieldReference = fieldPut.getField();
+          ProgramField field = appInfo.resolveField(fieldReference).getProgramField();
+          if (field != null) {
+            if (isSubjectToOptimization(field)) {
+              recordFieldPut(field, fieldPut);
             } else if (isStaticFieldValueAnalysis()
-                && programField.getHolder().isEnum()
-                && isSubjectToOptimizationIgnoringPinning(encodedField)) {
-              recordFieldPut(encodedField, fieldPut);
+                && field.getHolder().isEnum()
+                && isSubjectToOptimizationIgnoringPinning(field)) {
+              recordFieldPut(field, fieldPut);
             }
           }
         } else if (isInstanceFieldValueAnalysis()
@@ -144,51 +142,52 @@
       }
     }
 
+    boolean isStraightLineCode =
+        Iterables.all(code.getBlocks(), block -> block.getSuccessors().size() <= 1);
     List<BasicBlock> normalExitBlocks = code.computeNormalExitBlocks();
-    for (Entry<DexEncodedField, List<FieldInitializationInfo>> entry : putsPerField.entrySet()) {
-      DexEncodedField field = entry.getKey();
-      List<FieldInitializationInfo> fieldPuts = entry.getValue();
-      if (fieldPuts.size() > 1) {
-        continue;
-      }
-      FieldInitializationInfo info = ListUtils.first(fieldPuts);
-      Instruction instruction = info.instruction;
-      if (instruction.isInvokeDirect()) {
-        asInstanceFieldValueAnalysis()
-            .recordInstanceFieldIsInitializedWithInfo(field, info.instanceFieldInitializationInfo);
-        continue;
-      }
-      FieldInstruction fieldPut = instruction.asFieldInstruction();
-      if (!isStraightLineCode) {
-        if (!getOrCreateDominatorTree().dominatesAllOf(fieldPut.getBlock(), normalExitBlocks)) {
-          continue;
-        }
-      }
-      boolean priorReadsWillReadSameValue =
-          !classInitializerDefaultsResult.hasStaticValue(field) && fieldPut.value().isZero();
-      if (!priorReadsWillReadSameValue && fieldMaybeReadBeforeInstruction(field, fieldPut)) {
-        // TODO(b/172528424): Generalize to InstanceFieldValueAnalysis.
-        if (isStaticFieldValueAnalysis()) {
-          // At this point the value read in the field can be only the default static value, if read
-          // prior to the put, or the value put, if read after the put. We still want to record it
-          // because the default static value is typically null/0, so code present after a null/0
-          // check can take advantage of the optimization.
-          DexValue valueBeforePut = classInitializerDefaultsResult.getStaticValue(field);
-          asStaticFieldValueAnalysis()
-              .updateFieldOptimizationInfoWith2Values(field, fieldPut.value(), valueBeforePut);
-        }
-        continue;
-      }
-      updateFieldOptimizationInfo(field, fieldPut, fieldPut.value());
-    }
+    putsPerField.forEach(
+        (field, fieldPuts) -> {
+          if (fieldPuts.size() > 1) {
+            return;
+          }
+          FieldInitializationInfo info = ListUtils.first(fieldPuts);
+          Instruction instruction = info.instruction;
+          if (instruction.isInvokeDirect()) {
+            asInstanceFieldValueAnalysis()
+                .recordInstanceFieldIsInitializedWithInfo(
+                    field, info.instanceFieldInitializationInfo);
+            return;
+          }
+          FieldInstruction fieldPut = instruction.asFieldInstruction();
+          if (!isStraightLineCode) {
+            if (!getOrCreateDominatorTree().dominatesAllOf(fieldPut.getBlock(), normalExitBlocks)) {
+              return;
+            }
+          }
+          boolean priorReadsWillReadSameValue =
+              !classInitializerDefaultsResult.hasStaticValue(field) && fieldPut.value().isZero();
+          if (!priorReadsWillReadSameValue && fieldMaybeReadBeforeInstruction(field, fieldPut)) {
+            // TODO(b/172528424): Generalize to InstanceFieldValueAnalysis.
+            if (isStaticFieldValueAnalysis()) {
+              // At this point the value read in the field can be only the default static value, if
+              // read prior to the put, or the value put, if read after the put. We still want to
+              // record it because the default static value is typically null/0, so code present
+              // after a null/0 check can take advantage of the optimization.
+              DexValue valueBeforePut = classInitializerDefaultsResult.getStaticValue(field);
+              asStaticFieldValueAnalysis()
+                  .updateFieldOptimizationInfoWith2Values(field, fieldPut.value(), valueBeforePut);
+            }
+            return;
+          }
+          updateFieldOptimizationInfo(field, fieldPut, fieldPut.value());
+        });
   }
 
-  private boolean fieldMaybeReadBeforeInstruction(
-      DexEncodedField encodedField, Instruction instruction) {
+  private boolean fieldMaybeReadBeforeInstruction(DexClassAndField field, Instruction instruction) {
     BasicBlock block = instruction.getBlock();
 
     // First check if the field may be read in any of the (transitive) predecessor blocks.
-    if (fieldMaybeReadBeforeBlock(encodedField, block)) {
+    if (fieldMaybeReadBeforeBlock(field, block)) {
       return true;
     }
 
@@ -200,7 +199,7 @@
       if (current == instruction) {
         break;
       }
-      if (current.readSet(appView, context).contains(encodedField)) {
+      if (current.readSet(appView, context).contains(field)) {
         return true;
       }
     }
@@ -209,18 +208,17 @@
     return false;
   }
 
-  private boolean fieldMaybeReadBeforeBlock(DexEncodedField encodedField, BasicBlock block) {
+  private boolean fieldMaybeReadBeforeBlock(DexClassAndField field, BasicBlock block) {
     for (BasicBlock predecessor : block.getPredecessors()) {
-      if (fieldMaybeReadBeforeBlockInclusive(encodedField, predecessor)) {
+      if (fieldMaybeReadBeforeBlockInclusive(field, predecessor)) {
         return true;
       }
     }
     return false;
   }
 
-  private boolean fieldMaybeReadBeforeBlockInclusive(
-      DexEncodedField encodedField, BasicBlock block) {
-    return getOrCreateFieldsMaybeReadBeforeBlockInclusive().get(block).contains(encodedField);
+  private boolean fieldMaybeReadBeforeBlockInclusive(DexClassAndField field, BasicBlock block) {
+    return getOrCreateFieldsMaybeReadBeforeBlockInclusive().get(block).contains(field);
   }
 
   /**
@@ -327,5 +325,5 @@
   }
 
   abstract void updateFieldOptimizationInfo(
-      DexEncodedField field, FieldInstruction fieldPut, Value value);
+      DexClassAndField field, FieldInstruction fieldPut, Value value);
 }
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/InstanceFieldValueAnalysis.java b/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/InstanceFieldValueAnalysis.java
index c4061ec..8b00bfb 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/InstanceFieldValueAnalysis.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/InstanceFieldValueAnalysis.java
@@ -8,8 +8,8 @@
 
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexClassAndField;
 import com.android.tools.r8.graph.DexClassAndMethod;
-import com.android.tools.r8.graph.DexEncodedField;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.analysis.type.ClassTypeElement;
@@ -22,6 +22,7 @@
 import com.android.tools.r8.ir.code.IRCodeUtils;
 import com.android.tools.r8.ir.code.InstancePut;
 import com.android.tools.r8.ir.code.Instruction;
+import com.android.tools.r8.ir.code.InstructionListIterator;
 import com.android.tools.r8.ir.code.InvokeDirect;
 import com.android.tools.r8.ir.code.Value;
 import com.android.tools.r8.ir.optimize.ClassInitializerDefaultsOptimization.ClassInitializerDefaultsResult;
@@ -119,17 +120,17 @@
   }
 
   @Override
-  boolean isSubjectToOptimization(DexEncodedField field) {
-    return !field.isStatic() && field.getHolderType() == context.getHolderType();
+  boolean isSubjectToOptimization(DexClassAndField field) {
+    return !field.getAccessFlags().isStatic() && field.getHolderType() == context.getHolderType();
   }
 
   @Override
-  boolean isSubjectToOptimizationIgnoringPinning(DexEncodedField field) {
+  boolean isSubjectToOptimizationIgnoringPinning(DexClassAndField field) {
     throw new Unreachable("Used by static analysis only.");
   }
 
   @Override
-  void updateFieldOptimizationInfo(DexEncodedField field, FieldInstruction fieldPut, Value value) {
+  void updateFieldOptimizationInfo(DexClassAndField field, FieldInstruction fieldPut, Value value) {
     if (fieldNeverWrittenBetweenInstancePutAndMethodExit(field, fieldPut.asInstancePut())) {
       recordInstanceFieldIsInitializedWithValue(field, value);
     }
@@ -154,7 +155,7 @@
             .getOptimizationInfo()
             .getInstanceInitializerInfo(invoke)
             .fieldInitializationInfos();
-    for (DexEncodedField field :
+    for (DexClassAndField field :
         singleTarget.getHolder().getDirectAndIndirectInstanceFields(appView)) {
       InstanceFieldInitializationInfo info = infos.get(field);
       if (info.isArgumentInitializationInfo()) {
@@ -193,7 +194,7 @@
   }
 
   private InstanceFieldInitializationInfo getInstanceFieldInitializationInfo(
-      DexEncodedField field, Value value) {
+      DexClassAndField field, Value value) {
     Value root = value.getAliasedValue();
     if (root.isDefinedByInstructionSatisfying(Instruction::isArgument)) {
       Argument argument = root.definition.asArgument();
@@ -203,7 +204,7 @@
     if (abstractValue.isSingleValue()) {
       return abstractValue.asSingleValue();
     }
-    DexType fieldType = field.type();
+    DexType fieldType = field.getType();
     if (fieldType.isClassType()) {
       ClassTypeElement dynamicLowerBoundType = value.getDynamicLowerBoundType(appView);
       TypeElement dynamicUpperBoundType = value.getDynamicUpperBoundType(appView);
@@ -216,20 +217,21 @@
   }
 
   void recordInstanceFieldIsInitializedWithInfo(
-      DexEncodedField field, InstanceFieldInitializationInfo info) {
-    if (!info.isUnknown()) {
+      DexClassAndField field, InstanceFieldInitializationInfo info) {
+    if (!info.isUnknown()
+        && appView.appInfo().mayPropagateValueFor(appView, field.getReference())) {
       builder.recordInitializationInfo(field, info);
     }
   }
 
-  void recordInstanceFieldIsInitializedWithValue(DexEncodedField field, Value value) {
+  void recordInstanceFieldIsInitializedWithValue(DexClassAndField field, Value value) {
     recordInstanceFieldIsInitializedWithInfo(
         field, getInstanceFieldInitializationInfo(field, value));
   }
 
   private boolean fieldNeverWrittenBetweenInstancePutAndMethodExit(
-      DexEncodedField field, InstancePut instancePut) {
-    if (field.isFinal()) {
+      DexClassAndField field, InstancePut instancePut) {
+    if (field.getAccessFlags().isFinal()) {
       return true;
     }
 
@@ -238,7 +240,7 @@
     }
 
     if (appView.appInfo().isInstanceFieldWrittenOnlyInInstanceInitializers(field)) {
-      if (parentConstructorCall.getInvokedMethod().holder != context.getHolderType()) {
+      if (parentConstructorCall.getInvokedMethod().getHolderType() != context.getHolderType()) {
         // The field is only written in instance initializers of the enclosing class, and the
         // constructor call targets a constructor in the super class.
         return true;
@@ -268,13 +270,27 @@
       throw new Unreachable();
     }
 
+    // Analyze all subsequent instructions.
+    // TODO(b/279877113): Extend this analysis to analyze the full remainder of this method.
+    if (instancePut.getBlock().getSuccessors().isEmpty()) {
+      InstructionListIterator instructionIterator =
+          instancePut.getBlock().listIterator(code, instancePut);
+      while (instructionIterator.hasNext()) {
+        Instruction instruction = instructionIterator.next();
+        if (instruction.readSet(appView, context).contains(field)) {
+          return false;
+        }
+      }
+      return true;
+    }
+
     // Otherwise, conservatively return false.
     return false;
   }
 
   private boolean fieldNeverWrittenBetweenParentConstructorCallAndMethodExit(
-      DexEncodedField field) {
-    if (field.isFinal()) {
+      DexClassAndField field) {
+    if (field.isFinalOrEffectivelyFinal(appView)) {
       return true;
     }
     if (appView.appInfo().isFieldOnlyWrittenInMethod(field, parentConstructor.getDefinition())) {
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/KnownFieldSet.java b/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/KnownFieldSet.java
index 263101b..83859a1 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/KnownFieldSet.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/KnownFieldSet.java
@@ -4,12 +4,15 @@
 
 package com.android.tools.r8.ir.analysis.fieldvalueanalysis;
 
+import com.android.tools.r8.graph.DexClassAndField;
 import com.android.tools.r8.graph.DexEncodedField;
 
 public interface KnownFieldSet {
 
   boolean contains(DexEncodedField field);
 
+  boolean contains(DexClassAndField field);
+
   default boolean isConcreteFieldSet() {
     return false;
   }
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/StaticFieldValueAnalysis.java b/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/StaticFieldValueAnalysis.java
index 4df6b75..cc62515 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/StaticFieldValueAnalysis.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/StaticFieldValueAnalysis.java
@@ -11,6 +11,7 @@
 
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexClassAndField;
 import com.android.tools.r8.graph.DexEncodedField;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.DexValue;
@@ -113,19 +114,20 @@
           } else {
             assert false : value.getClass().getName();
           }
-        });
+        },
+        appView);
   }
 
   @Override
-  boolean isSubjectToOptimization(DexEncodedField field) {
-    return field.isStatic()
+  boolean isSubjectToOptimization(DexClassAndField field) {
+    return field.getAccessFlags().isStatic()
         && field.getHolderType() == context.getHolderType()
         && appView.appInfo().isFieldOnlyWrittenInMethod(field, context.getDefinition());
   }
 
   @Override
-  boolean isSubjectToOptimizationIgnoringPinning(DexEncodedField field) {
-    return field.isStatic()
+  boolean isSubjectToOptimizationIgnoringPinning(DexClassAndField field) {
+    return field.getAccessFlags().isStatic()
         && field.getHolderType() == context.getHolderType()
         && appView
             .appInfo()
@@ -133,13 +135,13 @@
   }
 
   @Override
-  void updateFieldOptimizationInfo(DexEncodedField field, FieldInstruction fieldPut, Value value) {
+  void updateFieldOptimizationInfo(DexClassAndField field, FieldInstruction fieldPut, Value value) {
     AbstractValue abstractValue = getOrComputeAbstractValue(value, field);
     updateFieldOptimizationInfo(field, value, abstractValue, false);
   }
 
   void updateFieldOptimizationInfo(
-      DexEncodedField field, Value value, AbstractValue abstractValue, boolean maybeNull) {
+      DexClassAndField field, Value value, AbstractValue abstractValue, boolean maybeNull) {
     builder.recordStaticField(field, abstractValue, appView.dexItemFactory());
 
     // We cannot modify FieldOptimizationInfo of pinned fields.
@@ -165,7 +167,7 @@
   }
 
   public void updateFieldOptimizationInfoWith2Values(
-      DexEncodedField field, Value valuePut, DexValue valueBeforePut) {
+      DexClassAndField field, Value valuePut, DexValue valueBeforePut) {
     // We are interested in the AbstractValue only if it's null or a value, so we can use the value
     // if the code is protected by a null check.
     if (valueBeforePut != DexValueNull.NULL) {
@@ -177,7 +179,7 @@
     updateFieldOptimizationInfo(field, valuePut, abstractValue, true);
   }
 
-  private AbstractValue getOrComputeAbstractValue(Value value, DexEncodedField field) {
+  private AbstractValue getOrComputeAbstractValue(Value value, DexClassAndField field) {
     Value root = value.getAliasedValue();
     AbstractValue abstractValue = root.getAbstractValue(appView, context);
     if (!abstractValue.isSingleValue()) {
@@ -186,7 +188,7 @@
     return abstractValue;
   }
 
-  private SingleFieldValue computeSingleFieldValue(DexEncodedField field, Value value) {
+  private SingleFieldValue computeSingleFieldValue(DexClassAndField field, Value value) {
     assert !value.hasAliasedValue();
     SingleFieldValue result = computeSingleEnumFieldValue(value);
     if (result != null) {
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/StaticFieldValues.java b/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/StaticFieldValues.java
index 8e5b4ca..0b9e480 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/StaticFieldValues.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/StaticFieldValues.java
@@ -5,7 +5,7 @@
 package com.android.tools.r8.ir.analysis.fieldvalueanalysis;
 
 import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.DexEncodedField;
+import com.android.tools.r8.graph.DexClassAndField;
 import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexProgramClass;
@@ -32,7 +32,7 @@
   public abstract static class Builder {
 
     public abstract void recordStaticField(
-        DexEncodedField staticField, AbstractValue value, DexItemFactory factory);
+        DexClassAndField staticField, AbstractValue value, DexItemFactory factory);
 
     public abstract StaticFieldValues build();
   }
@@ -69,7 +69,7 @@
 
       @Override
       public void recordStaticField(
-          DexEncodedField staticField, AbstractValue value, DexItemFactory factory) {
+          DexClassAndField staticField, AbstractValue value, DexItemFactory factory) {
         if (factory.enumMembers.isValuesFieldCandidate(staticField, staticField.getHolderType())) {
           if (value.isSingleFieldValue()
               && value.asSingleFieldValue().getObjectState().isEnumValuesObjectState()) {
@@ -131,7 +131,7 @@
 
       @Override
       public void recordStaticField(
-          DexEncodedField staticField, AbstractValue value, DexItemFactory factory) {
+          DexClassAndField staticField, AbstractValue value, DexItemFactory factory) {
         // Do nothing.
       }
 
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/UnknownFieldSet.java b/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/UnknownFieldSet.java
index 6131581..09e336e 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/UnknownFieldSet.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/UnknownFieldSet.java
@@ -5,7 +5,7 @@
 package com.android.tools.r8.ir.analysis.fieldvalueanalysis;
 
 import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.DexEncodedField;
+import com.android.tools.r8.graph.DexClassAndField;
 import com.android.tools.r8.graph.PrunedItems;
 import com.android.tools.r8.graph.lens.GraphLens;
 import com.android.tools.r8.graph.proto.ArgumentInfoCollection;
@@ -22,7 +22,7 @@
   }
 
   @Override
-  public boolean contains(DexEncodedField field) {
+  public boolean contains(DexClassAndField field) {
     return true;
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/modeling/LibraryMethodReadSetModeling.java b/src/main/java/com/android/tools/r8/ir/analysis/modeling/LibraryMethodReadSetModeling.java
index 9a61590..95d68a4 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/modeling/LibraryMethodReadSetModeling.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/modeling/LibraryMethodReadSetModeling.java
@@ -28,6 +28,14 @@
       return EmptyFieldSet.getInstance();
     }
 
+    // Model that checkNotNullParameter() does not read any instance fields of the app. This is
+    // currently needed for constructors that call checkNotNullParameter() not to be marked as
+    // reading any field.
+    if (invokedMethod == appView.dexItemFactory().kotlin.intrinsics.checkNotNullParameter
+        || invokedMethod == appView.dexItemFactory().kotlin.intrinsics.checkParameterIsNotNull) {
+      return EmptyFieldSet.getInstance();
+    }
+
     // Already handled above.
     assert !appView.dexItemFactory().classMethods.isReflectiveNameLookup(invokedMethod);
 
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/proto/EnumLiteProtoShrinker.java b/src/main/java/com/android/tools/r8/ir/analysis/proto/EnumLiteProtoShrinker.java
index e527832..75e4631 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/proto/EnumLiteProtoShrinker.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/proto/EnumLiteProtoShrinker.java
@@ -6,7 +6,7 @@
 
 import com.android.tools.r8.errors.CompilationError;
 import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.DexEncodedField;
+import com.android.tools.r8.graph.DexClassAndField;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.graph.DexMethod;
@@ -84,8 +84,8 @@
     if (clazz.getInterfaces().contains(references.enumLiteMapType)) {
       DexProgramClass enumLite = computeCorrespondingEnumLite(clazz);
       if (enumLite != null) {
-        DexEncodedField field =
-            enumLite.lookupField(createInternalValueMapField(enumLite.getType()));
+        DexClassAndField field =
+            enumLite.lookupClassField(createInternalValueMapField(enumLite.getType()));
         if (field == null) {
           return false;
         }
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/proto/GeneratedMessageLiteBuilderShrinker.java b/src/main/java/com/android/tools/r8/ir/analysis/proto/GeneratedMessageLiteBuilderShrinker.java
index 2ff901b..c6658c4 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/proto/GeneratedMessageLiteBuilderShrinker.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/proto/GeneratedMessageLiteBuilderShrinker.java
@@ -43,11 +43,11 @@
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.shaking.Enqueuer;
 import com.android.tools.r8.shaking.EnqueuerWorklist;
+import com.android.tools.r8.utils.Box;
 import com.android.tools.r8.utils.ObjectUtils;
 import com.android.tools.r8.utils.PredicateSet;
 import com.android.tools.r8.utils.ThreadUtils;
 import com.android.tools.r8.utils.Timing;
-import com.google.common.collect.ImmutableList;
 import com.google.common.collect.Sets;
 import java.util.ArrayList;
 import java.util.IdentityHashMap;
@@ -220,11 +220,8 @@
     assert builder.superType == references.generatedMessageLiteBuilderType
         || builder.superType == references.generatedMessageLiteExtendableBuilderType;
 
-    DexField defaultInstanceField = references.getDefaultInstanceField(dynamicMethod.getHolder());
     Value builderValue =
         code.createValue(ClassTypeElement.create(builder.superType, definitelyNotNull(), appView));
-    Value defaultInstanceValue =
-        code.createValue(ClassTypeElement.create(defaultInstanceField.type, maybeNull(), appView));
 
     // Replace `new Message.Builder()` by `new GeneratedMessageLite.Builder()`
     // (or `new GeneratedMessageLite.ExtendableBuilder()`).
@@ -239,23 +236,47 @@
     //
     // We may also see an accessibility bridge constructor, because the Builder constructor is
     // private. The accessibility bridge takes null as an argument.
+    DexField defaultInstanceField = references.getDefaultInstanceField(dynamicMethod.getHolder());
+    Box<Value> existingDefaultInstanceValue = new Box<>();
     InvokeDirect constructorInvoke =
         instructionIterator.nextUntil(
             instruction -> {
+              // After constructor inlining we may see a load of the DEFAULT_INSTANCE field.
+              if (instruction.isStaticGet()) {
+                StaticGet staticGet = instruction.asStaticGet();
+                if (staticGet.getField() == defaultInstanceField) {
+                  existingDefaultInstanceValue.set(staticGet.outValue());
+                  return false;
+                }
+              }
               assert instruction.isInvokeDirect() || instruction.isConstNumber();
               return instruction.isInvokeDirect();
             });
     assert constructorInvoke != null;
-    instructionIterator.replaceCurrentInstruction(
-        new StaticGet(defaultInstanceValue, defaultInstanceField));
-    instructionIterator.setInsertionPosition(constructorInvoke.getPosition());
-    instructionIterator.add(
-        new InvokeDirect(
-            builder.superType == references.generatedMessageLiteBuilderType
-                ? references.generatedMessageLiteBuilderMethods.constructorMethod
-                : references.generatedMessageLiteExtendableBuilderMethods.constructorMethod,
-            null,
-            ImmutableList.of(builderValue, defaultInstanceValue)));
+
+    DexMethod constructorMethod =
+        builder.superType == references.generatedMessageLiteBuilderType
+            ? references.generatedMessageLiteBuilderMethods.constructorMethod
+            : references.generatedMessageLiteExtendableBuilderMethods.constructorMethod;
+    if (existingDefaultInstanceValue.isSet()) {
+      instructionIterator.replaceCurrentInstruction(
+          InvokeDirect.builder()
+              .setArguments(builderValue, existingDefaultInstanceValue.get())
+              .setMethod(constructorMethod)
+              .build());
+    } else {
+      Value defaultInstanceValue =
+          code.createValue(
+              ClassTypeElement.create(defaultInstanceField.type, maybeNull(), appView));
+      instructionIterator.replaceCurrentInstruction(
+          new StaticGet(defaultInstanceValue, defaultInstanceField));
+      instructionIterator.setInsertionPosition(constructorInvoke.getPosition());
+      instructionIterator.add(
+          InvokeDirect.builder()
+              .setArguments(builderValue, defaultInstanceValue)
+              .setMethod(constructorMethod)
+              .build());
+    }
 
     converter.removeDeadCodeAndFinalizeIR(
         code, OptimizationFeedbackSimple.getInstance(), Timing.empty());
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/proto/GeneratedMessageLiteShrinker.java b/src/main/java/com/android/tools/r8/ir/analysis/proto/GeneratedMessageLiteShrinker.java
index ca151c5..809f0ad 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/proto/GeneratedMessageLiteShrinker.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/proto/GeneratedMessageLiteShrinker.java
@@ -14,9 +14,11 @@
 import com.android.tools.r8.graph.AccessControl;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexMember;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.ProgramMember;
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.analysis.proto.schema.ProtoMessageInfo;
 import com.android.tools.r8.ir.analysis.proto.schema.ProtoObject;
@@ -110,6 +112,16 @@
             }
           });
     }
+
+    DexProgramClass rawMessageInfoClass =
+        asProgramClassOrNull(
+            appView.appInfo().definitionForWithoutExistenceAssert(references.rawMessageInfoType));
+    if (rawMessageInfoClass != null) {
+      disallowOptimization(
+          rawMessageInfoClass, references.rawMessageInfoInfoField, dependentMinimumKeepInfo);
+      disallowOptimization(
+          rawMessageInfoClass, references.rawMessageInfoObjectsField, dependentMinimumKeepInfo);
+    }
   }
 
   private void disallowSignatureOptimizations(KeepMethodInfo.Joiner methodJoiner) {
@@ -124,6 +136,18 @@
         .disallowUnusedReturnValueOptimization();
   }
 
+  private void disallowOptimization(
+      DexProgramClass clazz,
+      DexMember<?, ?> reference,
+      DependentMinimumKeepInfoCollection dependentMinimumKeepInfo) {
+    ProgramMember<?, ?> member = clazz.lookupProgramMember(reference);
+    if (member != null) {
+      dependentMinimumKeepInfo
+          .getOrCreateUnconditionalMinimumKeepInfoFor(reference)
+          .disallowOptimization();
+    }
+  }
+
   public void run(IRCode code) {
     ProgramMethod method = code.context();
     if (references.isDynamicMethod(method.getReference())) {
@@ -363,7 +387,7 @@
     for (Instruction instruction : code.instructions()) {
       if (instruction.isInvokeMethod()) {
         InvokeMethod invoke = instruction.asInvokeMethod();
-        if (references.isMessageInfoConstructionMethod(invoke.getInvokedMethod())) {
+        if (references.isMessageInfoConstruction(invoke)) {
           return invoke;
         }
       }
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/proto/ProtoReferences.java b/src/main/java/com/android/tools/r8/ir/analysis/proto/ProtoReferences.java
index 31633de..0b15b9a 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/proto/ProtoReferences.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/proto/ProtoReferences.java
@@ -13,6 +13,9 @@
 import com.android.tools.r8.graph.DexString;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.ir.code.Instruction;
+import com.android.tools.r8.ir.code.InvokeMethod;
+import com.android.tools.r8.ir.code.NewInstance;
 import com.android.tools.r8.ir.code.Value;
 import java.util.function.Consumer;
 
@@ -59,6 +62,8 @@
   public final DexMethod dynamicMethod;
   public final DexMethod newMessageInfoMethod;
   public final DexMethod rawMessageInfoConstructor;
+  public final DexField rawMessageInfoInfoField;
+  public final DexField rawMessageInfoObjectsField;
 
   public ProtoReferences(DexItemFactory factory) {
     dexItemFactory = factory;
@@ -122,6 +127,11 @@
                 factory.voidType, messageLiteType, factory.stringType, factory.objectArrayType),
             factory.constructorMethodName);
 
+    // Fields.
+    rawMessageInfoInfoField = factory.createField(rawMessageInfoType, factory.stringType, "info");
+    rawMessageInfoObjectsField =
+        factory.createField(rawMessageInfoType, factory.objectArrayType, "objects");
+
     generatedExtensionMethods = new GeneratedExtensionMethods(factory);
     generatedMessageLiteMethods = new GeneratedMessageLiteMethods(factory);
     generatedMessageLiteBuilderMethods = new GeneratedMessageLiteBuilderMethods(factory);
@@ -130,6 +140,10 @@
     methodToInvokeMembers = new MethodToInvokeMembers(factory);
   }
 
+  public DexItemFactory dexItemFactory() {
+    return dexItemFactory;
+  }
+
   public void forEachMethodReference(Consumer<DexMethod> consumer) {
     generatedExtensionMethods.forEachMethodReference(consumer);
     generatedMessageLiteMethods.forEachMethodReference(consumer);
@@ -195,8 +209,18 @@
         && !isAbstractGeneratedMessageLiteBuilder(clazz);
   }
 
-  public boolean isMessageInfoConstructionMethod(DexMethod method) {
-    return method.match(newMessageInfoMethod) || method == rawMessageInfoConstructor;
+  public boolean isMessageInfoConstruction(InvokeMethod invoke) {
+    if (invoke.getInvokedMethod().match(newMessageInfoMethod)) {
+      return true;
+    }
+    if (invoke.isInvokeConstructor(dexItemFactory)) {
+      Value receiver = invoke.asInvokeDirect().getReceiver();
+      if (receiver.isDefinedByInstructionSatisfying(Instruction::isNewInstance)) {
+        NewInstance newInstance = receiver.getDefinition().asNewInstance();
+        return newInstance.getType() == rawMessageInfoType;
+      }
+    }
+    return false;
   }
 
   public boolean isProtoLibraryClass(DexProgramClass clazz) {
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/proto/ProtoUtils.java b/src/main/java/com/android/tools/r8/ir/analysis/proto/ProtoUtils.java
index 82b4571..b6c1f4d 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/proto/ProtoUtils.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/proto/ProtoUtils.java
@@ -4,9 +4,11 @@
 
 package com.android.tools.r8.ir.analysis.proto;
 
+import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.ir.code.InstancePut;
+import com.android.tools.r8.ir.code.Instruction;
 import com.android.tools.r8.ir.code.InvokeMethod;
 import com.android.tools.r8.ir.code.Value;
-import com.android.tools.r8.utils.BooleanUtils;
 
 public class ProtoUtils {
 
@@ -14,23 +16,74 @@
 
   public static Value getInfoValueFromMessageInfoConstructionInvoke(
       InvokeMethod invoke, ProtoReferences references) {
-    assert references.isMessageInfoConstructionMethod(invoke.getInvokedMethod());
-    int adjustment = BooleanUtils.intValue(invoke.isInvokeDirect());
-    return invoke.inValues().get(1 + adjustment).getAliasedValue();
+    assert references.isMessageInfoConstruction(invoke);
+    // First check if there is a call to the static method newMessageInfo(...).
+    if (invoke.getInvokedMethod().match(references.newMessageInfoMethod)) {
+      return invoke.getOperand(1);
+    }
+    // Otherwise, the static method has been inlined. Check if there is a call to
+    // RawMessageInfo.<init>(...).
+    assert invoke.isInvokeDirect();
+    if (invoke.getInvokedMethod() == references.rawMessageInfoConstructor) {
+      return invoke.getOperand(2);
+    }
+    // Otherwise, RawMessageInfo.<init>(...) has been inlined, and we should find a call to
+    // Object.<init>(). In this case, we should find an instance field assignment to
+    // `RawMessageInfo.info`.
+    assert invoke.getInvokedMethod() == references.dexItemFactory().objectMembers.constructor;
+    // Find the value being assigned to the `info` field.
+    for (InstancePut instancePut :
+        invoke.getFirstArgument().<InstancePut>uniqueUsers(Instruction::isInstancePut)) {
+      if (instancePut.getField() == references.rawMessageInfoInfoField) {
+        return instancePut.value();
+      }
+    }
+    throw new Unreachable();
   }
 
   static Value getObjectsValueFromMessageInfoConstructionInvoke(
       InvokeMethod invoke, ProtoReferences references) {
-    assert references.isMessageInfoConstructionMethod(invoke.getInvokedMethod());
-    int adjustment = BooleanUtils.intValue(invoke.isInvokeDirect());
-    return invoke.inValues().get(2 + adjustment).getAliasedValue();
+    assert references.isMessageInfoConstruction(invoke);
+    if (invoke.getInvokedMethod().match(references.newMessageInfoMethod)) {
+      return invoke.getOperand(2);
+    }
+    assert invoke.isInvokeDirect();
+    if (invoke.getInvokedMethod() == references.rawMessageInfoConstructor) {
+      return invoke.getOperand(3);
+    }
+    assert invoke.getInvokedMethod() == references.dexItemFactory().objectMembers.constructor;
+    // Find the value being assigned to the `info` field.
+    for (InstancePut instancePut :
+        invoke.getFirstArgument().<InstancePut>uniqueUsers(Instruction::isInstancePut)) {
+      if (instancePut.getField() == references.rawMessageInfoObjectsField) {
+        return instancePut.value();
+      }
+    }
+    throw new Unreachable();
   }
 
   static void setObjectsValueForMessageInfoConstructionInvoke(
       InvokeMethod invoke, Value newObjectsValue, ProtoReferences references) {
-    assert references.isMessageInfoConstructionMethod(invoke.getInvokedMethod());
-    int adjustment = BooleanUtils.intValue(invoke.isInvokeDirect());
-    invoke.replaceValue(2 + adjustment, newObjectsValue);
+    assert references.isMessageInfoConstruction(invoke);
+    if (invoke.getInvokedMethod().match(references.newMessageInfoMethod)) {
+      invoke.replaceValue(2, newObjectsValue);
+      return;
+    }
+    assert invoke.isInvokeDirect();
+    if (invoke.getInvokedMethod() == references.rawMessageInfoConstructor) {
+      invoke.replaceValue(3, newObjectsValue);
+      return;
+    }
+    assert invoke.getInvokedMethod() == references.dexItemFactory().objectMembers.constructor;
+    // Find the value being assigned to the `info` field.
+    for (InstancePut instancePut :
+        invoke.getFirstArgument().<InstancePut>uniqueUsers(Instruction::isInstancePut)) {
+      if (instancePut.getField() == references.rawMessageInfoObjectsField) {
+        instancePut.setValue(newObjectsValue);
+        return;
+      }
+    }
+    throw new Unreachable();
   }
 
   public static boolean isProto2(int flags) {
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/proto/RawMessageInfoDecoder.java b/src/main/java/com/android/tools/r8/ir/analysis/proto/RawMessageInfoDecoder.java
index 00182d3..3cf7971 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/proto/RawMessageInfoDecoder.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/proto/RawMessageInfoDecoder.java
@@ -79,7 +79,7 @@
   }
 
   public ProtoMessageInfo run(ProgramMethod dynamicMethod, InvokeMethod invoke) {
-    assert references.isMessageInfoConstructionMethod(invoke.getInvokedMethod());
+    assert references.isMessageInfoConstruction(invoke);
     Value infoValue = getInfoValueFromMessageInfoConstructionInvoke(invoke, references);
     Value objectsValue = getObjectsValueFromMessageInfoConstructionInvoke(invoke, references);
     return run(dynamicMethod, infoValue, objectsValue);
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/proto/schema/ProtoEnqueuerExtension.java b/src/main/java/com/android/tools/r8/ir/analysis/proto/schema/ProtoEnqueuerExtension.java
index e84fc5a..61747c0 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/proto/schema/ProtoEnqueuerExtension.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/proto/schema/ProtoEnqueuerExtension.java
@@ -447,13 +447,18 @@
         if (newlyLiveField != null) {
           // Mark hazzer and one-of proto fields as read from dynamicMethod() if they are written in
           // the app. This is needed to ensure that field writes are not removed from the app.
-          ProgramMethod defaultInitializer =
-              dynamicMethod.getHolder().getProgramDefaultInitializer();
-          assert defaultInitializer != null;
           Predicate<ProgramMethod> neitherDefaultConstructorNorDynamicMethod =
-              writer ->
-                  !writer.isStructurallyEqualTo(defaultInitializer)
-                      && !writer.isStructurallyEqualTo(dynamicMethod);
+              writer -> {
+                if (dynamicMethod.getHolder().hasDefaultInitializer()
+                    && writer.isStructurallyEqualTo(
+                        dynamicMethod.getHolder().getProgramDefaultInitializer())) {
+                  return false;
+                }
+                if (writer.isStructurallyEqualTo(dynamicMethod)) {
+                  return false;
+                }
+                return true;
+              };
           if (enqueuer.isFieldWrittenInMethodSatisfying(
               newlyLiveField, neitherDefaultConstructorNorDynamicMethod)) {
             worklist.enqueueTraceReflectiveFieldReadAction(newlyLiveField, dynamicMethod);
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/value/objectstate/ObjectState.java b/src/main/java/com/android/tools/r8/ir/analysis/value/objectstate/ObjectState.java
index 00165e5..3b02fc4 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/value/objectstate/ObjectState.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/value/objectstate/ObjectState.java
@@ -6,6 +6,7 @@
 
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexClassAndField;
 import com.android.tools.r8.graph.DexEncodedField;
 import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.graph.ProgramMethod;
@@ -80,7 +81,7 @@
 
     private final Map<DexField, AbstractValue> state = new IdentityHashMap<>();
 
-    public void recordFieldHasValue(DexEncodedField field, AbstractValue abstractValue) {
+    public void recordFieldHasValue(DexClassAndField field, AbstractValue abstractValue) {
       if (!abstractValue.isUnknown()) {
         assert !state.containsKey(field.getReference());
         state.put(field.getReference(), abstractValue);
diff --git a/src/main/java/com/android/tools/r8/ir/code/ArrayPut.java b/src/main/java/com/android/tools/r8/ir/code/ArrayPut.java
index eff1adc..483e19d 100644
--- a/src/main/java/com/android/tools/r8/ir/code/ArrayPut.java
+++ b/src/main/java/com/android/tools/r8/ir/code/ArrayPut.java
@@ -190,9 +190,13 @@
 
   @Override
   public DeadInstructionResult canBeDeadCode(AppView<?> appView, IRCode code) {
-    return instructionInstanceCanThrow(appView, code.context())
-        ? DeadInstructionResult.notDead()
-        : DeadInstructionResult.deadIfInValueIsDead(array());
+    if (!instructionInstanceCanThrow(appView, code.context())) {
+      Value arrayRoot = array().getAliasedValue();
+      if (arrayRoot.isDefinedByInstructionSatisfying(Instruction::isCreatingArray)) {
+        return DeadInstructionResult.deadIfInValueIsDead(arrayRoot);
+      }
+    }
+    return DeadInstructionResult.notDead();
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/BasicBlockInstructionListIterator.java b/src/main/java/com/android/tools/r8/ir/code/BasicBlockInstructionListIterator.java
index afe8e0b..bc3297f 100644
--- a/src/main/java/com/android/tools/r8/ir/code/BasicBlockInstructionListIterator.java
+++ b/src/main/java/com/android/tools/r8/ir/code/BasicBlockInstructionListIterator.java
@@ -29,6 +29,7 @@
 import com.google.common.collect.Iterables;
 import com.google.common.collect.Sets;
 import java.util.ArrayList;
+import java.util.Collection;
 import java.util.List;
 import java.util.ListIterator;
 import java.util.Set;
@@ -168,6 +169,14 @@
     metadata.record(instruction);
   }
 
+  @Override
+  public void set(Collection<Instruction> instructions) {
+    for (Instruction instruction : instructions) {
+      set(instruction);
+      next();
+    }
+  }
+
   /**
    * Remove the current instruction (aka the {@link Instruction} returned by the previous call to
    * {@link #next}.
diff --git a/src/main/java/com/android/tools/r8/ir/code/ConstNumber.java b/src/main/java/com/android/tools/r8/ir/code/ConstNumber.java
index cae2002..00b983b 100644
--- a/src/main/java/com/android/tools/r8/ir/code/ConstNumber.java
+++ b/src/main/java/com/android/tools/r8/ir/code/ConstNumber.java
@@ -54,6 +54,10 @@
     return instruction.asConstNumber();
   }
 
+  public static Builder builder() {
+    return new Builder();
+  }
+
   @Override
   public int opcode() {
     return Opcodes.CONST_NUMBER;
@@ -349,4 +353,24 @@
   public void buildLir(LirBuilder<Value, ?> builder) {
     builder.addConstNumber(outType(), value);
   }
+
+  public static class Builder extends BuilderBase<Builder, ConstNumber> {
+
+    private long value;
+
+    public Builder setValue(long value) {
+      this.value = value;
+      return this;
+    }
+
+    @Override
+    public ConstNumber build() {
+      return amend(new ConstNumber(outValue, value));
+    }
+
+    @Override
+    public Builder self() {
+      return this;
+    }
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/code/ConstString.java b/src/main/java/com/android/tools/r8/ir/code/ConstString.java
index e2ce753..803223f 100644
--- a/src/main/java/com/android/tools/r8/ir/code/ConstString.java
+++ b/src/main/java/com/android/tools/r8/ir/code/ConstString.java
@@ -34,6 +34,10 @@
     this.value = value;
   }
 
+  public static Builder builder() {
+    return new Builder();
+  }
+
   @Override
   public int opcode() {
     return Opcodes.CONST_STRING;
@@ -185,4 +189,24 @@
   public void buildLir(LirBuilder<Value, ?> builder) {
     builder.addConstString(value);
   }
+
+  public static class Builder extends BuilderBase<Builder, ConstString> {
+
+    private DexString value;
+
+    public Builder setValue(DexString value) {
+      this.value = value;
+      return this;
+    }
+
+    @Override
+    public ConstString build() {
+      return amend(new ConstString(outValue, value));
+    }
+
+    @Override
+    public Builder self() {
+      return this;
+    }
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/code/DebugLocalRead.java b/src/main/java/com/android/tools/r8/ir/code/DebugLocalRead.java
index 5a3e094..3e8e999 100644
--- a/src/main/java/com/android/tools/r8/ir/code/DebugLocalRead.java
+++ b/src/main/java/com/android/tools/r8/ir/code/DebugLocalRead.java
@@ -12,6 +12,7 @@
 import com.android.tools.r8.ir.optimize.DeadCodeRemover.DeadInstructionResult;
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
 import com.android.tools.r8.ir.optimize.InliningConstraints;
+import com.android.tools.r8.lightir.LirBuilder;
 
 public class DebugLocalRead extends Instruction {
   private static final String ERROR_MESSAGE = "Unexpected attempt to emit debug-local read.";
@@ -51,6 +52,11 @@
   }
 
   @Override
+  public void buildLir(LirBuilder<Value, ?> builder) {
+    builder.addDebugLocalRead();
+  }
+
+  @Override
   public boolean identicalNonValueNonPositionParts(Instruction other) {
     return other.isDebugLocalRead();
   }
diff --git a/src/main/java/com/android/tools/r8/ir/code/DexItemBasedConstString.java b/src/main/java/com/android/tools/r8/ir/code/DexItemBasedConstString.java
index 3a5623d..51182ac 100644
--- a/src/main/java/com/android/tools/r8/ir/code/DexItemBasedConstString.java
+++ b/src/main/java/com/android/tools/r8/ir/code/DexItemBasedConstString.java
@@ -22,6 +22,7 @@
 import com.android.tools.r8.ir.optimize.DeadCodeRemover.DeadInstructionResult;
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
 import com.android.tools.r8.ir.optimize.InliningConstraints;
+import com.android.tools.r8.lightir.LirBuilder;
 import com.android.tools.r8.naming.dexitembasedstring.NameComputationInfo;
 
 public class DexItemBasedConstString extends ConstInstruction {
@@ -89,6 +90,11 @@
   }
 
   @Override
+  public void buildLir(LirBuilder<Value, ?> builder) {
+    builder.addDexItemBasedConstString(item, nameComputationInfo);
+  }
+
+  @Override
   public boolean identicalNonValueNonPositionParts(Instruction other) {
     return other.isDexItemBasedConstString()
         && other.asDexItemBasedConstString().item == item
diff --git a/src/main/java/com/android/tools/r8/ir/code/FieldInstruction.java b/src/main/java/com/android/tools/r8/ir/code/FieldInstruction.java
index 86ef0d7..5fa9853 100644
--- a/src/main/java/com/android/tools/r8/ir/code/FieldInstruction.java
+++ b/src/main/java/com/android/tools/r8/ir/code/FieldInstruction.java
@@ -167,7 +167,7 @@
    * since that could change the lifetime of the value.
    */
   boolean isStoringObjectWithFinalizer(
-      AppView<AppInfoWithLiveness> appView, DexEncodedField field) {
+      AppView<AppInfoWithLiveness> appView, DexClassAndField field) {
     assert isFieldPut();
 
     TypeElement type = value().getType();
diff --git a/src/main/java/com/android/tools/r8/ir/code/IRCodeInstructionListIterator.java b/src/main/java/com/android/tools/r8/ir/code/IRCodeInstructionListIterator.java
index 8b519ed..611d8dd 100644
--- a/src/main/java/com/android/tools/r8/ir/code/IRCodeInstructionListIterator.java
+++ b/src/main/java/com/android/tools/r8/ir/code/IRCodeInstructionListIterator.java
@@ -14,6 +14,7 @@
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.utils.InternalOptions;
+import java.util.Collection;
 import java.util.ListIterator;
 import java.util.NoSuchElementException;
 import java.util.Set;
@@ -227,6 +228,11 @@
   }
 
   @Override
+  public void set(Collection<Instruction> instructions) {
+    instructionIterator.set(instructions);
+  }
+
+  @Override
   public void replaceCurrentInstruction(Instruction newInstruction, Set<Value> affectedValues) {
     instructionIterator.replaceCurrentInstruction(newInstruction, affectedValues);
   }
diff --git a/src/main/java/com/android/tools/r8/ir/code/InitClass.java b/src/main/java/com/android/tools/r8/ir/code/InitClass.java
index 765d4a1..7d1b853 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InitClass.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InitClass.java
@@ -23,6 +23,7 @@
 import com.android.tools.r8.ir.conversion.DexBuilder;
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
 import com.android.tools.r8.ir.optimize.InliningConstraints;
+import com.android.tools.r8.lightir.LirBuilder;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 
 public class InitClass extends Instruction {
@@ -82,6 +83,11 @@
   }
 
   @Override
+  public void buildLir(LirBuilder<Value, ?> builder) {
+    builder.addInitClass(clazz);
+  }
+
+  @Override
   public boolean definitelyTriggersClassInitialization(
       DexType clazz,
       ProgramMethod context,
diff --git a/src/main/java/com/android/tools/r8/ir/code/InstancePut.java b/src/main/java/com/android/tools/r8/ir/code/InstancePut.java
index ad8a742..5a3984c 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InstancePut.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InstancePut.java
@@ -155,8 +155,8 @@
         return false;
       }
 
-      return appInfoWithLiveness.isFieldRead(field.getDefinition())
-          || isStoringObjectWithFinalizer(appViewWithLiveness, field.getDefinition());
+      return appInfoWithLiveness.isFieldRead(field)
+          || isStoringObjectWithFinalizer(appViewWithLiveness, field);
     }
 
     // In D8, we always have to assume that the field can be read, and thus have side effects.
diff --git a/src/main/java/com/android/tools/r8/ir/code/Instruction.java b/src/main/java/com/android/tools/r8/ir/code/Instruction.java
index c33be9f..511c157 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Instruction.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Instruction.java
@@ -225,6 +225,11 @@
 
   public abstract void buildCf(CfBuilder builder);
 
+  // TODO(b/225838009): Make this abstract.
+  public void buildLir(LirBuilder<Value, ?> builder) {
+    throw new Unimplemented("Missing impl for " + getClass().getSimpleName());
+  }
+
   public void replaceValue(Value oldValue, Value newValue) {
     for (int i = 0; i < inValues.size(); i++) {
       if (oldValue == inValues.get(i)) {
@@ -1535,10 +1540,6 @@
     return false;
   }
 
-  public void buildLir(LirBuilder<Value, ?> builder) {
-    throw new Unimplemented("Missing impl for " + getClass().getSimpleName());
-  }
-
   public void registerUse(UseRegistry registry, ProgramMethod context) {
     internalRegisterUse(registry, context);
   }
diff --git a/src/main/java/com/android/tools/r8/ir/code/InstructionListIterator.java b/src/main/java/com/android/tools/r8/ir/code/InstructionListIterator.java
index 8a69912..96db1b9 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InstructionListIterator.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InstructionListIterator.java
@@ -17,6 +17,7 @@
 import com.android.tools.r8.utils.ConsumerUtils;
 import com.android.tools.r8.utils.InternalOptions;
 import com.google.common.collect.Sets;
+import java.util.Collection;
 import java.util.ListIterator;
 import java.util.Set;
 import java.util.function.Consumer;
@@ -83,6 +84,8 @@
    */
   void removeOrReplaceByDebugLocalRead();
 
+  void set(Collection<Instruction> instructions);
+
   default boolean hasInsertionPosition() {
     return false;
   }
@@ -126,6 +129,10 @@
     return next();
   }
 
+  default Instruction positionBeforeNextInstruction(Instruction instruction) {
+    return positionBeforeNextInstructionThatMatches(i -> i == instruction);
+  }
+
   default Instruction positionBeforeNextInstructionThatMatches(Predicate<Instruction> predicate) {
     nextUntil(predicate);
     return previous();
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokeCustom.java b/src/main/java/com/android/tools/r8/ir/code/InvokeCustom.java
index 92ecb73..4539f17 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InvokeCustom.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InvokeCustom.java
@@ -25,6 +25,7 @@
 import com.android.tools.r8.ir.desugar.LambdaDescriptor;
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
 import com.android.tools.r8.ir.optimize.InliningConstraints;
+import com.android.tools.r8.lightir.LirBuilder;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import java.util.List;
 
@@ -157,6 +158,11 @@
   }
 
   @Override
+  public void buildLir(LirBuilder<Value, ?> builder) {
+    builder.addInvokeCustom(getCallSite(), arguments());
+  }
+
+  @Override
   public boolean identicalNonValueNonPositionParts(Instruction other) {
     return other.isInvokeCustom() && callSite == other.asInvokeCustom().callSite;
   }
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 b7a8fae..1871e96 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
@@ -37,6 +37,7 @@
 import com.android.tools.r8.utils.BooleanUtils;
 import com.android.tools.r8.utils.collections.ProgramMethodSet;
 import com.google.common.collect.ImmutableList;
+import java.util.Arrays;
 import java.util.BitSet;
 import java.util.Collections;
 import java.util.List;
@@ -282,6 +283,10 @@
     protected DexMethod method;
     protected List<Value> arguments = Collections.emptyList();
 
+    public B setArguments(Value... arguments) {
+      return setArguments(Arrays.asList(arguments));
+    }
+
     public B setArguments(List<Value> arguments) {
       assert arguments != null;
       this.arguments = arguments;
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokeMultiNewArray.java b/src/main/java/com/android/tools/r8/ir/code/InvokeMultiNewArray.java
index 47fc4f9..e492bca 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InvokeMultiNewArray.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InvokeMultiNewArray.java
@@ -21,6 +21,7 @@
 import com.android.tools.r8.ir.conversion.DexBuilder;
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
 import com.android.tools.r8.ir.optimize.InliningConstraints;
+import com.android.tools.r8.lightir.LirBuilder;
 import com.android.tools.r8.utils.LongInterval;
 import java.util.List;
 
@@ -115,6 +116,11 @@
   }
 
   @Override
+  public void buildLir(LirBuilder<Value, ?> builder) {
+    builder.addInvokeMultiNewArray(type, arguments());
+  }
+
+  @Override
   public boolean instructionInstanceCanThrow(AppView<?> appView, ProgramMethod context) {
     DexType baseType = type.isArrayType() ? type.toBaseType(appView.dexItemFactory()) : type;
     if (baseType.isPrimitiveType()) {
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokePolymorphic.java b/src/main/java/com/android/tools/r8/ir/code/InvokePolymorphic.java
index 08b3ab8..5e80e07ef 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InvokePolymorphic.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InvokePolymorphic.java
@@ -24,6 +24,7 @@
 import com.android.tools.r8.ir.optimize.Inliner.Reason;
 import com.android.tools.r8.ir.optimize.InliningConstraints;
 import com.android.tools.r8.ir.optimize.inliner.WhyAreYouNotInliningReporter;
+import com.android.tools.r8.lightir.LirBuilder;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.utils.collections.ProgramMethodSet;
 import java.util.List;
@@ -112,6 +113,11 @@
   }
 
   @Override
+  public void buildLir(LirBuilder<Value, ?> builder) {
+    builder.addInvokePolymorphic(getInvokedMethod(), getProto(), arguments());
+  }
+
+  @Override
   public boolean identicalNonValueNonPositionParts(Instruction other) {
     return other.isInvokePolymorphic()
         && proto.equals(other.asInvokePolymorphic().proto)
diff --git a/src/main/java/com/android/tools/r8/ir/code/LinearFlowInstructionListIterator.java b/src/main/java/com/android/tools/r8/ir/code/LinearFlowInstructionListIterator.java
index 819ba43..d4951ac 100644
--- a/src/main/java/com/android/tools/r8/ir/code/LinearFlowInstructionListIterator.java
+++ b/src/main/java/com/android/tools/r8/ir/code/LinearFlowInstructionListIterator.java
@@ -14,6 +14,7 @@
 import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.utils.InternalOptions;
 import com.google.common.collect.Sets;
+import java.util.Collection;
 import java.util.ListIterator;
 import java.util.Set;
 import java.util.function.Consumer;
@@ -292,4 +293,9 @@
   public void set(Instruction instruction) {
     currentBlockIterator.set(instruction);
   }
+
+  @Override
+  public void set(Collection<Instruction> instructions) {
+    currentBlockIterator.set(instructions);
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/code/Neg.java b/src/main/java/com/android/tools/r8/ir/code/Neg.java
index dde52df..7b7c683 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Neg.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Neg.java
@@ -17,6 +17,7 @@
 import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.conversion.CfBuilder;
 import com.android.tools.r8.ir.conversion.DexBuilder;
+import com.android.tools.r8.lightir.LirBuilder;
 import java.util.function.Function;
 
 public class Neg extends Unop {
@@ -111,4 +112,9 @@
   public void buildCf(CfBuilder builder) {
     builder.add(new CfNeg(type), this);
   }
+
+  @Override
+  public void buildLir(LirBuilder<Value, ?> builder) {
+    builder.addNeg(type, source());
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/code/NewUnboxedEnumInstance.java b/src/main/java/com/android/tools/r8/ir/code/NewUnboxedEnumInstance.java
index 123b988..d47c812 100644
--- a/src/main/java/com/android/tools/r8/ir/code/NewUnboxedEnumInstance.java
+++ b/src/main/java/com/android/tools/r8/ir/code/NewUnboxedEnumInstance.java
@@ -22,6 +22,7 @@
 import com.android.tools.r8.ir.optimize.InliningConstraints;
 import com.android.tools.r8.ir.optimize.enums.EnumUnboxer;
 import com.android.tools.r8.ir.optimize.enums.EnumUnboxerImpl;
+import com.android.tools.r8.lightir.LirBuilder;
 
 /**
  * Special instruction used by {@link EnumUnboxerImpl}.
@@ -166,4 +167,9 @@
   void internalRegisterUse(UseRegistry<?> registry, DexClassAndMethod context) {
     registry.registerNewUnboxedEnumInstance(clazz);
   }
+
+  @Override
+  public void buildLir(LirBuilder<Value, ?> builder) {
+    builder.addNewUnboxedEnumInstance(clazz, ordinal);
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/code/Not.java b/src/main/java/com/android/tools/r8/ir/code/Not.java
index 097ae22..3c6f4c7 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Not.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Not.java
@@ -15,6 +15,7 @@
 import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.conversion.CfBuilder;
 import com.android.tools.r8.ir.conversion.DexBuilder;
+import com.android.tools.r8.lightir.LirBuilder;
 import java.util.function.Function;
 
 public class Not extends Unop {
@@ -105,4 +106,9 @@
     // JVM has no Not instruction, they should be replaced by "Load -1, Xor" before building CF.
     throw new Unreachable();
   }
+
+  @Override
+  public void buildLir(LirBuilder<Value, ?> builder) {
+    builder.addNot(type, source());
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/code/StaticPut.java b/src/main/java/com/android/tools/r8/ir/code/StaticPut.java
index ed5958c..32b1cb2 100644
--- a/src/main/java/com/android/tools/r8/ir/code/StaticPut.java
+++ b/src/main/java/com/android/tools/r8/ir/code/StaticPut.java
@@ -140,8 +140,8 @@
         return false;
       }
 
-      return appInfoWithLiveness.isFieldRead(field.getDefinition())
-          || isStoringObjectWithFinalizer(appViewWithLiveness, field.getDefinition());
+      return appInfoWithLiveness.isFieldRead(field)
+          || isStoringObjectWithFinalizer(appViewWithLiveness, field);
     }
 
     // In D8, we always have to assume that the field can be read, and thus have side effects.
diff --git a/src/main/java/com/android/tools/r8/ir/code/Value.java b/src/main/java/com/android/tools/r8/ir/code/Value.java
index d7b86fe..fcec4f3 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Value.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Value.java
@@ -34,6 +34,7 @@
 import com.android.tools.r8.position.MethodPosition;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.utils.BooleanUtils;
+import com.android.tools.r8.utils.IterableUtils;
 import com.android.tools.r8.utils.LongInterval;
 import com.android.tools.r8.utils.Reporter;
 import com.android.tools.r8.utils.SetUtils;
@@ -357,6 +358,10 @@
     return uniqueUsers = ImmutableSet.copyOf(users);
   }
 
+  public <T extends Instruction> Iterable<T> uniqueUsers(Predicate<? super Instruction> predicate) {
+    return IterableUtils.filter(uniqueUsers(), predicate);
+  }
+
   public boolean hasSingleUniqueUser() {
     return uniqueUsers().size() == 1;
   }
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/FieldOptimizationFeedback.java b/src/main/java/com/android/tools/r8/ir/conversion/FieldOptimizationFeedback.java
index b5e3b1e..4eb2020 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/FieldOptimizationFeedback.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/FieldOptimizationFeedback.java
@@ -5,6 +5,7 @@
 package com.android.tools.r8.ir.conversion;
 
 import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexClassAndField;
 import com.android.tools.r8.graph.DexEncodedField;
 import com.android.tools.r8.ir.analysis.type.DynamicType;
 import com.android.tools.r8.ir.analysis.value.AbstractValue;
@@ -18,10 +19,19 @@
 
   void markFieldAsPropagated(DexEncodedField field);
 
+  default void markFieldHasDynamicType(DexClassAndField field, DynamicType dynamicType) {
+    markFieldHasDynamicType(field.getDefinition(), dynamicType);
+  }
+
   void markFieldHasDynamicType(DexEncodedField field, DynamicType dynamicType);
 
   void markFieldBitsRead(DexEncodedField field, int bitsRead);
 
+  default void recordFieldHasAbstractValue(
+      DexClassAndField field, AppView<AppInfoWithLiveness> appView, AbstractValue abstractValue) {
+    recordFieldHasAbstractValue(field.getDefinition(), appView, abstractValue);
+  }
+
   void recordFieldHasAbstractValue(
       DexEncodedField field, AppView<AppInfoWithLiveness> appView, AbstractValue abstractValue);
 }
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 a5417c6..431a610 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
@@ -8,6 +8,8 @@
 import com.android.tools.r8.graph.AppInfo;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.Code;
+import com.android.tools.r8.graph.DefaultInstanceInitializerCode;
+import com.android.tools.r8.graph.DexClassAndMethod;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexProgramClass;
@@ -24,6 +26,7 @@
 import com.android.tools.r8.ir.code.BasicBlock;
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.Value;
+import com.android.tools.r8.ir.conversion.passes.ParentConstructorHoistingCodeRewriter;
 import com.android.tools.r8.ir.desugar.CfInstructionDesugaringCollection;
 import com.android.tools.r8.ir.desugar.CovariantReturnTypeAnnotationTransformer;
 import com.android.tools.r8.ir.optimize.AssertionErrorTwoArgsConstructorRewriter;
@@ -349,14 +352,13 @@
     return onWaveDoneActions != null;
   }
 
-  protected void processSimpleSynthesizeMethods(
-      List<ProgramMethod> serviceLoadMethods, ExecutorService executorService)
-      throws ExecutionException {
+  public void processSimpleSynthesizeMethods(
+      List<ProgramMethod> methods, ExecutorService executorService) throws ExecutionException {
     ThreadUtils.processItems(
-        serviceLoadMethods, this::processAndFinalizeSimpleSynthesiedMethod, executorService);
+        methods, this::processAndFinalizeSimpleSynthesizedMethod, executorService);
   }
 
-  private void processAndFinalizeSimpleSynthesiedMethod(ProgramMethod method) {
+  private void processAndFinalizeSimpleSynthesizedMethod(ProgramMethod method) {
     IRCode code = method.buildIR(appView);
     assert code != null;
     codeRewriter.rewriteMoveResult(code);
@@ -894,6 +896,8 @@
 
     deadCodeRemover.run(code, timing);
 
+    new ParentConstructorHoistingCodeRewriter(appView).run(context, code, timing);
+
     BytecodeMetadataProvider.Builder bytecodeMetadataProviderBuilder =
         BytecodeMetadataProvider.builder();
     if (appView.enableWholeProgramOptimizations()) {
@@ -949,7 +953,19 @@
     }
     Code code = method.getDefinition().getCode();
     assert !code.isThrowNullCode();
-    return code.isDefaultInstanceInitializerCode();
+    if (code.isDefaultInstanceInitializerCode()) {
+      // Passthrough unless the parent constructor may be inlineable.
+      if (options.canInitNewInstanceUsingSuperclassConstructor()) {
+        DexMethod parentConstructorReference =
+            DefaultInstanceInitializerCode.getParentConstructor(method, appView.dexItemFactory());
+        DexClassAndMethod parentConstructor = appView.definitionFor(parentConstructorReference);
+        if (parentConstructor != null && parentConstructor.isProgramMethod()) {
+          return false;
+        }
+      }
+      return true;
+    }
+    return false;
   }
 
   // Compute optimization info summary for the current method unless it is pinned
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/PrimaryR8IRConverter.java b/src/main/java/com/android/tools/r8/ir/conversion/PrimaryR8IRConverter.java
index 7db097a..cda8f4e 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/PrimaryR8IRConverter.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/PrimaryR8IRConverter.java
@@ -245,7 +245,7 @@
     onWaveDoneActions = Collections.synchronizedList(new ArrayList<>());
   }
 
-  private void waveDone(ProgramMethodSet wave, ExecutorService executorService)
+  public void waveDone(ProgramMethodSet wave, ExecutorService executorService)
       throws ExecutionException {
     delayedOptimizationFeedback.refineAppInfoWithLiveness(appView.appInfo().withLiveness());
     delayedOptimizationFeedback.updateVisibleOptimizationInfo();
@@ -256,8 +256,10 @@
     }
     enumUnboxer.updateEnumUnboxingCandidatesInfo();
     assert delayedOptimizationFeedback.noUpdatesLeft();
-    onWaveDoneActions.forEach(com.android.tools.r8.utils.Action::execute);
-    onWaveDoneActions = null;
+    if (onWaveDoneActions != null) {
+      onWaveDoneActions.forEach(com.android.tools.r8.utils.Action::execute);
+      onWaveDoneActions = null;
+    }
     if (!prunedMethodsInWave.isEmpty()) {
       appView.pruneItems(
           PrunedItems.builder()
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/passes/CodeRewriterPass.java b/src/main/java/com/android/tools/r8/ir/conversion/passes/CodeRewriterPass.java
new file mode 100644
index 0000000..aea343b
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/conversion/passes/CodeRewriterPass.java
@@ -0,0 +1,47 @@
+// Copyright (c) 2023, 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.conversion.passes;
+
+import com.android.tools.r8.graph.AppInfo;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.ir.code.IRCode;
+import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.Timing;
+
+public abstract class CodeRewriterPass<T extends AppInfo> {
+
+  final AppView<?> appView;
+  final DexItemFactory dexItemFactory;
+  final InternalOptions options;
+
+  CodeRewriterPass(AppView<?> appView) {
+    this.appView = appView;
+    this.dexItemFactory = appView.dexItemFactory();
+    this.options = appView.options();
+  }
+
+  @SuppressWarnings("unchecked")
+  AppView<? extends T> appView() {
+    return (AppView<? extends T>) appView;
+  }
+
+  public final void run(ProgramMethod method, IRCode code, Timing timing) {
+    timing.time(getTimingId(), () -> run(method, code));
+  }
+
+  public final void run(ProgramMethod method, IRCode code) {
+    if (shouldRewriteCode(method, code)) {
+      rewriteCode(method, code);
+    }
+  }
+
+  abstract String getTimingId();
+
+  abstract void rewriteCode(ProgramMethod method, IRCode code);
+
+  abstract boolean shouldRewriteCode(ProgramMethod method, IRCode code);
+}
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/passes/ParentConstructorHoistingCodeRewriter.java b/src/main/java/com/android/tools/r8/ir/conversion/passes/ParentConstructorHoistingCodeRewriter.java
new file mode 100644
index 0000000..06e410b
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/conversion/passes/ParentConstructorHoistingCodeRewriter.java
@@ -0,0 +1,216 @@
+// Copyright (c) 2023, 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.conversion.passes;
+
+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.ProgramMethod;
+import com.android.tools.r8.ir.code.BasicBlock;
+import com.android.tools.r8.ir.code.IRCode;
+import com.android.tools.r8.ir.code.Instruction;
+import com.android.tools.r8.ir.code.InstructionListIterator;
+import com.android.tools.r8.ir.code.InvokeDirect;
+import com.android.tools.r8.ir.code.Value;
+import com.android.tools.r8.shaking.KeepMethodInfo;
+import com.android.tools.r8.utils.CollectionUtils;
+import com.android.tools.r8.utils.IterableUtils;
+import com.android.tools.r8.utils.IteratorUtils;
+import com.android.tools.r8.utils.ListUtils;
+import com.android.tools.r8.utils.TraversalContinuation;
+import com.android.tools.r8.utils.WorkList;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Iterables;
+import java.util.ArrayDeque;
+import java.util.Deque;
+import java.util.List;
+
+/**
+ * Pass that attempts to hoist the parent constructor call inside constructors to avoid
+ * instance-puts to the unininitialized this, as this enables more aggressive inlining of
+ * constructors.
+ */
+// TODO(b/278975138): Do not remove verification errors from hoisting.
+public class ParentConstructorHoistingCodeRewriter
+    extends CodeRewriterPass<AppInfoWithClassHierarchy> {
+
+  private List<InvokeDirect> sideEffectFreeConstructorCalls;
+
+  public ParentConstructorHoistingCodeRewriter(AppView<?> appView) {
+    super(appView);
+  }
+
+  @Override
+  String getTimingId() {
+    return "Parent constructor hoisting pass";
+  }
+
+  @Override
+  void rewriteCode(ProgramMethod method, IRCode code) {
+    for (InvokeDirect invoke : getOrComputeSideEffectFreeConstructorCalls(code)) {
+      hoistSideEffectFreeConstructorCall(code, invoke);
+    }
+  }
+
+  private void hoistSideEffectFreeConstructorCall(IRCode code, InvokeDirect invoke) {
+    Deque<Instruction> constants = new ArrayDeque<>();
+    // TODO(b/281975599): This loop would not be needed if we did not have any trivial gotos.
+    while (true) {
+      hoistSideEffectFreeConstructorCallInCurrentBlock(code, invoke, constants);
+      Instruction firstHoistedInstruction = CollectionUtils.getFirstOrDefault(constants, invoke);
+      if (invoke.getBlock().entry() != firstHoistedInstruction
+          || !hoistSideEffectFreeConstructorCallIntoPredecessorBlock(code, invoke, constants)) {
+        break;
+      }
+    }
+  }
+
+  // TODO(b/278975138): Instead of hoisting constructor call one instruction up at a time as a
+  //  peephole optimization, consider finding the insertion position and then modifying the IR once.
+  private void hoistSideEffectFreeConstructorCallInCurrentBlock(
+      IRCode code, InvokeDirect invoke, Deque<Instruction> constants) {
+    InstructionListIterator instructionIterator = invoke.getBlock().listIterator(code);
+    instructionIterator.positionBeforeNextInstruction(
+        CollectionUtils.getFirstOrDefault(constants, invoke));
+    while (instructionIterator.hasPrevious()) {
+      Instruction previousInstruction = instructionIterator.previous();
+      if (previousInstruction.isArgument()) {
+        // Cannot hoist the constructor call above the Argument instruction.
+        return;
+      }
+      if (previousInstruction.hasOutValue()
+          && invoke.inValues().contains(previousInstruction.outValue())) {
+        if (previousInstruction.isConstNumber() || previousInstruction.isConstString()) {
+          // Record that the constant instruction should be hoisted along with the constructor call.
+          constants.addFirst(previousInstruction);
+          continue;
+        }
+        // Cannot hoist the constructor call above the definition of a value that is used as an
+        // argument to the constructor call.
+        return;
+      }
+      // Change the instruction order and continue the hoisting.
+      List<Instruction> newInstructionOrder =
+          ImmutableList.<Instruction>builderWithExpectedSize(constants.size() + 2)
+              .addAll(constants)
+              .add(invoke)
+              .add(previousInstruction)
+              .build();
+      instructionIterator.next();
+      instructionIterator.set(newInstructionOrder);
+      IteratorUtils.skip(instructionIterator, -newInstructionOrder.size() - 1);
+    }
+  }
+
+  private boolean hoistSideEffectFreeConstructorCallIntoPredecessorBlock(
+      IRCode code, InvokeDirect invoke, Deque<Instruction> constants) {
+    BasicBlock block = invoke.getBlock();
+    if (!block.hasUniquePredecessor()) {
+      return false;
+    }
+
+    BasicBlock predecessorBlock = block.getUniquePredecessor();
+    if (!predecessorBlock.hasUniqueSuccessor() || predecessorBlock.hasCatchHandlers()) {
+      return false;
+    }
+
+    // Remove the constants and the invoke from the block.
+    constants.forEach(constant -> block.getInstructions().removeFirst());
+    block.getInstructions().removeFirst();
+
+    // Add the constants and the invoke before the exit instruction in the predecessor block.
+    InstructionListIterator predecessorInstructionIterator =
+        predecessorBlock.listIterator(code, predecessorBlock.getInstructions().size() - 1);
+    constants.forEach(predecessorInstructionIterator::add);
+    predecessorInstructionIterator.add(invoke);
+
+    // Position the predecessor instruction iterator right before the first instruction that is
+    // subject to hoisting.
+    IteratorUtils.skip(predecessorInstructionIterator, -constants.size() - 1);
+    assert predecessorInstructionIterator.peekNext()
+        == CollectionUtils.getFirstOrDefault(constants, invoke);
+    return true;
+  }
+
+  /** Only run this when the rewriting may actually enable more constructor inlining. */
+  @Override
+  boolean shouldRewriteCode(ProgramMethod method, IRCode code) {
+    if (!appView.hasClassHierarchy()) {
+      assert !appView.enableWholeProgramOptimizations();
+      return false;
+    }
+    if (!method.getDefinition().isInstanceInitializer()
+        || !options.canInitNewInstanceUsingSuperclassConstructor()) {
+      return false;
+    }
+    KeepMethodInfo keepInfo = appView.getKeepInfo(method);
+    return keepInfo.isOptimizationAllowed(options)
+        && keepInfo.isShrinkingAllowed(options)
+        && hoistingMayRemoveInstancePutToUninitializedThis(code);
+  }
+
+  private boolean hoistingMayRemoveInstancePutToUninitializedThis(IRCode code) {
+    if (!code.metadata().mayHaveInstancePut()) {
+      return false;
+    }
+    Value thisValue = code.getThis();
+    WorkList<BasicBlock> worklist = WorkList.newIdentityWorkList();
+    for (InvokeDirect invoke : getOrComputeSideEffectFreeConstructorCalls(code)) {
+      // Check if any of the previous instructions in the current block has an instance-put that
+      // assigns a field on `this`.
+      if (IterableUtils.anyBefore(
+          invoke.getBlock().getInstructions(),
+          instruction -> isInstancePutToUninitializedThis(instruction, thisValue),
+          instruction -> instruction == invoke)) {
+        return true;
+      }
+      // Otherwise check if any of the (transitive) predecessor blocks has an instance-put that
+      // assigns a field on `this`.
+      worklist.addIfNotSeen(invoke.getBlock().getPredecessors());
+    }
+    return worklist
+        .run(
+            block -> {
+              if (Iterables.any(
+                  block.getInstructions(),
+                  instruction -> isInstancePutToUninitializedThis(instruction, thisValue))) {
+                return TraversalContinuation.doBreak();
+              }
+              worklist.addIfNotSeen(block.getPredecessors());
+              return TraversalContinuation.doContinue();
+            })
+        .shouldBreak();
+  }
+
+  private List<InvokeDirect> getOrComputeSideEffectFreeConstructorCalls(IRCode code) {
+    if (sideEffectFreeConstructorCalls == null) {
+      sideEffectFreeConstructorCalls = computeSideEffectFreeConstructorCalls(code);
+    }
+    return sideEffectFreeConstructorCalls;
+  }
+
+  private List<InvokeDirect> computeSideEffectFreeConstructorCalls(IRCode code) {
+    Value thisValue = code.getThis();
+    return ListUtils.filter(
+        thisValue.uniqueUsers(),
+        instruction -> {
+          if (!instruction.isInvokeConstructor(dexItemFactory)) {
+            return false;
+          }
+          InvokeDirect invoke = instruction.asInvokeDirect();
+          if (invoke.getReceiver() != thisValue) {
+            return false;
+          }
+          DexClassAndMethod target = invoke.lookupSingleTarget(appView, code.context());
+          return target != null
+              && !target.getOptimizationInfo().mayHaveSideEffects(invoke, options);
+        });
+  }
+
+  private static boolean isInstancePutToUninitializedThis(
+      Instruction instruction, Value thisValue) {
+    return instruction.isInstancePut() && instruction.asInstancePut().object() == thisValue;
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/retargeter/DesugaredLibraryRetargeterPostProcessor.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/retargeter/DesugaredLibraryRetargeterPostProcessor.java
index 98b707f..be8dd14 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/retargeter/DesugaredLibraryRetargeterPostProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/retargeter/DesugaredLibraryRetargeterPostProcessor.java
@@ -128,7 +128,8 @@
         continue;
       }
       clazz.addExtraInterfaces(
-          Collections.singletonList(new ClassTypeSignature(newInterface.type)));
+          Collections.singletonList(new ClassTypeSignature(newInterface.type)),
+          appView.dexItemFactory());
       eventConsumer.acceptInterfaceInjection(clazz, newInterface);
       DexMethod itfMethod =
           syntheticHelper.emulatedInterfaceDispatchMethod(newInterface, descriptor);
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 922fd4d..f69890a 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
@@ -529,7 +529,7 @@
               eventConsumer.acceptEmulatedInterfaceMarkerInterface(
                   clazz, helper.ensureEmulatedInterfaceMarkerInterface(signature.type()));
             }
-            clazz.addExtraInterfaces(extraInterfaceSignatures);
+            clazz.addExtraInterfaces(extraInterfaceSignatures, appView.dexItemFactory());
           }
         });
   }
@@ -698,6 +698,7 @@
     // TODO(b/182329331): Only handle type arguments for Cf to Cf desugar.
     if (appView.options().isCfDesugaring() && clazz.validInterfaceSignatures()) {
       clazz.forEachImmediateSupertypeWithSignature(
+          appView.dexItemFactory(),
           (type, signature) -> {
             if (emulatesInterfaces.contains(type)) {
               extraInterfaceSignatures.put(
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/itf/EmulatedInterfaceApplicationRewriter.java b/src/main/java/com/android/tools/r8/ir/desugar/itf/EmulatedInterfaceApplicationRewriter.java
index 47a6d30..d33fcec 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/itf/EmulatedInterfaceApplicationRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/itf/EmulatedInterfaceApplicationRewriter.java
@@ -90,7 +90,7 @@
             false,
             emulatedInterface.getChecksumSupplier());
     newEmulatedInterface.addExtraInterfaces(
-        getRewrittenInterfacesOfEmulatedInterface(emulatedInterface));
+        getRewrittenInterfacesOfEmulatedInterface(emulatedInterface), appView.dexItemFactory());
     return newEmulatedInterface;
   }
 
@@ -106,7 +106,7 @@
           typeArguments = Collections.emptyList();
         } else {
           GenericSignature.ClassTypeSignature classTypeSignature =
-              classSignature.superInterfaceSignatures().get(i);
+              classSignature.getSuperInterfaceSignatures().get(i);
           assert itf == classTypeSignature.type();
           typeArguments = classTypeSignature.typeArguments();
         }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/records/RecordDesugaring.java b/src/main/java/com/android/tools/r8/ir/desugar/records/RecordDesugaring.java
index 4e982d8..5cd5230 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/records/RecordDesugaring.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/records/RecordDesugaring.java
@@ -442,16 +442,23 @@
       Collection<? extends ProgramDefinition> contexts) {
     DexItemFactory factory = appView.dexItemFactory();
     checkRecordTagNotPresent(factory);
+    return ensureRecordClassHelper(appView, contexts, eventConsumer);
+  }
+
+  public static DexProgramClass ensureRecordClassHelper(
+      AppView<?> appView,
+      Collection<? extends ProgramDefinition> contexts,
+      RecordDesugaringEventConsumer eventConsumer) {
     return appView
         .getSyntheticItems()
         .ensureGlobalClass(
             () -> new MissingGlobalSyntheticsConsumerDiagnostic("Record desugaring"),
             kinds -> kinds.RECORD_TAG,
-            factory.recordType,
+            appView.dexItemFactory().recordType,
             contexts,
             appView,
             builder -> {
-              DexEncodedMethod init = synthesizeRecordInitMethod();
+              DexEncodedMethod init = synthesizeRecordInitMethod(appView);
               builder.setAbstract().setDirectMethods(ImmutableList.of(init));
             },
             eventConsumer::acceptRecordClass);
@@ -532,14 +539,16 @@
     return factory.objectMembers.toString;
   }
 
-  private DexEncodedMethod synthesizeRecordInitMethod() {
+  private static DexEncodedMethod synthesizeRecordInitMethod(AppView<?> appView) {
     MethodAccessFlags methodAccessFlags =
         MethodAccessFlags.fromSharedAccessFlags(
             Constants.ACC_SYNTHETIC | Constants.ACC_PROTECTED, true);
     return DexEncodedMethod.syntheticBuilder()
-        .setMethod(factory.recordMembers.constructor)
+        .setMethod(appView.dexItemFactory().recordMembers.constructor)
         .setAccessFlags(methodAccessFlags)
-        .setCode(new CallObjectInitCfCodeProvider(appView, factory.recordTagType).generateCfCode())
+        .setCode(
+            new CallObjectInitCfCodeProvider(appView, appView.dexItemFactory().recordTagType)
+                .generateCfCode())
         // Will be traced by the enqueuer.
         .disableAndroidApiLevelCheck()
         .build();
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/varhandle/VarHandleDesugaring.java b/src/main/java/com/android/tools/r8/ir/desugar/varhandle/VarHandleDesugaring.java
index 063286c..c267943 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/varhandle/VarHandleDesugaring.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/varhandle/VarHandleDesugaring.java
@@ -183,17 +183,21 @@
     return refersToMethodHandlesLookup(field.type, factory);
   }
 
-  private void ensureMethodHandlesLookupClass(
+  @SuppressWarnings("InconsistentOverloads")
+  public static void ensureMethodHandlesLookupClass(
+      AppView<?> appView,
       VarHandleDesugaringEventConsumer eventConsumer,
       Collection<? extends ProgramDefinition> contexts) {
-    assert contexts.stream().allMatch(context -> context.getContextType() != factory.lookupType);
+    assert contexts.stream()
+        .allMatch(context -> context.getContextType() != appView.dexItemFactory().lookupType);
     DexProgramClass clazz =
         appView
             .getSyntheticItems()
             .ensureGlobalClass(
-                () -> new MissingGlobalSyntheticsConsumerDiagnostic("VarHandle desugaring"),
+                () ->
+                    new MissingGlobalSyntheticsConsumerDiagnostic("MethodHandlesLookup desugaring"),
                 kinds -> kinds.METHOD_HANDLES_LOOKUP,
-                factory.lookupType,
+                appView.dexItemFactory().lookupType,
                 contexts,
                 appView,
                 builder ->
@@ -207,20 +211,23 @@
 
   private void ensureMethodHandlesLookupClass(
       VarHandleDesugaringEventConsumer eventConsumer, ProgramDefinition context) {
-    ensureMethodHandlesLookupClass(eventConsumer, ImmutableList.of(context));
+    ensureMethodHandlesLookupClass(appView, eventConsumer, ImmutableList.of(context));
   }
 
-  private void ensureVarHandleClass(
+  @SuppressWarnings("InconsistentOverloads")
+  public static void ensureVarHandleClass(
+      AppView<?> appView,
       VarHandleDesugaringEventConsumer eventConsumer,
       Collection<? extends ProgramDefinition> contexts) {
-    assert contexts.stream().allMatch(context -> context.getContextType() != factory.varHandleType);
+    assert contexts.stream()
+        .allMatch(context -> context.getContextType() != appView.dexItemFactory().varHandleType);
     DexProgramClass clazz =
         appView
             .getSyntheticItems()
             .ensureGlobalClass(
                 () -> new MissingGlobalSyntheticsConsumerDiagnostic("VarHandle desugaring"),
                 kinds -> kinds.VAR_HANDLE,
-                factory.varHandleType,
+                appView.dexItemFactory().varHandleType,
                 contexts,
                 appView,
                 builder ->
@@ -235,7 +242,7 @@
   private void ensureVarHandleClass(
       VarHandleDesugaringEventConsumer eventConsumer, ProgramDefinition context) {
     if (context.getContextType() != factory.varHandleType) {
-      ensureVarHandleClass(eventConsumer, ImmutableList.of(context));
+      ensureVarHandleClass(appView, eventConsumer, ImmutableList.of(context));
     }
   }
 
@@ -590,12 +597,12 @@
         flags,
         DexApplicationReadFlags::hasReadMethodHandlesLookupReferenceFromProgramClass,
         DexApplicationReadFlags::getMethodHandlesLookupWitnesses,
-        classes -> ensureMethodHandlesLookupClass(eventConsumer, classes));
+        classes -> ensureMethodHandlesLookupClass(appView, eventConsumer, classes));
     synthesizeClassIfReferenced(
         flags,
         DexApplicationReadFlags::hasReadVarHandleReferenceFromProgramClass,
         DexApplicationReadFlags::getVarHandleWitnesses,
-        classes -> ensureVarHandleClass(eventConsumer, classes));
+        classes -> ensureVarHandleClass(appView, eventConsumer, classes));
   }
 
   private void synthesizeClassIfReferenced(
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/ClassInitializerDefaultsOptimization.java b/src/main/java/com/android/tools/r8/ir/optimize/ClassInitializerDefaultsOptimization.java
index 66ef64e..8d2b4bb 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/ClassInitializerDefaultsOptimization.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/ClassInitializerDefaultsOptimization.java
@@ -11,6 +11,7 @@
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexClassAndField;
 import com.android.tools.r8.graph.DexEncodedField;
 import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.graph.DexItemFactory;
@@ -82,21 +83,28 @@
       }
     }
 
-    public boolean hasStaticValue(DexEncodedField field) {
-      if (field.isStatic()) {
-        return (fieldsWithStaticValues != null && fieldsWithStaticValues.containsKey(field))
-            || field.getStaticValue() != null;
+    public void forEachOptimizedField(
+        BiConsumer<DexClassAndField, DexValue> consumer, AppView<?> appView) {
+      forEachOptimizedField((field, value) -> consumer.accept(field.asClassField(appView), value));
+    }
+
+    public boolean hasStaticValue(DexClassAndField field) {
+      if (field.getAccessFlags().isStatic()) {
+        return (fieldsWithStaticValues != null
+                && fieldsWithStaticValues.containsKey(field.getDefinition()))
+            || field.getDefinition().getStaticValue() != null;
       }
       return false;
     }
 
-    public DexValue getStaticValue(DexEncodedField field) {
+    public DexValue getStaticValue(DexClassAndField field) {
       assert hasStaticValue(field);
-      assert field.isStatic();
-      if (fieldsWithStaticValues != null && fieldsWithStaticValues.containsKey(field)) {
-        return fieldsWithStaticValues.get(field);
+      assert field.getAccessFlags().isStatic();
+      if (fieldsWithStaticValues != null
+          && fieldsWithStaticValues.containsKey(field.getDefinition())) {
+        return fieldsWithStaticValues.get(field.getDefinition());
       }
-      return field.getStaticValue();
+      return field.getDefinition().getStaticValue();
     }
   }
 
@@ -251,7 +259,7 @@
                 .filter(unnecessaryStaticPuts::contains)
                 .map(FieldInstruction::getField)
                 .map(appInfoWithLiveness::resolveField)
-                .map(FieldResolutionResult::getResolvedField)
+                .map(FieldResolutionResult::getResolutionPair)
                 .filter(appInfoWithLiveness::isStaticFieldWrittenOnlyInEnclosingStaticInitializer)
                 .map(field -> field.getReference())
                 .collect(Collectors.toSet());
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/ConstantCanonicalizer.java b/src/main/java/com/android/tools/r8/ir/optimize/ConstantCanonicalizer.java
index eb7671a..677da9c 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/ConstantCanonicalizer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/ConstantCanonicalizer.java
@@ -152,7 +152,7 @@
   public ConstantCanonicalizer canonicalize() {
     Object2ObjectLinkedOpenCustomHashMap<Instruction, List<Instruction>> valuesDefinedByConstant =
         new Object2ObjectLinkedOpenCustomHashMap<>(
-            new Strategy<Instruction>() {
+            new Strategy<>() {
 
               @Override
               public int hashCode(Instruction candidate) {
@@ -428,7 +428,7 @@
               return false;
             }
           }
-          if (!isReadOfFinalFieldOutsideInitializer(instanceGet)) {
+          if (!isReadOfEffectivelyFinalFieldOutsideInitializer(instanceGet)) {
             return false;
           }
           if (getOrComputeIneligibleInstanceGetInstructions().contains(instanceGet)) {
@@ -444,7 +444,7 @@
           if (staticGet.instructionMayHaveSideEffects(appView, context)) {
             return false;
           }
-          if (!isReadOfFinalFieldOutsideInitializer(staticGet)
+          if (!isReadOfEffectivelyFinalFieldOutsideInitializer(staticGet)
               && !isEffectivelyFinalField(staticGet)) {
             return false;
           }
@@ -475,7 +475,7 @@
     return true;
   }
 
-  private boolean isReadOfFinalFieldOutsideInitializer(FieldGet fieldGet) {
+  private boolean isReadOfEffectivelyFinalFieldOutsideInitializer(FieldGet fieldGet) {
     if (getOrComputeIsAccessingVolatileField()) {
       // A final field may be initialized concurrently. A requirement for this is that the field is
       // volatile. However, the reading or writing of another volatile field also allows for
@@ -500,9 +500,7 @@
     ProgramField resolvedField = resolutionResult.getSingleProgramField();
     FieldAccessFlags accessFlags = resolvedField.getAccessFlags();
     assert !accessFlags.isVolatile();
-    // TODO(b/236661949): Add support for effectively final fields so that this also works well
-    //  without -allowaccessmodification.
-    if (!accessFlags.isFinal()) {
+    if (!resolvedField.isFinalOrEffectivelyFinal(appViewWithClassHierarchy)) {
       return false;
     }
     if (appView.getKeepInfo(resolvedField).isPinned(appView.options())) {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/DefaultInliningOracle.java b/src/main/java/com/android/tools/r8/ir/optimize/DefaultInliningOracle.java
index 05c83da..79ba9d1 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/DefaultInliningOracle.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/DefaultInliningOracle.java
@@ -478,6 +478,10 @@
       ProgramMethod singleTarget,
       InliningIRProvider inliningIRProvider,
       WhyAreYouNotInliningReporter whyAreYouNotInliningReporter) {
+    if (!inlinerOptions.isConstructorInliningEnabled()) {
+      return false;
+    }
+
     IRCode inlinee = inliningIRProvider.getInliningIR(invoke, singleTarget);
 
     // In the Java VM Specification section "4.10.2.4. Instance Initialization Methods and
@@ -517,32 +521,30 @@
     //       ...
     //     }
     //   }
-    // TODO(b/278679664): Relax requirement (3) when targeting DEX.
     Value thisValue = inlinee.entryBlock().entry().asArgument().outValue();
 
     List<InvokeDirect> initCallsOnThis = new ArrayList<>();
     for (Instruction instruction : inlinee.instructions()) {
-      if (instruction.isInvokeDirect()) {
+      if (instruction.isInvokeConstructor(appView.dexItemFactory())) {
         InvokeDirect initCall = instruction.asInvokeDirect();
-        DexMethod invokedMethod = initCall.getInvokedMethod();
-        if (appView.dexItemFactory().isConstructor(invokedMethod)) {
-          Value receiver = initCall.getReceiver().getAliasedValue();
-          if (receiver == thisValue) {
-            // The <init>() call of the constructor must be on the same class.
-            if (calleeMethodHolder != invokedMethod.holder) {
-              whyAreYouNotInliningReporter
-                  .reportUnsafeConstructorInliningDueToIndirectConstructorCall(initCall);
-              return false;
-            }
-            initCallsOnThis.add(initCall);
+        Value receiver = initCall.getReceiver().getAliasedValue();
+        if (receiver == thisValue) {
+          // The <init>() call of the constructor must be on the same class when targeting the JVM
+          // and Dalvik.
+          if (!options.canInitNewInstanceUsingSuperclassConstructor()
+              && calleeMethodHolder != initCall.getInvokedMethod().getHolderType()) {
+            whyAreYouNotInliningReporter
+                .reportUnsafeConstructorInliningDueToIndirectConstructorCall(initCall);
+            return false;
           }
+          initCallsOnThis.add(initCall);
         }
       } else if (instruction.isInstancePut()) {
         // Final fields may not be initialized outside of a constructor in the enclosing class.
         InstancePut instancePut = instruction.asInstancePut();
         DexField field = instancePut.getField();
         DexEncodedField target = appView.appInfo().lookupInstanceTarget(field);
-        if (target == null || target.accessFlags.isFinal()) {
+        if (target == null || target.isFinal()) {
           whyAreYouNotInliningReporter.reportUnsafeConstructorInliningDueToFinalFieldAssignment(
               instancePut);
           return false;
@@ -550,29 +552,22 @@
       }
     }
 
-    // Check that there are no uses of the uninitialized object before it gets initialized.
-    int markingColor = inlinee.reserveMarkingColor();
-    for (InvokeDirect initCallOnThis : initCallsOnThis) {
-      BasicBlock block = initCallOnThis.getBlock();
-      for (Instruction instruction : block.instructionsBefore(initCallOnThis)) {
-        for (Value inValue : instruction.inValues()) {
-          Value root = inValue.getAliasedValue();
-          if (root == thisValue) {
-            inlinee.returnMarkingColor(markingColor);
-            whyAreYouNotInliningReporter.reportUnsafeConstructorInliningDueToUninitializedObjectUse(
-                instruction);
-            return false;
-          }
+    if (initCallsOnThis.isEmpty()) {
+      // In the unusual case where there is no parent/forwarding constructor call, there must be no
+      // instance-put instructions that assign fields on the receiver.
+      for (Instruction user : thisValue.uniqueUsers()) {
+        if (user.isInstancePut() && user.asInstancePut().object().getAliasedValue() == thisValue) {
+          whyAreYouNotInliningReporter.reportUnsafeConstructorInliningDueToUninitializedObjectUse(
+              user);
+          return false;
         }
       }
-      for (BasicBlock predecessor : block.getPredecessors()) {
-        inlinee.markTransitivePredecessors(predecessor, markingColor);
-      }
-    }
-
-    for (BasicBlock block : inlinee.blocks) {
-      if (block.isMarked(markingColor)) {
-        for (Instruction instruction : block.getInstructions()) {
+    } else {
+      // Check that there are no uses of the uninitialized object before it gets initialized.
+      int markingColor = inlinee.reserveMarkingColor();
+      for (InvokeDirect initCallOnThis : initCallsOnThis) {
+        BasicBlock block = initCallOnThis.getBlock();
+        for (Instruction instruction : block.instructionsBefore(initCallOnThis)) {
           for (Value inValue : instruction.inValues()) {
             Value root = inValue.getAliasedValue();
             if (root == thisValue) {
@@ -583,10 +578,29 @@
             }
           }
         }
+        for (BasicBlock predecessor : block.getPredecessors()) {
+          inlinee.markTransitivePredecessors(predecessor, markingColor);
+        }
       }
+
+      for (BasicBlock block : inlinee.getBlocks()) {
+        if (block.isMarked(markingColor)) {
+          for (Instruction instruction : block.getInstructions()) {
+            for (Value inValue : instruction.inValues()) {
+              Value root = inValue.getAliasedValue();
+              if (root == thisValue) {
+                inlinee.returnMarkingColor(markingColor);
+                whyAreYouNotInliningReporter
+                    .reportUnsafeConstructorInliningDueToUninitializedObjectUse(instruction);
+                return false;
+              }
+            }
+          }
+        }
+      }
+      inlinee.returnMarkingColor(markingColor);
     }
 
-    inlinee.returnMarkingColor(markingColor);
     inliningIRProvider.cacheInliningIR(invoke, inlinee);
     return true;
   }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/RedundantFieldLoadAndStoreElimination.java b/src/main/java/com/android/tools/r8/ir/optimize/RedundantFieldLoadAndStoreElimination.java
index 178c021..06a8cc2 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/RedundantFieldLoadAndStoreElimination.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/RedundantFieldLoadAndStoreElimination.java
@@ -514,8 +514,9 @@
                 invoke.getArgument(info.asArgumentInitializationInfo().getArgumentIndex());
             Value object = invoke.getReceiver().getAliasedValue();
             FieldAndObject fieldAndObject = new FieldAndObject(field.getReference(), object);
-            if (field.isFinal()) {
-              activeState.putFinalInstanceField(fieldAndObject, new ExistingValue(value));
+            if (field.isFinalOrEffectivelyFinal(appViewWithLiveness)) {
+              activeState.putFinalOrEffectivelyFinalInstanceField(
+                  fieldAndObject, new ExistingValue(value));
             } else {
               activeState.putNonFinalInstanceField(fieldAndObject, new ExistingValue(value));
             }
@@ -524,8 +525,9 @@
             if (value.isMaterializableInContext(appViewWithLiveness, method)) {
               Value object = invoke.getReceiver().getAliasedValue();
               FieldAndObject fieldAndObject = new FieldAndObject(field.getReference(), object);
-              if (field.isFinal()) {
-                activeState.putFinalInstanceField(fieldAndObject, new MaterializableValue(value));
+              if (field.isFinalOrEffectivelyFinal(appViewWithLiveness)) {
+                activeState.putFinalOrEffectivelyFinalInstanceField(
+                    fieldAndObject, new MaterializableValue(value));
               } else {
                 activeState.putNonFinalInstanceField(
                     fieldAndObject, new MaterializableValue(value));
@@ -648,8 +650,10 @@
     FieldAndObject fieldAndObject = new FieldAndObject(field.getReference(), object);
     FieldValue replacement = activeState.getInstanceFieldValue(fieldAndObject);
     if (replacement != null) {
-      markAssumeDynamicTypeUsersForRemoval(instanceGet, replacement, assumeRemover);
-      replacement.eliminateRedundantRead(it, instanceGet);
+      if (isRedundantFieldLoadEliminationAllowed(field)) {
+        markAssumeDynamicTypeUsersForRemoval(instanceGet, replacement, assumeRemover);
+        replacement.eliminateRedundantRead(it, instanceGet);
+      }
       return;
     }
 
@@ -658,6 +662,15 @@
     clearMostRecentInstanceFieldWrite(instanceGet, field);
   }
 
+  private boolean isRedundantFieldLoadEliminationAllowed(DexClassAndField field) {
+    // Always allowed in D8 since D8 does not support @NoRedundantFieldLoadElimination.
+    return !appView.enableWholeProgramOptimizations()
+        || !field.isProgramField()
+        || appView
+            .getKeepInfo(field.asProgramField())
+            .isRedundantFieldLoadEliminationAllowed(appView.options());
+  }
+
   private void handleNewInstance(NewInstance newInstance) {
     markClassAsInitialized(newInstance.getType());
     markMostRecentInitClassForRemoval(newInstance.getType());
@@ -706,24 +719,24 @@
     Value object = instancePut.object().getAliasedValue();
     FieldAndObject fieldAndObject = new FieldAndObject(field.getReference(), object);
     ExistingValue value = new ExistingValue(instancePut.value());
-    if (isFinal(field)) {
+    if (field.isFinalOrEffectivelyFinal(appView)) {
       assert !field.getDefinition().isFinal()
           || method.getDefinition().isInstanceInitializer()
           || verifyWasInstanceInitializer();
-      activeState.putFinalInstanceField(fieldAndObject, value);
+      activeState.putFinalOrEffectivelyFinalInstanceField(fieldAndObject, value);
     } else {
       activeState.putNonFinalInstanceField(fieldAndObject, value);
+    }
 
-      // Record that this field is now most recently written by the current instruction.
-      if (release) {
-        InstancePut mostRecentInstanceFieldWrite =
-            activeState.putMostRecentInstanceFieldWrite(fieldAndObject, instancePut);
-        if (mostRecentInstanceFieldWrite != null) {
-          instructionsToRemove
-              .computeIfAbsent(
-                  mostRecentInstanceFieldWrite.getBlock(), ignoreKey(Sets::newIdentityHashSet))
-              .add(mostRecentInstanceFieldWrite);
-        }
+    // Record that this field is now most recently written by the current instruction.
+    if (release) {
+      InstancePut mostRecentInstanceFieldWrite =
+          activeState.putMostRecentInstanceFieldWrite(fieldAndObject, instancePut);
+      if (mostRecentInstanceFieldWrite != null) {
+        instructionsToRemove
+            .computeIfAbsent(
+                mostRecentInstanceFieldWrite.getBlock(), ignoreKey(Sets::newIdentityHashSet))
+            .add(mostRecentInstanceFieldWrite);
       }
     }
 
@@ -755,7 +768,7 @@
     clearMostRecentStaticFieldWrite(staticGet, field);
 
     FieldValue value = new ExistingValue(staticGet.value());
-    if (isFinal(field)) {
+    if (field.isFinalOrEffectivelyFinal(appView)) {
       activeState.putFinalStaticField(field.getReference(), value);
     } else {
       activeState.putNonFinalStaticField(field.getReference(), value);
@@ -796,7 +809,7 @@
     }
 
     ExistingValue value = new ExistingValue(staticPut.value());
-    if (isFinal(field)) {
+    if (field.isFinalOrEffectivelyFinal(appView)) {
       assert appView.checkForTesting(
           () -> !field.getDefinition().isFinal() || method.getDefinition().isClassInitializer());
       activeState.putFinalStaticField(field.getReference(), value);
@@ -827,7 +840,7 @@
               && fieldValue.isSingleValue()) {
             SingleValue singleFieldValue = fieldValue.asSingleValue();
             if (singleFieldValue.isMaterializableInContext(appViewWithLiveness, method)) {
-              activeState.putFinalInstanceField(
+              activeState.putFinalOrEffectivelyFinalInstanceField(
                   new FieldAndObject(field, value), new MaterializableValue(singleFieldValue));
             }
           }
@@ -1325,7 +1338,7 @@
       arraySlotValues.put(arraySlot, value);
     }
 
-    public void putFinalInstanceField(FieldAndObject field, FieldValue value) {
+    public void putFinalOrEffectivelyFinalInstanceField(FieldAndObject field, FieldValue value) {
       ensureCapacityForNewElement();
       if (finalInstanceFieldValues == null) {
         finalInstanceFieldValues = new LinkedHashMap<>();
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java
index 72a2169..69d5822 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java
@@ -883,12 +883,17 @@
       }
     }
 
-    // Must be a constructor of the exact same class.
+    // Must be a constructor of the exact same class except when targeting ART, where constructors
+    // in the superclass hierarchy are also allowed.
     DexMethod init = invoke.getInvokedMethod();
-    if (init.holder != eligibleClass.type) {
-      // Calling a constructor on a class that is different from the type of the instance.
-      // Gracefully abort class inlining (see the test B116282409).
-      return null;
+    if (appView.options().canInitNewInstanceUsingSuperclassConstructor()) {
+      if (!appView.appInfo().isSubtype(eligibleClass.getType(), init.getHolderType())) {
+        return null;
+      }
+    } else {
+      if (eligibleClass.getType() != init.getHolderType()) {
+        return null;
+      }
     }
 
     // Check that the `eligibleInstance` does not escape via the constructor.
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxerImpl.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxerImpl.java
index 4754658..4838202 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxerImpl.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxerImpl.java
@@ -107,6 +107,7 @@
 import com.android.tools.r8.shaking.KeepInfoCollection;
 import com.android.tools.r8.utils.Reporter;
 import com.android.tools.r8.utils.StringDiagnostic;
+import com.android.tools.r8.utils.TraversalContinuation;
 import com.android.tools.r8.utils.collections.ImmutableInt2ReferenceSortedMap;
 import com.android.tools.r8.utils.collections.LongLivedClassSetBuilder;
 import com.android.tools.r8.utils.collections.LongLivedProgramMethodMapBuilder;
@@ -860,89 +861,102 @@
     // This maps the ordinal to the object state, note that some fields may have been removed,
     // hence the entry is in this map but not the enumToOrdinalMap.
     Int2ReferenceMap<ObjectState> ordinalToObjectState = new Int2ReferenceArrayMap<>();
-    // Any fields matching the expected $VALUES content can be recorded here, they have however
-    // all the same content.
-    ImmutableSet.Builder<DexField> valuesField = ImmutableSet.builder();
-    EnumValuesObjectState valuesContents = null;
 
-    EnumStaticFieldValues enumStaticFieldValues = staticFieldValuesMap.get(enumClass.type);
-    if (enumStaticFieldValues == null) {
+    if (!staticFieldValuesMap.containsKey(enumClass.getType())) {
       reportFailure(enumClass, new MissingEnumStaticFieldValuesReason());
       return null;
     }
 
-    enumStaticFieldValues =
-        enumStaticFieldValues.rewrittenWithLens(appView, appView.graphLens(), appView.codeLens());
+    EnumStaticFieldValues enumStaticFieldValues =
+        staticFieldValuesMap
+            .get(enumClass.getType())
+            .rewrittenWithLens(appView, appView.graphLens(), appView.codeLens());
     Set<DexType> enumSubtypes = enumUnboxingCandidatesInfo.getSubtypes(enumClass.getType());
 
     // Step 1: We iterate over the field to find direct enum instance information and the values
     // fields.
-    for (DexEncodedField staticField : enumClass.staticFields()) {
-      // The field might be specialized while the data was recorded without the specialization.
-      if (factory.enumMembers.isEnumField(staticField, enumClass.type, enumSubtypes)) {
-        ObjectState enumState =
-            enumStaticFieldValues.getObjectStateForPossiblyPinnedField(staticField.getReference());
-        if (enumState == null) {
-          assert enumStaticFieldValues.getObjectStateForPossiblyPinnedField(
-                  staticField.getReference().withType(enumClass.type, factory))
-              == null;
-          if (staticField.getOptimizationInfo().isDead()) {
-            // We don't care about unused field data.
-            continue;
-          }
-          // We could not track the content of that field. We bail out.
-          reportFailure(
-              enumClass, new MissingObjectStateForEnumInstanceReason(staticField.getReference()));
-          return null;
-        }
-        OptionalInt optionalOrdinal = getOrdinal(enumState);
-        if (!optionalOrdinal.isPresent()) {
-          reportFailure(
-              enumClass,
-              new MissingInstanceFieldValueForEnumInstanceReason(
-                  factory.enumMembers.ordinalField, staticField.getReference()));
-          return null;
-        }
-        int ordinal = optionalOrdinal.getAsInt();
-        unboxedValues.put(staticField.getReference(), ordinalToUnboxedInt(ordinal));
-        ordinalToObjectState.put(ordinal, enumState);
-        if (isEnumWithSubtypes) {
-          DynamicType dynamicType = staticField.getOptimizationInfo().getDynamicType();
-          if (dynamicType.isExactClassType()) {
-            valueTypes.put(ordinal, dynamicType.getExactClassType().getClassType());
-          } else {
-            reportFailure(
-                enumClass,
-                new MissingExactDynamicEnumTypeForEnumWithSubtypesReason(
-                    staticField.getReference()));
-            return null;
-          }
-        }
-      } else if (factory.enumMembers.isValuesFieldCandidate(staticField, enumClass.type)) {
-        ObjectState valuesState =
-            enumStaticFieldValues.getObjectStateForPossiblyPinnedField(staticField.getReference());
-        if (valuesState == null) {
-          if (staticField.getOptimizationInfo().isDead()) {
-            // We don't care about unused field data.
-            continue;
-          }
-          // We could not track the content of that field. We bail out.
-          // We could not track the content of that field, and the field could be a values field.
-          // We conservatively bail out.
-          reportFailure(
-              enumClass, new MissingContentsForEnumValuesArrayReason(staticField.getReference()));
-          return null;
-        }
-        assert valuesState.isEnumValuesObjectState();
-        assert valuesContents == null
-            || valuesContents.equals(valuesState.asEnumValuesObjectState());
-        valuesContents = valuesState.asEnumValuesObjectState();
-        valuesField.add(staticField.getReference());
-      }
+    ImmutableSet.Builder<DexField> valuesField = ImmutableSet.builder();
+    TraversalContinuation<?, EnumValuesObjectState> traversalContinuation =
+        enumClass.traverseProgramFields(
+            (field, valuesContents) -> {
+              if (!field.getAccessFlags().isStatic()) {
+                return TraversalContinuation.doContinue(valuesContents);
+              }
+              // The field might be specialized while the data was recorded without the
+              // specialization.
+              if (factory.enumMembers.isEnumField(field, enumClass.type, enumSubtypes)) {
+                ObjectState enumState =
+                    enumStaticFieldValues.getObjectStateForPossiblyPinnedField(
+                        field.getReference());
+                if (enumState == null) {
+                  assert enumStaticFieldValues.getObjectStateForPossiblyPinnedField(
+                          field.getReference().withType(enumClass.type, factory))
+                      == null;
+                  if (field.getOptimizationInfo().isDead()) {
+                    // We don't care about unused field data.
+                    return TraversalContinuation.doContinue(valuesContents);
+                  }
+                  // We could not track the content of that field. We bail out.
+                  reportFailure(
+                      enumClass, new MissingObjectStateForEnumInstanceReason(field.getReference()));
+                  return TraversalContinuation.doBreak();
+                }
+                OptionalInt optionalOrdinal = getOrdinal(enumState);
+                if (!optionalOrdinal.isPresent()) {
+                  reportFailure(
+                      enumClass,
+                      new MissingInstanceFieldValueForEnumInstanceReason(
+                          factory.enumMembers.ordinalField, field.getReference()));
+                  return TraversalContinuation.doBreak();
+                }
+                int ordinal = optionalOrdinal.getAsInt();
+                unboxedValues.put(field.getReference(), ordinalToUnboxedInt(ordinal));
+                ordinalToObjectState.put(ordinal, enumState);
+                if (isEnumWithSubtypes) {
+                  DynamicType dynamicType = field.getOptimizationInfo().getDynamicType();
+                  if (dynamicType.isExactClassType()) {
+                    valueTypes.put(ordinal, dynamicType.getExactClassType().getClassType());
+                  } else {
+                    reportFailure(
+                        enumClass,
+                        new MissingExactDynamicEnumTypeForEnumWithSubtypesReason(
+                            field.getReference()));
+                    return TraversalContinuation.doBreak();
+                  }
+                }
+              } else if (factory.enumMembers.isValuesFieldCandidate(field, enumClass.type)) {
+                ObjectState valuesState =
+                    enumStaticFieldValues.getObjectStateForPossiblyPinnedField(
+                        field.getReference());
+                if (valuesState == null) {
+                  if (field.getOptimizationInfo().isDead()) {
+                    // We don't care about unused field data.
+                    return TraversalContinuation.doContinue(valuesContents);
+                  }
+                  // We could not track the content of that field. We bail out.
+                  // We could not track the content of that field, and the field could be a values
+                  // field.
+                  // We conservatively bail out.
+                  reportFailure(
+                      enumClass, new MissingContentsForEnumValuesArrayReason(field.getReference()));
+                  return TraversalContinuation.doBreak();
+                }
+                assert valuesState.isEnumValuesObjectState();
+                assert valuesContents == null
+                    || valuesContents.equals(valuesState.asEnumValuesObjectState());
+                valuesContents = valuesState.asEnumValuesObjectState();
+                valuesField.add(field.getReference());
+              }
+              return TraversalContinuation.doContinue(valuesContents);
+            },
+            null);
+    if (traversalContinuation.shouldBreak()) {
+      return null;
     }
 
     // Step 2: We complete the information based on the values content, since some enum instances
     // may be reachable only though the $VALUES field.
+    EnumValuesObjectState valuesContents = traversalContinuation.asContinue().getValue();
     if (valuesContents != null) {
       for (int ordinal = 0; ordinal < valuesContents.getEnumValuesSize(); ordinal++) {
         if (!ordinalToObjectState.containsKey(ordinal)) {
@@ -1026,7 +1040,7 @@
     boolean canBeOrdinal = instanceField.type.isIntType();
     ImmutableInt2ReferenceSortedMap.Builder<AbstractValue> data =
         ImmutableInt2ReferenceSortedMap.builder();
-    for (Integer ordinal : ordinalToObjectState.keySet()) {
+    for (int ordinal : ordinalToObjectState.keySet()) {
       ObjectState state = ordinalToObjectState.get(ordinal);
       AbstractValue fieldValue = state.getAbstractFieldValue(encodedInstanceField);
       if (!fieldValue.isSingleValue()) {
@@ -1486,9 +1500,27 @@
       } else if (singleTargetReference == factory.enumMembers.hashCode) {
         return Reason.ELIGIBLE;
       } else if (singleTargetReference == factory.enumMembers.constructor) {
-        // Enum constructor call is allowed only if called from an enum initializer.
-        if (code.method().isInstanceInitializer() && code.context().getHolder() == enumClass) {
-          return Reason.ELIGIBLE;
+        assert invoke.getFirstArgument() == enumValue;
+        if (appView.options().canInitNewInstanceUsingSuperclassConstructor()) {
+          // Enum constructor call is allowed if called from any enum initializer.
+          DexProgramClass representativeContext =
+              enumUnboxingCandidatesInfo.getCandidateClassOrNull(context.getHolderType());
+          if (context.getDefinition().isInstanceInitializer()
+              && representativeContext == enumClass) {
+            return Reason.ELIGIBLE;
+          }
+          // Otherwise must be called from the class initializer of a root enum initializer.
+          if (context.isStructurallyEqualTo(enumClass.getProgramClassInitializer())) {
+            assert enumUnboxingCandidatesInfo.verifyIsSuperEnumUnboxingCandidate(enumClass);
+            assert context.getHolder() == representativeContext;
+            return Reason.ELIGIBLE;
+          }
+        } else {
+          // Enum constructor call is allowed only if called from a root enum initializer.
+          if (context.getDefinition().isInstanceInitializer() && context.getHolder() == enumClass) {
+            assert enumUnboxingCandidatesInfo.verifyIsSuperEnumUnboxingCandidate(enumClass);
+            return Reason.ELIGIBLE;
+          }
         }
       }
       return new UnsupportedLibraryInvokeReason(singleTargetReference);
@@ -1524,6 +1556,13 @@
           || singleTargetReference == factory.objectsMethods.requireNonNullWithMessage) {
         return Reason.ELIGIBLE;
       }
+      if (singleTargetReference == factory.objectsMethods.toStringWithObject) {
+        addRequiredNameData(enumClass);
+        return Reason.ELIGIBLE;
+      }
+      if (singleTargetReference == factory.objectsMethods.equals) {
+        return comparableAsUnboxedValues(invoke);
+      }
       return new UnsupportedLibraryInvokeReason(singleTargetReference);
     }
 
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingCandidateInfoCollection.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingCandidateInfoCollection.java
index 70e73dc..06cf453 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingCandidateInfoCollection.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingCandidateInfoCollection.java
@@ -158,6 +158,11 @@
     return true;
   }
 
+  public boolean verifyIsSuperEnumUnboxingCandidate(DexProgramClass clazz) {
+    assert enumTypeToInfo.containsKey(clazz.getType());
+    return true;
+  }
+
   public static class EnumUnboxingCandidateInfo {
 
     private final DexProgramClass enumClass;
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingCfMethods.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingCfMethods.java
index be89c4e..53e7b32 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingCfMethods.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingCfMethods.java
@@ -139,6 +139,38 @@
         ImmutableList.of());
   }
 
+  public static CfCode EnumUnboxingMethods_objectEquals(DexItemFactory factory, DexMethod method) {
+    CfLabel label0 = new CfLabel();
+    CfLabel label1 = new CfLabel();
+    CfLabel label2 = new CfLabel();
+    CfLabel label3 = new CfLabel();
+    return new CfCode(
+        method.holder,
+        2,
+        2,
+        ImmutableList.of(
+            label0,
+            new CfLoad(ValueType.INT, 0),
+            new CfLoad(ValueType.INT, 1),
+            new CfIfCmp(IfType.NE, ValueType.INT, label1),
+            new CfConstNumber(1, ValueType.INT),
+            new CfGoto(label2),
+            label1,
+            new CfFrame(
+                new Int2ObjectAVLTreeMap<>(
+                    new int[] {0, 1}, new FrameType[] {FrameType.intType(), FrameType.intType()})),
+            new CfConstNumber(0, ValueType.INT),
+            label2,
+            new CfFrame(
+                new Int2ObjectAVLTreeMap<>(
+                    new int[] {0, 1}, new FrameType[] {FrameType.intType(), FrameType.intType()}),
+                new ArrayDeque<>(Arrays.asList(FrameType.intType()))),
+            new CfReturn(ValueType.INT),
+            label3),
+        ImmutableList.of(),
+        ImmutableList.of());
+  }
+
   public static CfCode EnumUnboxingMethods_ordinal(DexItemFactory factory, DexMethod method) {
     CfLabel label0 = new CfLabel();
     CfLabel label1 = new CfLabel();
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingRewriter.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingRewriter.java
index 789c542..05648c3 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingRewriter.java
@@ -499,6 +499,18 @@
               getSharedUtilityClass()
                   .ensureCheckNotZeroWithMessageMethod(appView, context, eventConsumer));
         }
+      } else if (invokedMethod == factory.objectsMethods.toStringWithObject) {
+        rewriteStringValueOf(invoke, context, convertedEnums, instructionIterator, eventConsumer);
+      } else if (invokedMethod == factory.objectsMethods.equals) {
+        assert invoke.arguments().size() == 2;
+        Value argument = invoke.getFirstArgument();
+        DexType enumType = getEnumClassTypeOrNull(argument, convertedEnums);
+        if (enumType != null) {
+          replaceEnumInvoke(
+              instructionIterator,
+              invoke,
+              getSharedUtilityClass().ensureObjectsEqualsMethod(appView, context, eventConsumer));
+        }
       }
       return;
     }
@@ -506,17 +518,7 @@
     // Calls to java.lang.String.
     if (invokedMethod.getHolderType() == factory.stringType) {
       if (invokedMethod == factory.stringMembers.valueOf) {
-        assert invoke.arguments().size() == 1;
-        Value argument = invoke.getFirstArgument();
-        DexType enumType = getEnumClassTypeOrNull(argument, convertedEnums);
-        if (enumType != null) {
-          ProgramMethod stringValueOfMethod =
-              getLocalUtilityClass(enumType)
-                  .ensureStringValueOfMethod(appView, context, eventConsumer);
-          instructionIterator.replaceCurrentInstruction(
-              new InvokeStatic(
-                  stringValueOfMethod.getReference(), invoke.outValue(), invoke.arguments()));
-        }
+        rewriteStringValueOf(invoke, context, convertedEnums, instructionIterator, eventConsumer);
       }
       return;
     }
@@ -574,6 +576,24 @@
     }
   }
 
+  private void rewriteStringValueOf(
+      InvokeStatic invoke,
+      ProgramMethod context,
+      Map<Instruction, DexType> convertedEnums,
+      InstructionListIterator instructionIterator,
+      EnumUnboxerMethodProcessorEventConsumer eventConsumer) {
+    assert invoke.arguments().size() == 1;
+    Value argument = invoke.getFirstArgument();
+    DexType enumType = getEnumClassTypeOrNull(argument, convertedEnums);
+    if (enumType != null) {
+      ProgramMethod stringValueOfMethod =
+          getLocalUtilityClass(enumType).ensureStringValueOfMethod(appView, context, eventConsumer);
+      instructionIterator.replaceCurrentInstruction(
+          new InvokeStatic(
+              stringValueOfMethod.getReference(), invoke.outValue(), invoke.arguments()));
+    }
+  }
+
   public void rewriteNullCheck(
       InstructionListIterator iterator,
       InvokeMethod invoke,
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingTreeFixer.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingTreeFixer.java
index 6b8b450..189f603 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingTreeFixer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingTreeFixer.java
@@ -22,6 +22,7 @@
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.DexValue;
 import com.android.tools.r8.graph.MethodAccessFlags;
+import com.android.tools.r8.graph.MethodResolutionResult;
 import com.android.tools.r8.graph.ProgramField;
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.graph.PrunedItems;
@@ -32,6 +33,7 @@
 import com.android.tools.r8.graph.proto.RewrittenPrototypeDescription;
 import com.android.tools.r8.ir.analysis.type.ClassTypeElement;
 import com.android.tools.r8.ir.analysis.type.TypeElement;
+import com.android.tools.r8.ir.analysis.value.SingleNumberValue;
 import com.android.tools.r8.ir.code.BasicBlock;
 import com.android.tools.r8.ir.code.ConstClass;
 import com.android.tools.r8.ir.code.IRCode;
@@ -41,8 +43,11 @@
 import com.android.tools.r8.ir.code.InvokeVirtual;
 import com.android.tools.r8.ir.code.NewInstance;
 import com.android.tools.r8.ir.code.NewUnboxedEnumInstance;
+import com.android.tools.r8.ir.code.Position;
 import com.android.tools.r8.ir.code.StaticPut;
+import com.android.tools.r8.ir.code.TypeAndLocalInfoSupplier;
 import com.android.tools.r8.ir.code.Value;
+import com.android.tools.r8.ir.conversion.ExtraParameter;
 import com.android.tools.r8.ir.conversion.ExtraUnusedNullParameter;
 import com.android.tools.r8.ir.conversion.IRConverter;
 import com.android.tools.r8.ir.conversion.MethodProcessorEventConsumer;
@@ -288,10 +293,7 @@
   private void fixupSuperEnumClassInitializers(
       IRConverter converter, ExecutorService executorService) throws ExecutionException {
     DexEncodedField ordinalField =
-        appView
-            .appInfo()
-            .resolveField(appView.dexItemFactory().enumMembers.ordinalField)
-            .getResolvedField();
+        appView.appInfo().resolveField(factory.enumMembers.ordinalField).getResolvedField();
     ThreadUtils.processItems(
         unboxedEnumHierarchy.keySet(),
         unboxedEnum -> fixupSuperEnumClassInitializer(converter, unboxedEnum, ordinalField),
@@ -346,8 +348,7 @@
           for (Instruction user : constClass.outValue().aliasedUsers()) {
             if (user.isInvokeVirtual()) {
               InvokeVirtual invoke = user.asInvokeVirtual();
-              if (invoke.getInvokedMethod()
-                  == appView.dexItemFactory().classMethods.desiredAssertionStatus) {
+              if (invoke.getInvokedMethod() == factory.classMethods.desiredAssertionStatus) {
                 desiredAssertionStatusUsers.add(invoke);
               }
             }
@@ -371,8 +372,7 @@
           NewInstance newInstance = instruction.asNewInstance();
           DexType rewrittenType = appView.graphLens().lookupType(newInstance.getType());
           if (enumDataMap.isAssignableTo(rewrittenType, unboxedEnum.getType())) {
-            InvokeDirect constructorInvoke =
-                newInstance.getUniqueConstructorInvoke(appView.dexItemFactory());
+            InvokeDirect constructorInvoke = newInstance.getUniqueConstructorInvoke(factory);
             assert constructorInvoke != null;
 
             DexMethod invokedMethod = constructorInvoke.getInvokedMethod();
@@ -382,7 +382,8 @@
             // find the ordinal of the current enum instance.
             MethodLookupResult lookupResult =
                 appView.graphLens().lookupInvokeDirect(invokedMethod, classInitializer);
-            if (lookupResult.getReference() != invokedMethod) {
+            if (lookupResult.getReference() != invokedMethod
+                || !lookupResult.getPrototypeChanges().isEmpty()) {
               List<Value> rewrittenArguments =
                   new ArrayList<>(constructorInvoke.arguments().size());
               for (int i = 0; i < constructorInvoke.arguments().size(); i++) {
@@ -394,6 +395,23 @@
                   rewrittenArguments.add(argument);
                 }
               }
+              for (ExtraParameter extraParameter :
+                  lookupResult.getPrototypeChanges().getExtraParameters()) {
+                SingleNumberValue singleNumberValue = extraParameter.getValue(appView);
+                Instruction materializingInstruction =
+                    singleNumberValue.createMaterializingInstruction(
+                        appView,
+                        code,
+                        TypeAndLocalInfoSupplier.create(
+                            extraParameter.getType(appView.dexItemFactory()).toTypeElement(appView),
+                            null));
+                materializingInstruction.setPosition(Position.none());
+                instructionIterator.previous();
+                instructionIterator.add(materializingInstruction);
+                rewrittenArguments.add(materializingInstruction.outValue());
+                Instruction next = instructionIterator.next();
+                assert next == newInstance;
+              }
               InvokeDirect originalConstructorInvoke = constructorInvoke;
               constructorInvoke =
                   InvokeDirect.builder()
@@ -409,7 +427,6 @@
               // then remove the rewritten constructor invoke from the IR.
               instructionsToRemove.put(originalConstructorInvoke, Optional.of(constructorInvoke));
             } else {
-              assert lookupResult.getPrototypeChanges().isEmpty();
               // Record that the constructor invoke needs to be removed.
               instructionsToRemove.put(constructorInvoke, Optional.empty());
             }
@@ -418,7 +435,17 @@
                 newInstance.getType() == unboxedEnum.getType()
                     ? unboxedEnum
                     : appView.programDefinitionFor(newInstance.getType(), classInitializer);
-            ProgramMethod constructor = holder.lookupProgramMethod(lookupResult.getReference());
+            DexClassAndMethod constructor;
+            if (appView.options().canInitNewInstanceUsingSuperclassConstructor()) {
+              MethodResolutionResult resolutionResult =
+                  appView
+                      .appInfo()
+                      .resolveMethod(
+                          lookupResult.getReference(), constructorInvoke.getInterfaceBit());
+              constructor = resolutionResult.getResolutionPair();
+            } else {
+              constructor = holder.lookupProgramMethod(lookupResult.getReference());
+            }
             assert constructor != null;
 
             InstanceFieldInitializationInfo ordinalInitializationInfo =
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/SharedEnumUnboxingUtilityClass.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/SharedEnumUnboxingUtilityClass.java
index 3792bd4..37c814d 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/enums/SharedEnumUnboxingUtilityClass.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/SharedEnumUnboxingUtilityClass.java
@@ -72,6 +72,7 @@
     ensureCheckNotZeroWithMessageMethod(appView);
     ensureCompareToMethod(appView);
     ensureEqualsMethod(appView);
+    ensureObjectsEqualsMethod(appView);
     ensureOrdinalMethod(appView);
   }
 
@@ -151,6 +152,25 @@
         method -> EnumUnboxingCfMethods.EnumUnboxingMethods_equals(dexItemFactory, method));
   }
 
+  public ProgramMethod ensureObjectsEqualsMethod(
+      AppView<AppInfoWithLiveness> appView,
+      ProgramMethod context,
+      EnumUnboxerMethodProcessorEventConsumer eventConsumer) {
+    ProgramMethod method = ensureObjectsEqualsMethod(appView);
+    eventConsumer.acceptEnumUnboxerSharedUtilityClassMethodContext(method, context);
+    return method;
+  }
+
+  private ProgramMethod ensureObjectsEqualsMethod(AppView<AppInfoWithLiveness> appView) {
+    DexItemFactory dexItemFactory = appView.dexItemFactory();
+    return internalEnsureMethod(
+        appView,
+        dexItemFactory.createString("objects$equals"),
+        dexItemFactory.createProto(
+            dexItemFactory.booleanType, dexItemFactory.intType, dexItemFactory.intType),
+        method -> EnumUnboxingCfMethods.EnumUnboxingMethods_objectEquals(dexItemFactory, method));
+  }
+
   public ProgramMethod ensureOrdinalMethod(
       AppView<AppInfoWithLiveness> appView,
       ProgramMethod context,
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfoCollector.java b/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfoCollector.java
index f8e784e..ba4ba64 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfoCollector.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfoCollector.java
@@ -46,6 +46,7 @@
 
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexClassAndField;
 import com.android.tools.r8.graph.DexEncodedField;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexItemFactory;
@@ -339,8 +340,8 @@
           case STATIC_GET:
             {
               FieldInstruction fieldGet = instruction.asFieldInstruction();
-              DexEncodedField field =
-                  appView.appInfo().resolveField(fieldGet.getField()).getResolvedField();
+              DexClassAndField field =
+                  appView.appInfo().resolveField(fieldGet.getField()).getResolutionPair();
               if (field == null) {
                 return null;
               }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/bridge/BridgeAnalyzer.java b/src/main/java/com/android/tools/r8/ir/optimize/info/bridge/BridgeAnalyzer.java
index be806b4..88ee7f1 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/bridge/BridgeAnalyzer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/bridge/BridgeAnalyzer.java
@@ -6,14 +6,18 @@
 import static com.android.tools.r8.ir.code.Opcodes.ARGUMENT;
 import static com.android.tools.r8.ir.code.Opcodes.ASSUME;
 import static com.android.tools.r8.ir.code.Opcodes.CHECK_CAST;
+import static com.android.tools.r8.ir.code.Opcodes.GOTO;
 import static com.android.tools.r8.ir.code.Opcodes.INVOKE_DIRECT;
 import static com.android.tools.r8.ir.code.Opcodes.INVOKE_VIRTUAL;
 import static com.android.tools.r8.ir.code.Opcodes.RETURN;
 
 import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.ir.code.BasicBlock;
 import com.android.tools.r8.ir.code.CheckCast;
+import com.android.tools.r8.ir.code.Goto;
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.Instruction;
+import com.android.tools.r8.ir.code.InstructionListIterator;
 import com.android.tools.r8.ir.code.InvokeMethod;
 import com.android.tools.r8.ir.code.InvokeMethodWithReceiver;
 import com.android.tools.r8.ir.code.Return;
@@ -24,17 +28,15 @@
 
   /** Returns a {@link BridgeInfo} object describing this method if it is recognized as a bridge. */
   public static BridgeInfo analyzeMethod(DexEncodedMethod method, IRCode code) {
-    if (code.blocks.size() > 1) {
-      return failure();
-    }
-
     // Scan through the instructions one-by-one. We expect a sequence of Argument instructions,
     // followed by a (possibly empty) sequence of CheckCast instructions, followed by a single
     // InvokeMethod instruction, followed by an optional CheckCast instruction, followed by a Return
     // instruction.
     InvokeMethodWithReceiver uniqueInvoke = null;
     CheckCast uniqueReturnCast = null;
-    for (Instruction instruction : code.entryBlock().getInstructions()) {
+    InstructionListIterator instructionIterator = code.entryBlock().listIterator(code);
+    while (instructionIterator.hasNext()) {
+      Instruction instruction = instructionIterator.next();
       switch (instruction.opcode()) {
         case ARGUMENT:
           break;
@@ -74,6 +76,17 @@
             break;
           }
 
+        case GOTO:
+          {
+            Goto gotoInstruction = instruction.asGoto();
+            BasicBlock targetBlock = gotoInstruction.getTarget();
+            if (targetBlock.hasCatchHandlers()) {
+              return failure();
+            }
+            instructionIterator = targetBlock.listIterator(code);
+            break;
+          }
+
         case RETURN:
           if (!analyzeReturn(instruction.asReturn(), uniqueInvoke, uniqueReturnCast)) {
             return failure();
@@ -107,8 +120,10 @@
     }
     int argumentIndex = object.definition.asArgument().getIndex();
     Value castValue = checkCast.outValue();
-    // The out value cannot have any phi users since there is only one block.
-    assert !castValue.hasPhiUsers();
+    // The out value should not have any phi users since we only allow linear control flow.
+    if (castValue.hasPhiUsers()) {
+      return false;
+    }
     // It is not allowed to have any other users than the invoke instruction.
     if (castValue.hasDebugUsers() || !castValue.hasSingleUniqueUser()) {
       return false;
@@ -142,8 +157,10 @@
     Value returnValue = invoke.outValue();
     Value uncastValue = checkCast.object().getAliasedValue();
     Value castValue = checkCast.outValue();
-    // The out value cannot have any phi users since there is only one block.
-    assert !castValue.hasPhiUsers();
+    // The out value should not have any phi users since we only allow linear control flow.
+    if (castValue.hasPhiUsers()) {
+      return false;
+    }
     // It must cast the result to the return type of the enclosing method and return the cast value.
     return uncastValue == returnValue
         && checkCast.getType() == method.returnType()
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/field/EmptyInstanceFieldInitializationInfoCollection.java b/src/main/java/com/android/tools/r8/ir/optimize/info/field/EmptyInstanceFieldInitializationInfoCollection.java
index 1de19ad..c5ab574 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/field/EmptyInstanceFieldInitializationInfoCollection.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/field/EmptyInstanceFieldInitializationInfoCollection.java
@@ -5,6 +5,7 @@
 package com.android.tools.r8.ir.optimize.info.field;
 
 import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexClassAndField;
 import com.android.tools.r8.graph.DexDefinitionSupplier;
 import com.android.tools.r8.graph.DexEncodedField;
 import com.android.tools.r8.graph.lens.GraphLens;
@@ -31,14 +32,14 @@
   @Override
   public void forEach(
       DexDefinitionSupplier definitions,
-      BiConsumer<DexEncodedField, InstanceFieldInitializationInfo> consumer) {
+      BiConsumer<DexClassAndField, InstanceFieldInitializationInfo> consumer) {
     // Intentionally empty.
   }
 
   @Override
   public void forEachWithDeterministicOrder(
       DexDefinitionSupplier definitions,
-      BiConsumer<DexEncodedField, InstanceFieldInitializationInfo> consumer) {
+      BiConsumer<DexClassAndField, InstanceFieldInitializationInfo> consumer) {
     // Intentionally empty.
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/field/InstanceFieldInitializationInfoCollection.java b/src/main/java/com/android/tools/r8/ir/optimize/info/field/InstanceFieldInitializationInfoCollection.java
index d6ab75a..e4b044d 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/field/InstanceFieldInitializationInfoCollection.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/field/InstanceFieldInitializationInfoCollection.java
@@ -30,11 +30,11 @@
 
   public abstract void forEach(
       DexDefinitionSupplier definitions,
-      BiConsumer<DexEncodedField, InstanceFieldInitializationInfo> consumer);
+      BiConsumer<DexClassAndField, InstanceFieldInitializationInfo> consumer);
 
   public abstract void forEachWithDeterministicOrder(
       DexDefinitionSupplier definitions,
-      BiConsumer<DexEncodedField, InstanceFieldInitializationInfo> consumer);
+      BiConsumer<DexClassAndField, InstanceFieldInitializationInfo> consumer);
 
   public abstract InstanceFieldInitializationInfo get(DexEncodedField field);
 
@@ -55,7 +55,7 @@
     TreeMap<DexField, InstanceFieldInitializationInfo> infos = new TreeMap<>(DexField::compareTo);
 
     public void recordInitializationInfo(
-        DexEncodedField field, InstanceFieldInitializationInfo info) {
+        DexClassAndField field, InstanceFieldInitializationInfo info) {
       recordInitializationInfo(field.getReference(), info);
     }
 
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/field/NonTrivialInstanceFieldInitializationInfoCollection.java b/src/main/java/com/android/tools/r8/ir/optimize/info/field/NonTrivialInstanceFieldInitializationInfoCollection.java
index 1ecc4df..88a7626 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/field/NonTrivialInstanceFieldInitializationInfoCollection.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/field/NonTrivialInstanceFieldInitializationInfoCollection.java
@@ -6,7 +6,7 @@
 
 
 import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexClassAndField;
 import com.android.tools.r8.graph.DexDefinitionSupplier;
 import com.android.tools.r8.graph.DexEncodedField;
 import com.android.tools.r8.graph.DexField;
@@ -35,11 +35,10 @@
   @Override
   public void forEach(
       DexDefinitionSupplier definitions,
-      BiConsumer<DexEncodedField, InstanceFieldInitializationInfo> consumer) {
+      BiConsumer<DexClassAndField, InstanceFieldInitializationInfo> consumer) {
     infos.forEach(
         (field, info) -> {
-          DexClass holder = definitions.definitionForHolder(field);
-          DexEncodedField definition = field.lookupOnClass(holder);
+          DexClassAndField definition = definitions.definitionFor(field);
           if (definition != null) {
             consumer.accept(definition, info);
           } else {
@@ -51,7 +50,7 @@
   @Override
   public void forEachWithDeterministicOrder(
       DexDefinitionSupplier definitions,
-      BiConsumer<DexEncodedField, InstanceFieldInitializationInfo> consumer) {
+      BiConsumer<DexClassAndField, InstanceFieldInitializationInfo> consumer) {
     // We currently use a sorted backing and can therefore simply use forEach().
     forEach(definitions, consumer);
   }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/initializer/NonTrivialInstanceInitializerInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/info/initializer/NonTrivialInstanceInitializerInfo.java
index 2005de2..096adfb 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/initializer/NonTrivialInstanceInitializerInfo.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/initializer/NonTrivialInstanceInitializerInfo.java
@@ -5,7 +5,7 @@
 package com.android.tools.r8.ir.optimize.info.initializer;
 
 import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.DexEncodedField;
+import com.android.tools.r8.graph.DexClassAndField;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.PrunedItems;
 import com.android.tools.r8.graph.lens.GraphLens;
@@ -149,12 +149,12 @@
           && parent == null;
     }
 
-    public Builder markFieldAsRead(DexEncodedField field) {
+    public Builder markFieldAsRead(DexClassAndField field) {
       if (readSet.isKnownFieldSet()) {
         if (readSet.isBottom()) {
-          readSet = new ConcreteMutableFieldSet(field);
+          readSet = new ConcreteMutableFieldSet(field.getDefinition());
         } else {
-          readSet.asConcreteFieldSet().add(field);
+          readSet.asConcreteFieldSet().add(field.getDefinition());
         }
       }
       assert readSet.contains(field);
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/membervaluepropagation/R8MemberValuePropagation.java b/src/main/java/com/android/tools/r8/ir/optimize/membervaluepropagation/R8MemberValuePropagation.java
index 1810750..b8d6c66 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/membervaluepropagation/R8MemberValuePropagation.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/membervaluepropagation/R8MemberValuePropagation.java
@@ -271,7 +271,7 @@
     AbstractValue abstractValue;
     if (field.getType().isAlwaysNull(appView)) {
       abstractValue = appView.abstractValueFactory().createSingleNumberValue(0);
-    } else if (appView.appInfo().isFieldWrittenByFieldPutInstruction(definition)) {
+    } else if (appView.appInfo().isFieldWrittenByFieldPutInstruction(target)) {
       abstractValue = definition.getOptimizationInfo().getAbstractValue();
       if (abstractValue.isUnknown() && !definition.isStatic()) {
         AbstractValue abstractReceiverValue =
@@ -350,8 +350,7 @@
     // If the field is read, we can't remove the instance-put unless the value of the field is known
     // to be null (in which case the instance-put is a no-op because it assigns the field the same
     // value as its default value).
-    if (field.getType().isAlwaysNull(appView)
-        || !appView.appInfo().isFieldRead(field.getDefinition())) {
+    if (field.getType().isAlwaysNull(appView) || !appView.appInfo().isFieldRead(field)) {
       iterator.replaceCurrentInstructionByNullCheckIfPossible(appView, code.context());
       return;
     }
@@ -376,8 +375,7 @@
     // If the field is read, we can't remove the static-put unless the value of the field is known
     // to be null (in which case the static-put is a no-op because it assigns the field the same
     // value as its default value).
-    if (field.getType().isAlwaysNull(appView)
-        || !appView.appInfo().isFieldRead(field.getDefinition())) {
+    if (field.getType().isAlwaysNull(appView) || !appView.appInfo().isFieldRead(field)) {
       iterator.removeOrReplaceCurrentInstructionByInitClassIfPossible(
           appView, code, field.getHolderType());
       return;
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/string/StringBuilderAppendOptimizer.java b/src/main/java/com/android/tools/r8/ir/optimize/string/StringBuilderAppendOptimizer.java
index 5f0bea9..2798fb0 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/string/StringBuilderAppendOptimizer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/string/StringBuilderAppendOptimizer.java
@@ -31,7 +31,6 @@
 import com.android.tools.r8.ir.code.Phi;
 import com.android.tools.r8.ir.code.Value;
 import com.android.tools.r8.ir.optimize.string.StringBuilderNode.AppendNode;
-import com.android.tools.r8.ir.optimize.string.StringBuilderNode.EscapeNode;
 import com.android.tools.r8.ir.optimize.string.StringBuilderNode.ImplicitToStringNode;
 import com.android.tools.r8.ir.optimize.string.StringBuilderNode.InitNode;
 import com.android.tools.r8.ir.optimize.string.StringBuilderNode.InitOrAppendNode;
@@ -177,15 +176,17 @@
             previousState = result.asAbstractState();
             for (Phi phi : block.getPhis()) {
               if (previousState.isLiveStringBuilder(phi)) {
-                visitAllAliasing(
-                    phi,
-                    previousState,
-                    value -> {},
-                    alias -> {
-                      EscapeNode escapeNode = new EscapeNode();
-                      currentRoots.put(alias, escapeNode);
-                      currentTails.put(alias, escapeNode);
-                    });
+                boolean seenEscaped =
+                    visitAllAliasing(
+                        phi,
+                        previousState,
+                        value -> {},
+                        alias ->
+                            addNodeToRootAndTail(
+                                currentRoots, currentTails, alias, createEscapeNode()));
+                if (seenEscaped) {
+                  addNodeToRootAndTail(currentRoots, currentTails, phi, createEscapeNode());
+                }
               }
             }
             for (Instruction instruction : block.getInstructions()) {
@@ -198,16 +199,8 @@
               createNodesForInstruction(
                   instruction,
                   previousState,
-                  (value, sbNode) -> {
-                    StringBuilderNode currentTail = currentTails.get(value);
-                    if (currentTail == null) {
-                      currentRoots.put(value, sbNode);
-                      currentTails.put(value, sbNode);
-                    } else if (shouldAddNodeToGraph(currentTail, sbNode)) {
-                      currentTail.addSuccessor(sbNode);
-                      currentTails.put(value, sbNode);
-                    }
-                  });
+                  (value, sbNode) ->
+                      addNodeToRootAndTail(currentRoots, currentTails, value, sbNode));
             }
             assert currentRoots.keySet().equals(currentTails.keySet());
             assert previousState.getLiveStringBuilders().containsAll(currentRoots.keySet())
@@ -219,6 +212,21 @@
             return TraversalContinuation.doContinue();
           }
 
+          private void addNodeToRootAndTail(
+              Map<Value, StringBuilderNode> currentRoots,
+              Map<Value, StringBuilderNode> currentTails,
+              Value value,
+              StringBuilderNode node) {
+            StringBuilderNode currentTail = currentTails.get(value);
+            if (currentTail == null) {
+              currentRoots.put(value, node);
+              currentTails.put(value, node);
+            } else if (shouldAddNodeToGraph(currentTail, node)) {
+              currentTail.addSuccessor(node);
+              currentTails.put(value, node);
+            }
+          }
+
           private boolean shouldAddNodeToGraph(
               StringBuilderNode insertedNode, StringBuilderNode newNode) {
             // No need for multiple mutating nodes or inspecting nodes.
@@ -226,6 +234,8 @@
               return !newNode.isMutateNode() && !newNode.isInspectingNode();
             } else if (insertedNode.isInspectingNode()) {
               return !newNode.isInspectingNode();
+            } else if (insertedNode.isEscapeNode()) {
+              return !newNode.isEscapeNode();
             }
             return true;
           }
diff --git a/src/main/java/com/android/tools/r8/ir/regalloc/LinearScanRegisterAllocator.java b/src/main/java/com/android/tools/r8/ir/regalloc/LinearScanRegisterAllocator.java
index 90c6c2b..b66dd62 100644
--- a/src/main/java/com/android/tools/r8/ir/regalloc/LinearScanRegisterAllocator.java
+++ b/src/main/java/com/android/tools/r8/ir/regalloc/LinearScanRegisterAllocator.java
@@ -2122,35 +2122,36 @@
             needsRegisterPair,
             usePositions,
             Type.CONST_NUMBER);
-    if (candidate != Integer.MAX_VALUE) {
-      // Look for a non-const, non-monitor candidate.
-      int otherCandidate =
-          getLargestValidCandidate(
-              unhandledInterval, registerConstraint, needsRegisterPair, usePositions, Type.OTHER);
-      if (otherCandidate == Integer.MAX_VALUE || candidate == REGISTER_CANDIDATE_NOT_FOUND) {
+    // Look for a non-const, non-monitor candidate.
+    int otherCandidate =
+        getLargestValidCandidate(
+            unhandledInterval, registerConstraint, needsRegisterPair, usePositions, Type.OTHER);
+    if (otherCandidate != REGISTER_CANDIDATE_NOT_FOUND) {
+      // There is a potential other candidate, check if that should be used instead.
+      if (candidate == REGISTER_CANDIDATE_NOT_FOUND) {
         candidate = otherCandidate;
       } else {
         int largestConstUsePosition =
             getLargestPosition(usePositions, candidate, needsRegisterPair);
-        if (largestConstUsePosition - MIN_CONSTANT_FREE_FOR_POSITIONS <
-            unhandledInterval.getStart()) {
+        if (largestConstUsePosition - MIN_CONSTANT_FREE_FOR_POSITIONS
+            < unhandledInterval.getStart()) {
           // The candidate that can be rematerialized has a live range too short to use it.
           candidate = otherCandidate;
         }
       }
+    }
 
-      // If looking at constants and non-monitor registers did not find a valid spill candidate
-      // we allow ourselves to look at monitor spill candidates as well. Registers holding objects
-      // used as monitors should not be spilled if we can avoid it. Spilling them can lead
-      // to Art lock verification issues.
-      // Also, at this point we still don't allow splitting any string new-instance instructions
-      // that have been explicitly blocked. Doing so could lead to a behavioral bug on some ART
-      // runtimes (b/80118070). To remove this restriction, we would need to know when the call to
-      // <init> has definitely happened, and would be safe to split the value after that point.
-      if (candidate == REGISTER_CANDIDATE_NOT_FOUND) {
-        candidate = getLargestValidCandidate(
-            unhandledInterval, registerConstraint, needsRegisterPair, usePositions, Type.MONITOR);
-      }
+    // If looking at constants and non-monitor registers did not find a valid spill candidate
+    // we allow ourselves to look at monitor spill candidates as well. Registers holding objects
+    // used as monitors should not be spilled if we can avoid it. Spilling them can lead
+    // to Art lock verification issues.
+    // Also, at this point we still don't allow splitting any string new-instance instructions
+    // that have been explicitly blocked. Doing so could lead to a behavioral bug on some ART
+    // runtimes (b/80118070). To remove this restriction, we would need to know when the call to
+    // <init> has definitely happened, and would be safe to split the value after that point.
+    if (candidate == REGISTER_CANDIDATE_NOT_FOUND) {
+      candidate = getLargestValidCandidate(
+          unhandledInterval, registerConstraint, needsRegisterPair, usePositions, Type.MONITOR);
     }
 
     int largestUsePosition = getLargestPosition(usePositions, candidate, needsRegisterPair);
diff --git a/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java b/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java
index 19febfd..c2bbb70 100644
--- a/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java
+++ b/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java
@@ -513,7 +513,8 @@
         for (int i = 0; i < names.getValues().length; i++) {
           DexValueString name = names.getValues()[i].asDexValueString();
           DexValueInt access = accessFlags.getValues()[i].asDexValueInt();
-          visitor.visitParameter(name.value.toString(), access.value);
+          String nameString = name != null ? name.value.toString() : null;
+          visitor.visitParameter(nameString, access.value);
         }
       }
     }
diff --git a/src/main/java/com/android/tools/r8/lightir/ByteUtils.java b/src/main/java/com/android/tools/r8/lightir/ByteUtils.java
index dc174ff..559685a 100644
--- a/src/main/java/com/android/tools/r8/lightir/ByteUtils.java
+++ b/src/main/java/com/android/tools/r8/lightir/ByteUtils.java
@@ -8,17 +8,20 @@
 /** Simple utilities for byte encodings. */
 public class ByteUtils {
 
+  public static final int MAX_U1 = 0xFF;
+  public static final int MAX_U2 = 0xFFFF;
+
   public static boolean isU1(int value) {
-    return (0 <= value) && (value <= 0xFF);
+    return (0 <= value) && (value <= MAX_U1);
   }
 
   // Lossy truncation of an integer value to its lowest byte.
   private static int truncateToU1(int value) {
-    return value & 0xFF;
+    return value & MAX_U1;
   }
 
   private static int truncateToU1(long value) {
-    return (int) value & 0xFF;
+    return (int) value & MAX_U1;
   }
 
   public static int ensureU1(int value) {
@@ -27,7 +30,7 @@
   }
 
   public static int fromU1(byte value) {
-    return value & 0xFF;
+    return value & MAX_U1;
   }
 
   public static int intEncodingSize(int value) {
@@ -81,11 +84,11 @@
   }
 
   public static boolean isU2(int value) {
-    return (value >= 0) && (value <= 0xFFFF);
+    return (value >= 0) && (value <= MAX_U2);
   }
 
   private static int truncateToU2(int value) {
-    return value & 0xFFFF;
+    return value & MAX_U2;
   }
 
   public static int ensureU2(int value) {
diff --git a/src/main/java/com/android/tools/r8/lightir/Lir2IRConverter.java b/src/main/java/com/android/tools/r8/lightir/Lir2IRConverter.java
index bda771a..421be9b 100644
--- a/src/main/java/com/android/tools/r8/lightir/Lir2IRConverter.java
+++ b/src/main/java/com/android/tools/r8/lightir/Lir2IRConverter.java
@@ -6,8 +6,11 @@
 import com.android.tools.r8.errors.Unimplemented;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexCallSite;
 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;
 import com.android.tools.r8.graph.DexString;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.ProgramMethod;
@@ -15,6 +18,7 @@
 import com.android.tools.r8.ir.analysis.type.PrimitiveTypeElement;
 import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.code.Add;
+import com.android.tools.r8.ir.code.And;
 import com.android.tools.r8.ir.code.Argument;
 import com.android.tools.r8.ir.code.ArrayGet;
 import com.android.tools.r8.ir.code.ArrayLength;
@@ -27,21 +31,27 @@
 import com.android.tools.r8.ir.code.ConstClass;
 import com.android.tools.r8.ir.code.ConstNumber;
 import com.android.tools.r8.ir.code.ConstString;
+import com.android.tools.r8.ir.code.DebugLocalRead;
 import com.android.tools.r8.ir.code.DebugLocalWrite;
 import com.android.tools.r8.ir.code.DebugPosition;
+import com.android.tools.r8.ir.code.DexItemBasedConstString;
 import com.android.tools.r8.ir.code.Div;
 import com.android.tools.r8.ir.code.Goto;
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.If;
 import com.android.tools.r8.ir.code.IfType;
+import com.android.tools.r8.ir.code.InitClass;
 import com.android.tools.r8.ir.code.InstanceGet;
 import com.android.tools.r8.ir.code.InstanceOf;
 import com.android.tools.r8.ir.code.InstancePut;
 import com.android.tools.r8.ir.code.Instruction;
 import com.android.tools.r8.ir.code.IntSwitch;
+import com.android.tools.r8.ir.code.InvokeCustom;
 import com.android.tools.r8.ir.code.InvokeDirect;
 import com.android.tools.r8.ir.code.InvokeInterface;
+import com.android.tools.r8.ir.code.InvokeMultiNewArray;
 import com.android.tools.r8.ir.code.InvokeNewArray;
+import com.android.tools.r8.ir.code.InvokePolymorphic;
 import com.android.tools.r8.ir.code.InvokeStatic;
 import com.android.tools.r8.ir.code.InvokeSuper;
 import com.android.tools.r8.ir.code.InvokeVirtual;
@@ -50,27 +60,35 @@
 import com.android.tools.r8.ir.code.MonitorType;
 import com.android.tools.r8.ir.code.MoveException;
 import com.android.tools.r8.ir.code.Mul;
+import com.android.tools.r8.ir.code.Neg;
 import com.android.tools.r8.ir.code.NewArrayEmpty;
 import com.android.tools.r8.ir.code.NewArrayFilledData;
 import com.android.tools.r8.ir.code.NewInstance;
+import com.android.tools.r8.ir.code.NewUnboxedEnumInstance;
+import com.android.tools.r8.ir.code.Not;
 import com.android.tools.r8.ir.code.NumberConversion;
 import com.android.tools.r8.ir.code.NumberGenerator;
 import com.android.tools.r8.ir.code.NumericType;
+import com.android.tools.r8.ir.code.Or;
 import com.android.tools.r8.ir.code.Phi;
 import com.android.tools.r8.ir.code.Position;
 import com.android.tools.r8.ir.code.Position.SyntheticPosition;
 import com.android.tools.r8.ir.code.Rem;
 import com.android.tools.r8.ir.code.Return;
+import com.android.tools.r8.ir.code.Shl;
+import com.android.tools.r8.ir.code.Shr;
 import com.android.tools.r8.ir.code.StaticGet;
 import com.android.tools.r8.ir.code.StaticPut;
 import com.android.tools.r8.ir.code.Sub;
 import com.android.tools.r8.ir.code.Throw;
+import com.android.tools.r8.ir.code.Ushr;
 import com.android.tools.r8.ir.code.Value;
 import com.android.tools.r8.ir.code.ValueType;
 import com.android.tools.r8.ir.code.Xor;
 import com.android.tools.r8.ir.conversion.MethodConversionOptions.MutableMethodConversionOptions;
 import com.android.tools.r8.lightir.LirBuilder.IntSwitchPayload;
 import com.android.tools.r8.lightir.LirCode.PositionEntry;
+import com.android.tools.r8.naming.dexitembasedstring.NameComputationInfo;
 import com.android.tools.r8.utils.ListUtils;
 import com.google.common.collect.ImmutableList;
 import it.unimi.dsi.fastutil.ints.Int2IntMap;
@@ -148,7 +166,7 @@
       // Control instructions must close the block, thus the current block is null iff the
       // instruction denotes a new block.
       if (currentBlock == null) {
-        currentBlock = blocks.computeIfAbsent(nextInstructionIndex, k -> new BasicBlock());
+        currentBlock = getBasicBlock(nextInstructionIndex);
         CatchHandlers<Integer> handlers =
             code.getTryCatchTable().getHandlersForBlock(nextInstructionIndex);
         if (handlers != null) {
@@ -418,6 +436,48 @@
     }
 
     @Override
+    public void onNeg(NumericType type, EV value) {
+      Value dest = getOutValueForNextInstruction(valueTypeElement(type));
+      addInstruction(new Neg(type, dest, getValue(value)));
+    }
+
+    @Override
+    public void onNot(NumericType type, EV value) {
+      Value dest = getOutValueForNextInstruction(valueTypeElement(type));
+      addInstruction(new Not(type, dest, getValue(value)));
+    }
+
+    @Override
+    public void onShl(NumericType type, EV left, EV right) {
+      Value dest = getOutValueForNextInstruction(valueTypeElement(type));
+      addInstruction(new Shl(type, dest, getValue(left), getValue(right)));
+    }
+
+    @Override
+    public void onShr(NumericType type, EV left, EV right) {
+      Value dest = getOutValueForNextInstruction(valueTypeElement(type));
+      addInstruction(new Shr(type, dest, getValue(left), getValue(right)));
+    }
+
+    @Override
+    public void onUshr(NumericType type, EV left, EV right) {
+      Value dest = getOutValueForNextInstruction(valueTypeElement(type));
+      addInstruction(new Ushr(type, dest, getValue(left), getValue(right)));
+    }
+
+    @Override
+    public void onAnd(NumericType type, EV left, EV right) {
+      Value dest = getOutValueForNextInstruction(valueTypeElement(type));
+      addInstruction(And.createNonNormalized(type, dest, getValue(left), getValue(right)));
+    }
+
+    @Override
+    public void onOr(NumericType type, EV left, EV right) {
+      Value dest = getOutValueForNextInstruction(valueTypeElement(type));
+      addInstruction(Or.createNonNormalized(type, dest, getValue(left), getValue(right)));
+    }
+
+    @Override
     public void onXor(NumericType type, EV left, EV right) {
       Value dest = getOutValueForNextInstruction(valueTypeElement(type));
       addInstruction(Xor.createNonNormalized(type, dest, getValue(left), getValue(right)));
@@ -425,11 +485,22 @@
 
     @Override
     public void onConstString(DexString string) {
-      Value dest = getOutValueForNextInstruction(TypeElement.stringClassType(appView));
+      Value dest =
+          getOutValueForNextInstruction(
+              TypeElement.stringClassType(appView, Nullability.definitelyNotNull()));
       addInstruction(new ConstString(dest, string));
     }
 
     @Override
+    public void onDexItemBasedConstString(
+        DexReference item, NameComputationInfo<?> nameComputationInfo) {
+      Value dest =
+          getOutValueForNextInstruction(
+              TypeElement.stringClassType(appView, Nullability.definitelyNotNull()));
+      addInstruction(new DexItemBasedConstString(dest, item, nameComputationInfo));
+    }
+
+    @Override
     public void onConstClass(DexType type) {
       Value dest =
           getOutValueForNextInstruction(
@@ -553,10 +624,31 @@
       addInstruction(instruction);
     }
 
+    @Override
+    public void onInvokeCustom(DexCallSite callSite, List<EV> arguments) {
+      Value dest = getInvokeInstructionOutputValue(callSite.methodProto);
+      List<Value> ssaArgumentValues = getValues(arguments);
+      InvokeCustom instruction = new InvokeCustom(callSite, dest, ssaArgumentValues);
+      addInstruction(instruction);
+    }
+
+    @Override
+    public void onInvokePolymorphic(DexMethod target, DexProto proto, List<EV> arguments) {
+      Value dest = getInvokeInstructionOutputValue(target);
+      List<Value> ssaArgumentValues = getValues(arguments);
+      InvokePolymorphic instruction = new InvokePolymorphic(target, proto, dest, ssaArgumentValues);
+      addInstruction(instruction);
+    }
+
     private Value getInvokeInstructionOutputValue(DexMethod target) {
-      return target.getReturnType().isVoidType()
+      return getInvokeInstructionOutputValue(target.getProto());
+    }
+
+    private Value getInvokeInstructionOutputValue(DexProto target) {
+      DexType returnType = target.getReturnType();
+      return returnType.isVoidType()
           ? null
-          : getOutValueForNextInstruction(target.getReturnType().toTypeElement(appView));
+          : getOutValueForNextInstruction(returnType.toTypeElement(appView));
     }
 
     @Override
@@ -585,7 +677,8 @@
 
     @Override
     public void onInstancePut(DexField field, EV object, EV value) {
-      addInstruction(new InstancePut(field, getValue(object), getValue(value)));
+      addInstruction(
+          InstancePut.createPotentiallyInvalid(field, getValue(object), getValue(value)));
     }
 
     @Override
@@ -663,6 +756,19 @@
     }
 
     @Override
+    public void onDebugLocalRead() {
+      addInstruction(new DebugLocalRead());
+    }
+
+    @Override
+    public void onInvokeMultiNewArray(DexType type, List<EV> arguments) {
+      Value dest =
+          getOutValueForNextInstruction(
+              type.toTypeElement(appView, Nullability.definitelyNotNull()));
+      addInstruction(new InvokeMultiNewArray(type, dest, getValues(arguments)));
+    }
+
+    @Override
     public void onInvokeNewArray(DexType type, List<EV> arguments) {
       Value dest =
           getOutValueForNextInstruction(
@@ -740,5 +846,18 @@
           ArrayPut.createWithoutVerification(
               type, getValue(array), getValue(index), getValue(value)));
     }
+
+    @Override
+    public void onNewUnboxedEnumInstance(DexType clazz, int ordinal) {
+      TypeElement type = TypeElement.fromDexType(clazz, Nullability.definitelyNotNull(), appView);
+      Value dest = getOutValueForNextInstruction(type);
+      addInstruction(new NewUnboxedEnumInstance(clazz, ordinal, dest));
+    }
+
+    @Override
+    public void onInitClass(DexType clazz) {
+      Value dest = getOutValueForNextInstruction(TypeElement.getInt());
+      addInstruction(new InitClass(dest, clazz));
+    }
   }
 }
diff --git a/src/main/java/com/android/tools/r8/lightir/LirBuilder.java b/src/main/java/com/android/tools/r8/lightir/LirBuilder.java
index 7a88ae2..60df1eb 100644
--- a/src/main/java/com/android/tools/r8/lightir/LirBuilder.java
+++ b/src/main/java/com/android/tools/r8/lightir/LirBuilder.java
@@ -11,10 +11,13 @@
 import com.android.tools.r8.errors.Unimplemented;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.DebugLocalInfo;
+import com.android.tools.r8.graph.DexCallSite;
 import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.graph.DexItem;
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexProto;
+import com.android.tools.r8.graph.DexReference;
 import com.android.tools.r8.graph.DexString;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.ir.analysis.type.TypeElement;
@@ -33,6 +36,7 @@
 import com.android.tools.r8.lightir.LirCode.DebugLocalInfoTable;
 import com.android.tools.r8.lightir.LirCode.PositionEntry;
 import com.android.tools.r8.lightir.LirCode.TryCatchTable;
+import com.android.tools.r8.naming.dexitembasedstring.NameComputationInfo;
 import com.android.tools.r8.utils.ListUtils;
 import com.google.common.collect.ImmutableList;
 import it.unimi.dsi.fastutil.ints.Int2ReferenceMap;
@@ -125,6 +129,14 @@
     }
   }
 
+  public static class NameComputationPayload extends InstructionPayload {
+    public final NameComputationInfo<?> nameComputationInfo;
+
+    public NameComputationPayload(NameComputationInfo<?> nameComputationInfo) {
+      this.nameComputationInfo = nameComputationInfo;
+    }
+  }
+
   public LirBuilder(DexMethod method, LirEncodingStrategy<V, EV> strategy, DexItemFactory factory) {
     this.factory = factory;
     constants = new Reference2IntOpenHashMap<>();
@@ -379,6 +391,48 @@
     return addOneItemInstruction(LirOpcodes.LDC, type);
   }
 
+  public LirBuilder<V, EV> addNeg(NumericType type, V value) {
+    int opcode;
+    switch (type) {
+      case BYTE:
+      case CHAR:
+      case SHORT:
+      case INT:
+        opcode = LirOpcodes.INEG;
+        break;
+      case LONG:
+        opcode = LirOpcodes.LNEG;
+        break;
+      case FLOAT:
+        opcode = LirOpcodes.FNEG;
+        break;
+      case DOUBLE:
+        opcode = LirOpcodes.DNEG;
+        break;
+      default:
+        throw new Unreachable("Unexpected type: " + type);
+    }
+    return addOneValueInstruction(opcode, value);
+  }
+
+  public LirBuilder<V, EV> addNot(NumericType type, V value) {
+    int opcode;
+    switch (type) {
+      case BYTE:
+      case CHAR:
+      case SHORT:
+      case INT:
+        opcode = LirOpcodes.INOT;
+        break;
+      case LONG:
+        opcode = LirOpcodes.LNOT;
+        break;
+      default:
+        throw new Unreachable("Unexpected type: " + type);
+    }
+    return addOneValueInstruction(opcode, value);
+  }
+
   public LirBuilder<V, EV> addDiv(NumericType type, V leftValue, V rightValue) {
     int opcode;
     switch (type) {
@@ -445,7 +499,7 @@
         LirOpcodes.PUTFIELD, Collections.singletonList(field), ImmutableList.of(object, value));
   }
 
-  public LirBuilder<V, EV> addInvokeInstruction(int opcode, DexMethod method, List<V> arguments) {
+  public LirBuilder<V, EV> addInvokeInstruction(int opcode, DexItem method, List<V> arguments) {
     return addInstructionTemplate(opcode, Collections.singletonList(method), arguments);
   }
 
@@ -475,6 +529,16 @@
     return addInvokeInstruction(LirOpcodes.INVOKEINTERFACE, method, arguments);
   }
 
+  public LirBuilder<V, EV> addInvokeCustom(DexCallSite callSite, List<V> arguments) {
+    return addInvokeInstruction(LirOpcodes.INVOKEDYNAMIC, callSite, arguments);
+  }
+
+  public LirBuilder<V, EV> addInvokePolymorphic(
+      DexMethod invokedMethod, DexProto proto, List<V> arguments) {
+    return addInstructionTemplate(
+        LirOpcodes.INVOKEPOLYMORPHIC, ImmutableList.of(invokedMethod, proto), arguments);
+  }
+
   public LirBuilder<V, EV> addNewInstance(DexType clazz) {
     return addOneItemInstruction(LirOpcodes.NEW, clazz);
   }
@@ -613,6 +677,10 @@
     return addOneValueInstruction(LirOpcodes.DEBUGLOCALWRITE, src);
   }
 
+  public LirBuilder<V, EV> addDebugLocalRead() {
+    return addNoOperandInstruction(LirOpcodes.DEBUGLOCALREAD);
+  }
+
   public LirCode<EV> build() {
     assert metadata != null;
     int constantsCount = constants.size();
@@ -679,6 +747,11 @@
         LirOpcodes.NEWARRAY, Collections.singletonList(type), Collections.singletonList(size));
   }
 
+  public LirBuilder<V, EV> addInvokeMultiNewArray(DexType type, List<V> arguments) {
+    return addInstructionTemplate(
+        LirOpcodes.MULTIANEWARRAY, Collections.singletonList(type), arguments);
+  }
+
   public LirBuilder<V, EV> addInvokeNewArray(DexType type, List<V> arguments) {
     return addInstructionTemplate(
         LirOpcodes.INVOKENEWARRAY, Collections.singletonList(type), arguments);
@@ -767,4 +840,24 @@
     return addInstructionTemplate(
         opcode, Collections.emptyList(), ImmutableList.of(array, index, value));
   }
+
+  public LirBuilder<V, EV> addDexItemBasedConstString(
+      DexReference item, NameComputationInfo<?> nameComputationInfo) {
+    NameComputationPayload payload = new NameComputationPayload(nameComputationInfo);
+    return addInstructionTemplate(
+        LirOpcodes.ITEMBASEDCONSTSTRING, ImmutableList.of(item, payload), Collections.emptyList());
+  }
+
+  public LirBuilder<V, EV> addNewUnboxedEnumInstance(DexType clazz, int ordinal) {
+    advanceInstructionState();
+    int operandSize = constantIndexSize(clazz) + ByteUtils.intEncodingSize(ordinal);
+    writer.writeInstruction(LirOpcodes.NEWUNBOXEDENUMINSTANCE, operandSize);
+    writeConstantIndex(clazz);
+    ByteUtils.writeEncodedInt(ordinal, writer::writeOperand);
+    return this;
+  }
+
+  public LirBuilder<V, EV> addInitClass(DexType clazz) {
+    return addOneItemInstruction(LirOpcodes.INITCLASS, clazz);
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/lightir/LirIterator.java b/src/main/java/com/android/tools/r8/lightir/LirIterator.java
index 84eaee6..0d9ccbd 100644
--- a/src/main/java/com/android/tools/r8/lightir/LirIterator.java
+++ b/src/main/java/com/android/tools/r8/lightir/LirIterator.java
@@ -51,6 +51,10 @@
       // Any instruction that is not a single byte has a two-byte header. The second byte is the
       // size of the variable width operand payload.
       int operandSize = u1();
+      if (operandSize == 0) {
+        // Zero is used to indicate the operand size is larger than a u1 encoded value.
+        operandSize = u4();
+      }
       endOfCurrentInstruction = currentByteIndex + operandSize;
     }
     return this;
diff --git a/src/main/java/com/android/tools/r8/lightir/LirOpcodes.java b/src/main/java/com/android/tools/r8/lightir/LirOpcodes.java
index c4f2076..0f0811d 100644
--- a/src/main/java/com/android/tools/r8/lightir/LirOpcodes.java
+++ b/src/main/java/com/android/tools/r8/lightir/LirOpcodes.java
@@ -14,7 +14,11 @@
 
   static boolean isOneByteInstruction(int opcode) {
     assert opcode >= ACONST_NULL;
-    return opcode <= DCONST_1 || opcode == RETURN || opcode == DEBUGPOS || opcode == FALLTHROUGH;
+    return opcode <= DCONST_1
+        || opcode == RETURN
+        || opcode == DEBUGPOS
+        || opcode == FALLTHROUGH
+        || opcode == DEBUGLOCALREAD;
   }
 
   // Instructions maintaining the same opcode as defined in CF.
@@ -196,6 +200,13 @@
   int DEBUGLOCALWRITE = 213;
   int INVOKENEWARRAY = 214;
   int NEWARRAYFILLEDDATA = 215;
+  int ITEMBASEDCONSTSTRING = 216;
+  int NEWUNBOXEDENUMINSTANCE = 217;
+  int INOT = 218;
+  int LNOT = 219;
+  int DEBUGLOCALREAD = 220;
+  int INITCLASS = 221;
+  int INVOKEPOLYMORPHIC = 222;
 
   static String toString(int opcode) {
     switch (opcode) {
@@ -512,6 +523,20 @@
         return "INVOKENEWARRAY";
       case NEWARRAYFILLEDDATA:
         return "NEWARRAYFILLEDDATA";
+      case ITEMBASEDCONSTSTRING:
+        return "ITEMBASEDCONSTSTRING";
+      case NEWUNBOXEDENUMINSTANCE:
+        return "NEWUNBOXEDENUMINSTANCE";
+      case INOT:
+        return "INOT";
+      case LNOT:
+        return "LNOT";
+      case DEBUGLOCALREAD:
+        return "DEBUGLOCALREAD";
+      case INITCLASS:
+        return "INITCLASS";
+      case INVOKEPOLYMORPHIC:
+        return "INVOKEPOLYMORPHIC";
 
       default:
         throw new Unreachable("Unexpected LIR opcode: " + opcode);
diff --git a/src/main/java/com/android/tools/r8/lightir/LirParsedInstructionCallback.java b/src/main/java/com/android/tools/r8/lightir/LirParsedInstructionCallback.java
index d418d96..781db23 100644
--- a/src/main/java/com/android/tools/r8/lightir/LirParsedInstructionCallback.java
+++ b/src/main/java/com/android/tools/r8/lightir/LirParsedInstructionCallback.java
@@ -6,9 +6,12 @@
 import com.android.tools.r8.cf.code.CfNumberConversion;
 import com.android.tools.r8.errors.Unimplemented;
 import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.graph.DexCallSite;
 import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.graph.DexItem;
 import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexProto;
+import com.android.tools.r8.graph.DexReference;
 import com.android.tools.r8.graph.DexString;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.ir.code.IfType;
@@ -16,6 +19,8 @@
 import com.android.tools.r8.ir.code.NumericType;
 import com.android.tools.r8.lightir.LirBuilder.FillArrayPayload;
 import com.android.tools.r8.lightir.LirBuilder.IntSwitchPayload;
+import com.android.tools.r8.lightir.LirBuilder.NameComputationPayload;
+import com.android.tools.r8.naming.dexitembasedstring.NameComputationInfo;
 import java.util.ArrayList;
 import java.util.List;
 
@@ -87,6 +92,11 @@
     onInstruction();
   }
 
+  public void onDexItemBasedConstString(
+      DexReference item, NameComputationInfo<?> nameComputationInfo) {
+    onInstruction();
+  }
+
   public void onConstClass(DexType type) {
     onInstruction();
   }
@@ -123,10 +133,18 @@
     onInstruction();
   }
 
-  public void onAdd(NumericType type, EV leftValueIndex, EV rightValueIndex) {
+  public void onBinop(NumericType type, EV left, EV right) {
     onInstruction();
   }
 
+  public void onArithmeticBinop(NumericType type, EV left, EV right) {
+    onBinop(type, left, right);
+  }
+
+  public void onAdd(NumericType type, EV leftValueIndex, EV rightValueIndex) {
+    onArithmeticBinop(type, leftValueIndex, rightValueIndex);
+  }
+
   public void onAddInt(EV leftValueIndex, EV rightValueIndex) {
     onAdd(NumericType.INT, leftValueIndex, rightValueIndex);
   }
@@ -144,7 +162,7 @@
   }
 
   public void onSub(NumericType type, EV leftValueIndex, EV rightValueIndex) {
-    onInstruction();
+    onArithmeticBinop(type, leftValueIndex, rightValueIndex);
   }
 
   public void onSubInt(EV leftValueIndex, EV rightValueIndex) {
@@ -164,7 +182,7 @@
   }
 
   public void onMul(NumericType type, EV leftValueIndex, EV rightValueIndex) {
-    onInstruction();
+    onArithmeticBinop(type, leftValueIndex, rightValueIndex);
   }
 
   public void onMulInt(EV leftValueIndex, EV rightValueIndex) {
@@ -184,7 +202,7 @@
   }
 
   public void onDiv(NumericType type, EV leftValueIndex, EV rightValueIndex) {
-    onInstruction();
+    onArithmeticBinop(type, leftValueIndex, rightValueIndex);
   }
 
   public void onDivInt(EV leftValueIndex, EV rightValueIndex) {
@@ -204,7 +222,7 @@
   }
 
   public void onRem(NumericType type, EV leftValueIndex, EV rightValueIndex) {
-    onInstruction();
+    onArithmeticBinop(type, leftValueIndex, rightValueIndex);
   }
 
   public void onRemInt(EV leftValueIndex, EV rightValueIndex) {
@@ -223,34 +241,85 @@
     onRem(NumericType.DOUBLE, leftValueIndex, rightValueIndex);
   }
 
+  public void onNeg(NumericType type, EV value) {
+    onInstruction();
+  }
+
+  public void onNot(NumericType type, EV value) {
+    onInstruction();
+  }
+
   private void onLogicalBinopInternal(int opcode, LirInstructionView view) {
     EV left = getNextValueOperand(view);
     EV right = getNextValueOperand(view);
     switch (opcode) {
       case LirOpcodes.ISHL:
+        onShl(NumericType.INT, left, right);
+        return;
       case LirOpcodes.LSHL:
+        onShl(NumericType.LONG, left, right);
+        return;
       case LirOpcodes.ISHR:
+        onShr(NumericType.INT, left, right);
+        return;
       case LirOpcodes.LSHR:
+        onShr(NumericType.LONG, left, right);
+        return;
       case LirOpcodes.IUSHR:
+        onUshr(NumericType.INT, left, right);
+        return;
       case LirOpcodes.LUSHR:
+        onUshr(NumericType.LONG, left, right);
+        return;
       case LirOpcodes.IAND:
+        onAnd(NumericType.INT, left, right);
+        return;
       case LirOpcodes.LAND:
+        onAnd(NumericType.LONG, left, right);
+        return;
       case LirOpcodes.IOR:
+        onOr(NumericType.INT, left, right);
+        return;
       case LirOpcodes.LOR:
-        throw new Unimplemented();
+        onOr(NumericType.LONG, left, right);
+        return;
       case LirOpcodes.IXOR:
         onXor(NumericType.INT, left, right);
         return;
       case LirOpcodes.LXOR:
-        onXor(NumericType.INT, left, right);
+        onXor(NumericType.LONG, left, right);
         return;
       default:
         throw new Unreachable("Unexpected logical binop: " + opcode);
     }
   }
 
+  public void onLogicalBinop(NumericType type, EV left, EV right) {
+    onBinop(type, left, right);
+  }
+
+  public void onShl(NumericType type, EV left, EV right) {
+    onLogicalBinop(type, left, right);
+  }
+
+  public void onShr(NumericType type, EV left, EV right) {
+    onLogicalBinop(type, left, right);
+  }
+
+  public void onUshr(NumericType type, EV left, EV right) {
+    onLogicalBinop(type, left, right);
+  }
+
+  public void onAnd(NumericType type, EV left, EV right) {
+    onLogicalBinop(type, left, right);
+  }
+
+  public void onOr(NumericType type, EV left, EV right) {
+    onLogicalBinop(type, left, right);
+  }
+
   public void onXor(NumericType type, EV left, EV right) {
-    onInstruction();
+    onLogicalBinop(type, left, right);
   }
 
   public void onNumberConversion(int opcode, EV value) {
@@ -305,6 +374,14 @@
     onInstruction();
   }
 
+  public void onDebugLocalRead() {
+    onInstruction();
+  }
+
+  public void onInvokeMultiNewArray(DexType type, List<EV> arguments) {
+    onInstruction();
+  }
+
   public void onInvokeNewArray(DexType type, List<EV> arguments) {
     onInstruction();
   }
@@ -337,6 +414,14 @@
     onInvokeMethodInstruction(method, arguments);
   }
 
+  public void onInvokeCustom(DexCallSite callSite, List<EV> arguments) {
+    onInstruction();
+  }
+
+  public void onInvokePolymorphic(DexMethod target, DexProto proto, List<EV> arguments) {
+    onInstruction();
+  }
+
   public void onNewInstance(DexType clazz) {
     onInstruction();
   }
@@ -398,6 +483,14 @@
 
   public abstract void onMonitorExit(EV value);
 
+  public void onNewUnboxedEnumInstance(DexType type, int ordinal) {
+    onInstruction();
+  }
+
+  public void onInitClass(DexType clazz) {
+    onInstruction();
+  }
+
   private DexItem getConstantItem(int index) {
     return code.getConstantItem(index);
   }
@@ -714,6 +807,30 @@
           onRemDouble(leftValueIndex, rightValueIndex);
           return;
         }
+      case LirOpcodes.INEG:
+        {
+          EV value = getNextValueOperand(view);
+          onNeg(NumericType.INT, value);
+          return;
+        }
+      case LirOpcodes.LNEG:
+        {
+          EV value = getNextValueOperand(view);
+          onNeg(NumericType.LONG, value);
+          return;
+        }
+      case LirOpcodes.FNEG:
+        {
+          EV value = getNextValueOperand(view);
+          onNeg(NumericType.FLOAT, value);
+          return;
+        }
+      case LirOpcodes.DNEG:
+        {
+          EV value = getNextValueOperand(view);
+          onNeg(NumericType.DOUBLE, value);
+          return;
+        }
       case LirOpcodes.ISHL:
       case LirOpcodes.LSHL:
       case LirOpcodes.ISHR:
@@ -882,6 +999,21 @@
           onInvokeInterface(target, arguments);
           return;
         }
+      case LirOpcodes.INVOKEDYNAMIC:
+        {
+          DexCallSite callSite = (DexCallSite) getConstantItem(view.getNextConstantOperand());
+          List<EV> arguments = getInvokeInstructionArguments(view);
+          onInvokeCustom(callSite, arguments);
+          return;
+        }
+      case LirOpcodes.INVOKEPOLYMORPHIC:
+        {
+          DexMethod target = getInvokeInstructionTarget(view);
+          DexProto proto = (DexProto) getConstantItem(view.getNextConstantOperand());
+          List<EV> arguments = getInvokeInstructionArguments(view);
+          onInvokePolymorphic(target, proto, arguments);
+          return;
+        }
       case LirOpcodes.NEW:
         {
           DexItem item = getConstantItem(view.getNextConstantOperand());
@@ -1006,6 +1138,18 @@
           onDebugLocalWrite(srcIndex);
           return;
         }
+      case LirOpcodes.DEBUGLOCALREAD:
+        {
+          onDebugLocalRead();
+          return;
+        }
+      case LirOpcodes.MULTIANEWARRAY:
+        {
+          DexType type = getNextDexTypeOperand(view);
+          List<EV> arguments = getInvokeInstructionArguments(view);
+          onInvokeMultiNewArray(type, arguments);
+          return;
+        }
       case LirOpcodes.INVOKENEWARRAY:
         {
           DexType type = getNextDexTypeOperand(view);
@@ -1032,6 +1176,34 @@
           onCmpInstruction(opcode, leftValue, rightValue);
           return;
         }
+      case LirOpcodes.ITEMBASEDCONSTSTRING:
+        {
+          DexReference item = (DexReference) getConstantItem(view.getNextConstantOperand());
+          NameComputationPayload payload =
+              (NameComputationPayload) getConstantItem(view.getNextConstantOperand());
+          onDexItemBasedConstString(item, payload.nameComputationInfo);
+          return;
+        }
+      case LirOpcodes.NEWUNBOXEDENUMINSTANCE:
+        {
+          DexType type = getNextDexTypeOperand(view);
+          int ordinal = view.getNextIntegerOperand();
+          onNewUnboxedEnumInstance(type, ordinal);
+          return;
+        }
+      case LirOpcodes.INOT:
+      case LirOpcodes.LNOT:
+        {
+          EV value = getNextValueOperand(view);
+          onNot(opcode == LirOpcodes.INOT ? NumericType.INT : NumericType.LONG, value);
+          return;
+        }
+      case LirOpcodes.INITCLASS:
+        {
+          DexType clazz = getNextDexTypeOperand(view);
+          onInitClass(clazz);
+          return;
+        }
       default:
         throw new Unimplemented("No dispatch for opcode " + LirOpcodes.toString(opcode));
     }
diff --git a/src/main/java/com/android/tools/r8/lightir/LirPrinter.java b/src/main/java/com/android/tools/r8/lightir/LirPrinter.java
index e19661e..b20e303 100644
--- a/src/main/java/com/android/tools/r8/lightir/LirPrinter.java
+++ b/src/main/java/com/android/tools/r8/lightir/LirPrinter.java
@@ -4,14 +4,18 @@
 package com.android.tools.r8.lightir;
 
 import com.android.tools.r8.errors.Unimplemented;
+import com.android.tools.r8.graph.DexCallSite;
 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;
 import com.android.tools.r8.graph.DexString;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.ir.code.IfType;
 import com.android.tools.r8.ir.code.MemberType;
 import com.android.tools.r8.ir.code.NumericType;
 import com.android.tools.r8.lightir.LirBuilder.IntSwitchPayload;
+import com.android.tools.r8.naming.dexitembasedstring.NameComputationInfo;
 import com.android.tools.r8.utils.StringUtils;
 import java.util.Arrays;
 import java.util.List;
@@ -151,32 +155,32 @@
   }
 
   @Override
+  public void onDexItemBasedConstString(
+      DexReference item, NameComputationInfo<?> nameComputationInfo) {
+    appendOutValue().append("item(").append(item).append(")");
+  }
+
+  @Override
   public void onConstClass(DexType type) {
     appendOutValue().append("class(").append(type).append(")");
   }
 
   @Override
-  public void onAdd(NumericType type, EV leftValueIndex, EV rightValueIndex) {
+  public void onBinop(NumericType type, EV left, EV right) {
     appendOutValue();
-    appendValueArguments(leftValueIndex, rightValueIndex);
+    appendValueArguments(left, right);
   }
 
   @Override
-  public void onSub(NumericType type, EV leftValueIndex, EV rightValueIndex) {
+  public void onNeg(NumericType type, EV value) {
     appendOutValue();
-    appendValueArguments(leftValueIndex, rightValueIndex);
+    appendValueArguments(value);
   }
 
   @Override
-  public void onDiv(NumericType type, EV leftValueIndex, EV rightValueIndex) {
+  public void onNot(NumericType type, EV value) {
     appendOutValue();
-    appendValueArguments(leftValueIndex, rightValueIndex);
-  }
-
-  @Override
-  public void onXor(NumericType type, EV leftValueIndex, EV rightValueIndex) {
-    appendOutValue();
-    appendValueArguments(leftValueIndex, rightValueIndex);
+    appendValueArguments(value);
   }
 
   @Override
@@ -224,6 +228,18 @@
   }
 
   @Override
+  public void onDebugLocalRead() {
+    // Nothing to add.
+  }
+
+  @Override
+  public void onInvokeMultiNewArray(DexType type, List<EV> arguments) {
+    appendOutValue();
+    appendValueArguments(arguments);
+    builder.append(type);
+  }
+
+  @Override
   public void onInvokeNewArray(DexType type, List<EV> arguments) {
     appendOutValue();
     appendValueArguments(arguments);
@@ -252,6 +268,18 @@
   }
 
   @Override
+  public void onInvokeCustom(DexCallSite callSite, List<EV> arguments) {
+    appendValueArguments(arguments);
+    builder.append(callSite);
+  }
+
+  @Override
+  public void onInvokePolymorphic(DexMethod target, DexProto proto, List<EV> arguments) {
+    appendValueArguments(arguments);
+    builder.append(target).append(' ').append(proto);
+  }
+
+  @Override
   public void onStaticGet(DexField field) {
     appendOutValue();
     builder.append(field).append(' ');
@@ -364,4 +392,14 @@
   public void onMonitorExit(EV value) {
     appendValueArguments(value);
   }
+
+  @Override
+  public void onNewUnboxedEnumInstance(DexType type, int ordinal) {
+    appendOutValue().append("type(").append(type).append(") ordinal(").append(ordinal).append(")");
+  }
+
+  @Override
+  public void onInitClass(DexType clazz) {
+    builder.append(clazz);
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/lightir/LirWriter.java b/src/main/java/com/android/tools/r8/lightir/LirWriter.java
index 3f9e6b4..b7e4565 100644
--- a/src/main/java/com/android/tools/r8/lightir/LirWriter.java
+++ b/src/main/java/com/android/tools/r8/lightir/LirWriter.java
@@ -25,9 +25,15 @@
   }
 
   public void writeInstruction(int opcode, int operandsSizeInBytes) {
+    assert operandsSizeInBytes > 0;
     assert pendingOperandBytes == 0;
     writer.put(ByteUtils.ensureU1(opcode));
-    writer.put(ByteUtils.ensureU1(operandsSizeInBytes));
+    if (operandsSizeInBytes <= ByteUtils.MAX_U1) {
+      writer.put(ByteUtils.ensureU1(operandsSizeInBytes));
+    } else {
+      writer.put(0);
+      ByteUtils.writeEncodedInt(operandsSizeInBytes, writer);
+    }
     pendingOperandBytes = operandsSizeInBytes;
   }
 
diff --git a/src/main/java/com/android/tools/r8/naming/ClassNameMapper.java b/src/main/java/com/android/tools/r8/naming/ClassNameMapper.java
index cfd7b32..ad33448 100644
--- a/src/main/java/com/android/tools/r8/naming/ClassNameMapper.java
+++ b/src/main/java/com/android/tools/r8/naming/ClassNameMapper.java
@@ -18,6 +18,7 @@
 import com.android.tools.r8.position.Position;
 import com.android.tools.r8.utils.BiMapContainer;
 import com.android.tools.r8.utils.ChainableStringConsumer;
+import com.android.tools.r8.utils.DescriptorUtils;
 import com.android.tools.r8.utils.Reporter;
 import com.google.common.collect.BiMap;
 import com.google.common.collect.ImmutableBiMap;
@@ -32,6 +33,7 @@
 import java.util.Collections;
 import java.util.Comparator;
 import java.util.HashMap;
+import java.util.HashSet;
 import java.util.Iterator;
 import java.util.LinkedHashSet;
 import java.util.List;
@@ -247,6 +249,14 @@
     return preamble;
   }
 
+  public Set<String> getObfuscatedPackages() {
+    Set<String> packages = new HashSet<>();
+    classNameMappings.forEach(
+        (s, classNamingForNameMapper) ->
+            packages.add(DescriptorUtils.getPackageNameFromTypeName(s)));
+    return packages;
+  }
+
   public void setPreamble(List<String> preamble) {
     this.preamble = preamble;
   }
diff --git a/src/main/java/com/android/tools/r8/naming/IdentifierMinifier.java b/src/main/java/com/android/tools/r8/naming/IdentifierMinifier.java
index 0da73cf..43c2c1c 100644
--- a/src/main/java/com/android/tools/r8/naming/IdentifierMinifier.java
+++ b/src/main/java/com/android/tools/r8/naming/IdentifierMinifier.java
@@ -163,6 +163,7 @@
     assert code != null;
     if (code.isDexCode()) {
       DexInstruction[] instructions = code.asDexCode().instructions;
+      boolean updated = false;
       for (int i = 0; i < instructions.length; ++i) {
         DexInstruction instruction = instructions[i];
         if (instruction.isDexItemBasedConstString()) {
@@ -173,8 +174,12 @@
           DexConstString constString = new DexConstString(cnst.AA, replacement);
           constString.setOffset(instruction.getOffset());
           instructions[i] = constString;
+          updated = true;
         }
       }
+      if (updated) {
+        code.asDexCode().flushCachedValues();
+      }
     } else if (code.isCfCode()) {
       List<CfInstruction> instructions = code.asCfCode().getInstructions();
       List<CfInstruction> newInstructions =
diff --git a/src/main/java/com/android/tools/r8/optimize/AccessModifier.java b/src/main/java/com/android/tools/r8/optimize/AccessModifier.java
index 85533d5..43d46ea 100644
--- a/src/main/java/com/android/tools/r8/optimize/AccessModifier.java
+++ b/src/main/java/com/android/tools/r8/optimize/AccessModifier.java
@@ -15,7 +15,6 @@
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.FieldAccessFlags;
-import com.android.tools.r8.graph.FieldAccessInfo;
 import com.android.tools.r8.graph.InnerClassAttribute;
 import com.android.tools.r8.graph.MethodAccessFlags;
 import com.android.tools.r8.graph.ProgramDefinition;
@@ -134,29 +133,10 @@
 
   private void processField(ProgramField field) {
     if (appView.appInfo().isAccessModificationAllowed(field)) {
-      finalizeField(field);
       publicizeField(field);
     }
   }
 
-  private void finalizeField(ProgramField field) {
-    FieldAccessFlags flags = field.getAccessFlags();
-    FieldAccessInfo accessInfo =
-        appView.appInfo().getFieldAccessInfoCollection().get(field.getReference());
-    if (!appView.getKeepInfo(field).isPinned(options)
-        && !accessInfo.hasReflectiveWrite()
-        && !accessInfo.isWrittenFromMethodHandle()
-        && accessInfo.isWrittenOnlyInMethodSatisfying(
-            method ->
-                method.getDefinition().isInitializer()
-                    && method.getAccessFlags().isStatic() == flags.isStatic()
-                    && method.getHolder() == field.getHolder())
-        && !flags.isFinal()
-        && !flags.isVolatile()) {
-      flags.promoteToFinal();
-    }
-  }
-
   private void publicizeField(ProgramField field) {
     FieldAccessFlags flags = field.getAccessFlags();
     if (!flags.isPublic()) {
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagator.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagator.java
index 00be161..2d47dcc 100644
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagator.java
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagator.java
@@ -13,6 +13,7 @@
 import com.android.tools.r8.ir.conversion.IRConverter;
 import com.android.tools.r8.ir.conversion.MethodProcessor;
 import com.android.tools.r8.ir.conversion.PostMethodProcessor;
+import com.android.tools.r8.ir.conversion.PrimaryR8IRConverter;
 import com.android.tools.r8.optimize.argumentpropagation.codescanner.MethodState;
 import com.android.tools.r8.optimize.argumentpropagation.codescanner.MethodStateCollectionByReference;
 import com.android.tools.r8.optimize.argumentpropagation.codescanner.VirtualRootMethodsAnalysis;
@@ -139,7 +140,7 @@
   }
 
   public void tearDownCodeScanner(
-      IRConverter converter,
+      PrimaryR8IRConverter converter,
       PostMethodProcessor.Builder postMethodProcessorBuilder,
       ExecutorService executorService,
       Timing timing)
@@ -205,7 +206,7 @@
    * optimization info.
    */
   private void populateParameterOptimizationInfo(
-      IRConverter converter,
+      PrimaryR8IRConverter converter,
       ImmediateProgramSubtypingInfo immediateSubtypingInfo,
       List<Set<DexProgramClass>> stronglyConnectedProgramComponents,
       BiConsumer<Set<DexProgramClass>, DexMethodSignature> interfaceDispatchOutsideProgram,
@@ -250,15 +251,17 @@
    * <p>Therefore, we assert that we only find a method state for direct methods.
    */
   public void onMethodPruned(ProgramMethod method) {
-    assert codeScanner != null;
-    MethodState methodState = codeScanner.getMethodStates().removeOrElse(method, null);
-    assert methodState == null || method.getDefinition().belongsToDirectPool();
+    if (codeScanner != null) {
+      MethodState methodState = codeScanner.getMethodStates().removeOrElse(method, null);
+      assert methodState == null || method.getDefinition().belongsToDirectPool();
+    }
 
     assert effectivelyUnusedArgumentsAnalysis != null;
     effectivelyUnusedArgumentsAnalysis.onMethodPruned(method);
   }
 
   public void onMethodCodePruned(ProgramMethod method) {
-    // Intentionally empty.
+    assert effectivelyUnusedArgumentsAnalysis != null;
+    effectivelyUnusedArgumentsAnalysis.onMethodCodePruned(method);
   }
 }
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorOptimizationInfoPopulator.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorOptimizationInfoPopulator.java
index 0b47a5e..f4a7af2 100644
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorOptimizationInfoPopulator.java
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorOptimizationInfoPopulator.java
@@ -15,8 +15,8 @@
 import com.android.tools.r8.ir.analysis.type.DynamicType;
 import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.analysis.value.AbstractValue;
-import com.android.tools.r8.ir.conversion.IRConverter;
 import com.android.tools.r8.ir.conversion.PostMethodProcessor;
+import com.android.tools.r8.ir.conversion.PrimaryR8IRConverter;
 import com.android.tools.r8.ir.optimize.info.ConcreteCallSiteOptimizationInfo;
 import com.android.tools.r8.ir.optimize.info.MethodOptimizationInfo;
 import com.android.tools.r8.ir.optimize.info.OptimizationFeedback;
@@ -38,6 +38,7 @@
 import com.android.tools.r8.utils.OptionalBool;
 import com.android.tools.r8.utils.ThreadUtils;
 import com.android.tools.r8.utils.Timing;
+import com.android.tools.r8.utils.collections.ProgramMethodSet;
 import java.util.List;
 import java.util.Set;
 import java.util.concurrent.ExecutionException;
@@ -52,7 +53,7 @@
 public class ArgumentPropagatorOptimizationInfoPopulator {
 
   private final AppView<AppInfoWithLiveness> appView;
-  private final IRConverter converter;
+  private final PrimaryR8IRConverter converter;
   private final MethodStateCollectionByReference methodStates;
   private final InternalOptions options;
   private final PostMethodProcessor.Builder postMethodProcessorBuilder;
@@ -65,7 +66,7 @@
 
   ArgumentPropagatorOptimizationInfoPopulator(
       AppView<AppInfoWithLiveness> appView,
-      IRConverter converter,
+      PrimaryR8IRConverter converter,
       ImmediateProgramSubtypingInfo immediateSubtypingInfo,
       MethodStateCollectionByReference methodStates,
       PostMethodProcessor.Builder postMethodProcessorBuilder,
@@ -148,18 +149,42 @@
   }
 
   private void setOptimizationInfo(ExecutorService executorService) throws ExecutionException {
+    ProgramMethodSet prunedMethods = ProgramMethodSet.createConcurrent();
     ThreadUtils.processItems(
-        appView.appInfo().classes(), this::setOptimizationInfo, executorService);
+        appView.appInfo().classes(),
+        clazz -> prunedMethods.addAll(setOptimizationInfo(clazz)),
+        executorService);
+    for (ProgramMethod prunedMethod : prunedMethods) {
+      converter.onMethodPruned(prunedMethod);
+      postMethodProcessorBuilder.remove(prunedMethod, appView.graphLens());
+    }
+    converter.waveDone(ProgramMethodSet.empty(), executorService);
   }
 
-  private void setOptimizationInfo(DexProgramClass clazz) {
-    clazz.forEachProgramMethod(this::setOptimizationInfo);
+  private ProgramMethodSet setOptimizationInfo(DexProgramClass clazz) {
+    ProgramMethodSet prunedMethods = ProgramMethodSet.create();
+    clazz.forEachProgramMethod(method -> setOptimizationInfo(method, prunedMethods));
+    clazz.getMethodCollection().removeMethods(prunedMethods.toDefinitionSet());
+    return prunedMethods;
   }
 
-  private void setOptimizationInfo(ProgramMethod method) {
+  private void setOptimizationInfo(ProgramMethod method, ProgramMethodSet prunedMethods) {
     MethodState methodState = methodStates.remove(method);
     if (methodState.isBottom()) {
-      if (method.getDefinition().hasCode() && !method.getDefinition().isClassInitializer()) {
+      if (method.getDefinition().isClassInitializer()) {
+        return;
+      }
+      // If all uses of a direct method have been removed, we can remove the method. However, if its
+      // return value has been propagated, then we retain it for correct evaluation of -if rules in
+      // the final round of tree shaking.
+      // TODO(b/203188583): Enable pruning of methods with generic signatures. For this to
+      //  work we need to pass in a seed to GenericSignatureContextBuilder.create in R8.
+      if (method.getDefinition().belongsToDirectPool()
+          && !method.getOptimizationInfo().returnValueHasBeenPropagated()
+          && !method.getDefinition().getGenericSignature().hasSignature()
+          && !appView.appInfo().isFailedResolutionTarget(method.getReference())) {
+        prunedMethods.add(method);
+      } else if (method.getDefinition().hasCode()) {
         method.convertToAbstractOrThrowNullMethod(appView);
         converter.onMethodCodePruned(method);
         postMethodProcessorBuilder.remove(method, appView.graphLens());
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/lenscoderewriter/NullCheckInserter.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/lenscoderewriter/NullCheckInserter.java
index 468b735..6fa0cbc 100644
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/lenscoderewriter/NullCheckInserter.java
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/lenscoderewriter/NullCheckInserter.java
@@ -199,7 +199,7 @@
         DexField rewrittenField = appView.graphLens().lookupField(field, graphLens);
         FieldResolutionResult resolutionResult = appView.appInfo().resolveField(rewrittenField);
         return resolutionResult.isSingleFieldResolutionResult()
-            && !appView.appInfo().isFieldRead(resolutionResult.getResolvedField());
+            && !appView.appInfo().isFieldRead(resolutionResult.getResolutionPair());
       }
       return false;
     }
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/unusedarguments/EffectivelyUnusedArgumentsAnalysis.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/unusedarguments/EffectivelyUnusedArgumentsAnalysis.java
index 2bc10c5..c7472d7 100644
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/unusedarguments/EffectivelyUnusedArgumentsAnalysis.java
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/unusedarguments/EffectivelyUnusedArgumentsAnalysis.java
@@ -217,6 +217,10 @@
   }
 
   public void onMethodPruned(ProgramMethod method) {
+    onMethodCodePruned(method);
+  }
+
+  public void onMethodCodePruned(ProgramMethod method) {
     for (int argumentIndex = 0;
         argumentIndex < method.getDefinition().getNumberOfArguments();
         argumentIndex++) {
diff --git a/src/main/java/com/android/tools/r8/optimize/fields/FieldFinalizer.java b/src/main/java/com/android/tools/r8/optimize/fields/FieldFinalizer.java
new file mode 100644
index 0000000..9b4bec4
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/optimize/fields/FieldFinalizer.java
@@ -0,0 +1,58 @@
+// Copyright (c) 2023, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.optimize.fields;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.FieldAccessFlags;
+import com.android.tools.r8.graph.ProgramField;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.utils.ThreadUtils;
+import com.android.tools.r8.utils.Timing;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
+
+/**
+ * Sets the final flag of fields that are only assigned inside the instance initializers of its
+ * holder class.
+ */
+public class FieldFinalizer {
+
+  private final AppView<AppInfoWithLiveness> appView;
+
+  private FieldFinalizer(AppView<AppInfoWithLiveness> appView) {
+    this.appView = appView;
+  }
+
+  public static void run(
+      AppView<AppInfoWithLiveness> appView, ExecutorService executorService, Timing timing)
+      throws ExecutionException {
+    timing.time("Finalize fields pass", () -> run(appView, executorService));
+  }
+
+  private static void run(AppView<AppInfoWithLiveness> appView, ExecutorService executorService)
+      throws ExecutionException {
+    if (appView.options().isAccessModificationEnabled()
+        && appView.options().isOptimizing()
+        && appView.options().isShrinking()) {
+      new FieldFinalizer(appView).processClasses(executorService);
+    }
+  }
+
+  private void processClasses(ExecutorService executorService) throws ExecutionException {
+    ThreadUtils.processItems(appView.appInfo().classes(), this::processClass, executorService);
+  }
+
+  private void processClass(DexProgramClass clazz) {
+    clazz.forEachProgramField(this::processField);
+  }
+
+  private void processField(ProgramField field) {
+    FieldAccessFlags accessFlags = field.getAccessFlags();
+    if (!accessFlags.isFinal() && !accessFlags.isVolatile() && field.isEffectivelyFinal(appView)) {
+      accessFlags.promoteToFinal();
+    }
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/optimize/redundantbridgeremoval/RedundantBridgeRemovalOptions.java b/src/main/java/com/android/tools/r8/optimize/redundantbridgeremoval/RedundantBridgeRemovalOptions.java
index df58e45..0ca1265 100644
--- a/src/main/java/com/android/tools/r8/optimize/redundantbridgeremoval/RedundantBridgeRemovalOptions.java
+++ b/src/main/java/com/android/tools/r8/optimize/redundantbridgeremoval/RedundantBridgeRemovalOptions.java
@@ -4,42 +4,9 @@
 
 package com.android.tools.r8.optimize.redundantbridgeremoval;
 
-import com.android.tools.r8.graph.DexItemFactory;
-import com.android.tools.r8.graph.DexLibraryClass;
-import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.utils.InternalOptions;
-import com.android.tools.r8.utils.SetUtils;
-import java.util.Collections;
-import java.util.Set;
-
 public class RedundantBridgeRemovalOptions {
 
-  private final InternalOptions options;
-
-  private boolean enableRetargetingOfConstructorBridgeCalls = false;
-  private Set<DexType> noConstructorShrinkingHierarchies;
-
-  public RedundantBridgeRemovalOptions(InternalOptions options) {
-    this.options = options;
-  }
-
-  public void clearNoConstructorShrinkingHierarchiesForTesting() {
-    noConstructorShrinkingHierarchies = Collections.emptySet();
-  }
-
-  public RedundantBridgeRemovalOptions ensureInitialized() {
-    if (noConstructorShrinkingHierarchies == null) {
-      DexItemFactory dexItemFactory = options.dexItemFactory();
-      noConstructorShrinkingHierarchies =
-          SetUtils.newIdentityHashSet(
-              dexItemFactory.androidAppFragment, dexItemFactory.androidAppZygotePreload);
-    }
-    return this;
-  }
-
-  public boolean isPlatformReflectingOnDefaultConstructorInSubclasses(DexLibraryClass clazz) {
-    return noConstructorShrinkingHierarchies.contains(clazz.getType());
-  }
+  private boolean enableRetargetingOfConstructorBridgeCalls = true;
 
   public boolean isRetargetingOfConstructorBridgeCallsEnabled() {
     return enableRetargetingOfConstructorBridgeCalls;
diff --git a/src/main/java/com/android/tools/r8/optimize/redundantbridgeremoval/RedundantBridgeRemover.java b/src/main/java/com/android/tools/r8/optimize/redundantbridgeremoval/RedundantBridgeRemover.java
index 49b5d17..2ed2ab5 100644
--- a/src/main/java/com/android/tools/r8/optimize/redundantbridgeremoval/RedundantBridgeRemover.java
+++ b/src/main/java/com/android/tools/r8/optimize/redundantbridgeremoval/RedundantBridgeRemover.java
@@ -10,6 +10,8 @@
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.ImmediateProgramSubtypingInfo;
+import com.android.tools.r8.graph.MethodAccessFlags;
 import com.android.tools.r8.graph.MethodResolutionResult;
 import com.android.tools.r8.graph.MethodResolutionResult.FailedResolutionResult;
 import com.android.tools.r8.graph.MethodResolutionResult.SingleResolutionResult;
@@ -19,32 +21,31 @@
 import com.android.tools.r8.optimize.InvokeSingleTargetExtractor;
 import com.android.tools.r8.optimize.InvokeSingleTargetExtractor.InvokeKind;
 import com.android.tools.r8.optimize.MemberRebindingIdentityLens;
+import com.android.tools.r8.optimize.argumentpropagation.utils.DepthFirstTopDownClassHierarchyTraversal;
+import com.android.tools.r8.optimize.argumentpropagation.utils.ProgramClassesBidirectedGraph;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.shaking.KeepMethodInfo;
-import com.android.tools.r8.utils.IterableUtils;
 import com.android.tools.r8.utils.ThreadUtils;
-import com.android.tools.r8.utils.WorkList;
 import com.android.tools.r8.utils.collections.ProgramMethodSet;
-import com.google.common.collect.Iterables;
-import java.util.Collections;
-import java.util.Map;
-import java.util.concurrent.ConcurrentHashMap;
+import java.util.Collection;
+import java.util.List;
+import java.util.Set;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.ExecutorService;
-import java.util.function.Consumer;
 
 public class RedundantBridgeRemover {
 
   private final AppView<AppInfoWithLiveness> appView;
+  private final ImmediateProgramSubtypingInfo immediateSubtypingInfo;
   private final RedundantBridgeRemovalOptions redundantBridgeRemovalOptions;
 
-  private final InvokedReflectivelyFromPlatformAnalysis invokedReflectivelyFromPlatformAnalysis =
-      new InvokedReflectivelyFromPlatformAnalysis();
+  private final RedundantBridgeRemovalLens.Builder lensBuilder =
+      new RedundantBridgeRemovalLens.Builder();
 
   public RedundantBridgeRemover(AppView<AppInfoWithLiveness> appView) {
     this.appView = appView;
-    this.redundantBridgeRemovalOptions =
-        appView.options().getRedundantBridgeRemovalOptions().ensureInitialized();
+    this.immediateSubtypingInfo = ImmediateProgramSubtypingInfo.create(appView);
+    this.redundantBridgeRemovalOptions = appView.options().getRedundantBridgeRemovalOptions();
   }
 
   private DexClassAndMethod getTargetForRedundantBridge(ProgramMethod method) {
@@ -65,9 +66,6 @@
     if (!isTargetingSuperMethod(method, targetExtractor.getKind(), target)) {
       return null;
     }
-    if (invokedReflectivelyFromPlatformAnalysis.isMaybeInvokedReflectivelyFromPlatform(method)) {
-      return null;
-    }
     // This is a visibility forward, so check for the direct target.
     DexClassAndMethod targetMethod =
         appView.appInfo().unsafeResolveMethodDueToDexFormatLegacy(target).getResolutionPair();
@@ -106,6 +104,9 @@
     if (kind == InvokeKind.STATIC) {
       return appView.appInfo().isStrictSubtypeOf(method.getHolderType(), target.holder);
     }
+    if (kind == InvokeKind.VIRTUAL) {
+      return false;
+    }
     assert false : "Unexpected invoke-kind for visibility bridge: " + kind;
     return false;
   }
@@ -117,9 +118,7 @@
         || memberRebindingIdentityLens == appView.graphLens();
 
     // Collect all redundant bridges to remove.
-    RedundantBridgeRemovalLens.Builder lensBuilder = new RedundantBridgeRemovalLens.Builder();
-    Map<DexProgramClass, ProgramMethodSet> bridgesToRemove =
-        computeBridgesToRemove(lensBuilder, executorService);
+    ProgramMethodSet bridgesToRemove = removeRedundantBridgesConcurrently(executorService);
     if (bridgesToRemove.isEmpty()) {
       return;
     }
@@ -131,65 +130,50 @@
     }
 
     if (memberRebindingIdentityLens != null) {
-      for (ProgramMethodSet bridgesToRemoveFromClass : bridgesToRemove.values()) {
-        for (ProgramMethod bridgeToRemove : bridgesToRemoveFromClass) {
-          DexClassAndMethod resolvedMethod =
-              appView
-                  .appInfo()
-                  .resolveMethodOn(bridgeToRemove.getHolder(), bridgeToRemove.getReference())
-                  .getResolutionPair();
-          memberRebindingIdentityLens.addNonReboundMethodReference(
-              bridgeToRemove.getReference(), resolvedMethod.getReference());
-        }
+      for (ProgramMethod bridgeToRemove : bridgesToRemove) {
+        DexClassAndMethod resolvedMethod =
+            appView
+                .appInfo()
+                .resolveMethodOn(bridgeToRemove.getHolder(), bridgeToRemove.getReference())
+                .getResolutionPair();
+        memberRebindingIdentityLens.addNonReboundMethodReference(
+            bridgeToRemove.getReference(), resolvedMethod.getReference());
       }
     }
   }
 
-  private Map<DexProgramClass, ProgramMethodSet> computeBridgesToRemove(
-      RedundantBridgeRemovalLens.Builder lensBuilder, ExecutorService executorService)
+  private ProgramMethodSet removeRedundantBridgesConcurrently(ExecutorService executorService)
       throws ExecutionException {
-    Map<DexProgramClass, ProgramMethodSet> bridgesToRemove = new ConcurrentHashMap<>();
-    ThreadUtils.processItems(
-        appView.appInfo().classes(),
-        clazz -> {
-          ProgramMethodSet bridgesToRemoveForClass = ProgramMethodSet.create();
-          clazz.forEachProgramMethod(
-              method -> {
-                KeepMethodInfo keepInfo = appView.getKeepInfo(method);
-                if (!keepInfo.isShrinkingAllowed(appView.options())
-                    || !keepInfo.isOptimizationAllowed(appView.options())) {
-                  return;
-                }
-                if (isRedundantAbstractBridge(method)) {
-                  // Record that the redundant bridge should be removed.
-                  bridgesToRemoveForClass.add(method);
-                  return;
-                }
-                DexClassAndMethod target = getTargetForRedundantBridge(method);
-                if (target != null) {
-                  // Record that the redundant bridge should be removed.
-                  bridgesToRemoveForClass.add(method);
+    // Compute the strongly connected program components for parallelization.
+    List<Set<DexProgramClass>> stronglyConnectedProgramComponents =
+        new ProgramClassesBidirectedGraph(appView, immediateSubtypingInfo)
+            .computeStronglyConnectedComponents();
 
-                  // Rewrite invokes to the bridge to the target if it is accessible.
-                  // TODO(b/173751869): Consider enabling this for constructors as well.
-                  // TODO(b/245882297): Refine these visibility checks so that we also rewrite when
-                  //  the target is not public, but still accessible to call sites.
-                  boolean isEligibleForRetargeting =
-                      redundantBridgeRemovalOptions.isRetargetingOfConstructorBridgeCallsEnabled()
-                          || !method.getDefinition().isInstanceInitializer();
-                  if (isEligibleForRetargeting
-                      && target.getAccessFlags().isPublic()
-                      && target.getHolder().isPublic()) {
-                    lensBuilder.map(method, target);
-                  }
-                }
-              });
-          if (!bridgesToRemoveForClass.isEmpty()) {
-            bridgesToRemove.put(clazz, bridgesToRemoveForClass);
-          }
-        },
-        executorService);
-    return bridgesToRemove;
+    // Process the components concurrently.
+    Collection<ProgramMethodSet> results =
+        ThreadUtils.processItemsWithResultsThatMatches(
+            stronglyConnectedProgramComponents,
+            this::removeRedundantBridgesInComponent,
+            removedBridges -> !removedBridges.isEmpty(),
+            executorService);
+    ProgramMethodSet removedBridges = ProgramMethodSet.create();
+    results.forEach(
+        result -> {
+          removedBridges.addAll(result);
+          result.clear();
+        });
+    return removedBridges;
+  }
+
+  private ProgramMethodSet removeRedundantBridgesInComponent(
+      Set<DexProgramClass> stronglyConnectedProgramComponent) {
+    // Remove bridges in a top-down traversal of the class hierarchy. This ensures that we don't map
+    // an invoke to a removed bridge method to a method in the superclass hierarchy, which is then
+    // also removed by bridge removal.
+    RedundantBridgeRemoverClassHierarchyTraversal traversal =
+        new RedundantBridgeRemoverClassHierarchyTraversal();
+    traversal.run(stronglyConnectedProgramComponent);
+    return traversal.getRemovedBridges();
   }
 
   private boolean isRedundantAbstractBridge(ProgramMethod method) {
@@ -241,99 +225,95 @@
     return true;
   }
 
-  private void pruneApp(
-      Map<DexProgramClass, ProgramMethodSet> bridgesToRemove, ExecutorService executorService)
+  private void pruneApp(ProgramMethodSet bridgesToRemove, ExecutorService executorService)
       throws ExecutionException {
     PrunedItems.Builder prunedItemsBuilder = PrunedItems.builder().setPrunedApp(appView.app());
-    bridgesToRemove.forEach(
-        (clazz, methods) -> {
-          clazz.getMethodCollection().removeMethods(methods.toDefinitionSet());
-          methods.forEach(method -> prunedItemsBuilder.addRemovedMethod(method.getReference()));
-        });
+    bridgesToRemove.forEach(method -> prunedItemsBuilder.addRemovedMethod(method.getReference()));
     appView.pruneItems(prunedItemsBuilder.build(), executorService);
   }
 
-  class InvokedReflectivelyFromPlatformAnalysis {
+  class RedundantBridgeRemoverClassHierarchyTraversal
+      extends DepthFirstTopDownClassHierarchyTraversal {
 
-    // Maps each class to a boolean indicating if the class inherits from android.app.Fragment or
-    // android.app.ZygotePreload.
-    private final Map<DexClass, Boolean> cache = new ConcurrentHashMap<>();
+    private final ProgramMethodSet removedBridges = ProgramMethodSet.create();
 
-    boolean isMaybeInvokedReflectivelyFromPlatform(ProgramMethod method) {
-      return method.getDefinition().isDefaultInstanceInitializer()
-          && !method.getHolder().isAbstract()
-          && computeIsPlatformReflectingOnDefaultConstructor(method.getHolder());
+    RedundantBridgeRemoverClassHierarchyTraversal() {
+      super(
+          RedundantBridgeRemover.this.appView, RedundantBridgeRemover.this.immediateSubtypingInfo);
     }
 
-    private boolean computeIsPlatformReflectingOnDefaultConstructor(DexProgramClass clazz) {
-      Boolean cacheResult = cache.get(clazz);
-      if (cacheResult != null) {
-        return cacheResult;
-      }
-      WorkList.<WorklistItem>newIdentityWorkList(new NotProcessedWorklistItem(clazz))
-          .process(WorklistItem::accept);
-      assert cache.containsKey(clazz);
-      return cache.get(clazz);
+    public ProgramMethodSet getRemovedBridges() {
+      return removedBridges;
     }
 
-    abstract class WorklistItem implements Consumer<WorkList<WorklistItem>> {
+    @Override
+    public void visit(DexProgramClass clazz) {
+      ProgramMethodSet bridgesToRemoveForClass = ProgramMethodSet.create();
+      clazz.forEachProgramMethod(
+          method -> {
+            KeepMethodInfo keepInfo = appView.getKeepInfo(method);
+            if (!keepInfo.isShrinkingAllowed(appView.options())
+                || !keepInfo.isOptimizationAllowed(appView.options())) {
+              return;
+            }
+            if (isRedundantAbstractBridge(method)) {
+              // Record that the redundant bridge should be removed.
+              bridgesToRemoveForClass.add(method);
+              return;
+            }
+            DexClassAndMethod target = getTargetForRedundantBridge(method);
+            if (target != null) {
+              // Record that the redundant bridge should be removed.
+              bridgesToRemoveForClass.add(method);
 
-      protected final DexClass clazz;
-
-      WorklistItem(DexClass clazz) {
-        this.clazz = clazz;
-      }
-
-      Iterable<DexClass> getImmediateSupertypes() {
-        return IterableUtils.flatMap(
-            clazz.allImmediateSupertypes(),
-            supertype -> {
-              DexClass definition = appView.definitionFor(supertype);
-              return definition != null
-                  ? Collections.singletonList(definition)
-                  : Collections.emptyList();
-            });
+              // Rewrite invokes to the bridge to the target if it is accessible.
+              if (canRetargetInvokesToTargetMethod(method, target)) {
+                lensBuilder.map(method, target);
+              }
+            }
+          });
+      if (!bridgesToRemoveForClass.isEmpty()) {
+        clazz.getMethodCollection().removeMethods(bridgesToRemoveForClass.toDefinitionSet());
+        removedBridges.addAll(bridgesToRemoveForClass);
       }
     }
 
-    class NotProcessedWorklistItem extends WorklistItem {
-
-      NotProcessedWorklistItem(DexClass clazz) {
-        super(clazz);
+    private boolean canRetargetInvokesToTargetMethod(
+        ProgramMethod method, DexClassAndMethod target) {
+      // Check if constructor retargeting is enabled.
+      if (method.getDefinition().isInstanceInitializer()
+          && !redundantBridgeRemovalOptions.isRetargetingOfConstructorBridgeCallsEnabled()) {
+        return false;
       }
-
-      @Override
-      public void accept(WorkList<WorklistItem> worklist) {
-        // Enqueue a worklist item to process the current class after processing its super classes.
-        worklist.addFirstIgnoringSeenSet(new ProcessedWorklistItem(clazz));
-        // Enqueue all superclasses for processing.
-        for (DexClass supertype : getImmediateSupertypes()) {
-          if (!cache.containsKey(supertype)) {
-            worklist.addFirstIgnoringSeenSet(new NotProcessedWorklistItem(supertype));
-          }
+      // Check if all possible contexts that have access to the holder of the redundant bridge
+      // method also have access to the holder of the target method.
+      DexProgramClass methodHolder = method.getHolder();
+      DexClass targetHolder = target.getHolder();
+      if (!targetHolder.getAccessFlags().isPublic()) {
+        if (methodHolder.getAccessFlags().isPublic() || !method.isSamePackage(target)) {
+          return false;
         }
       }
+      // Check if all possible contexts that have access to the redundant bridge method also have
+      // access to the target method.
+      if (target.getAccessFlags().isPublic()) {
+        return true;
+      }
+      MethodAccessFlags methodAccessFlags = method.getAccessFlags();
+      MethodAccessFlags targetAccessFlags = target.getAccessFlags();
+      if (methodAccessFlags.isPackagePrivate()
+          && !targetAccessFlags.isPrivate()
+          && method.isSamePackage(target)) {
+        return true;
+      }
+      return methodAccessFlags.isProtected()
+          && targetAccessFlags.isProtected()
+          && method.isSamePackage(target);
     }
 
-    class ProcessedWorklistItem extends WorklistItem {
-
-      ProcessedWorklistItem(DexClass clazz) {
-        super(clazz);
-      }
-
-      @Override
-      public void accept(WorkList<WorklistItem> worklist) {
-        cache.put(
-            clazz,
-            Iterables.any(
-                getImmediateSupertypes(),
-                supertype ->
-                    cache.get(supertype)
-                        || (supertype.isLibraryClass()
-                            && redundantBridgeRemovalOptions
-                                .isPlatformReflectingOnDefaultConstructorInSubclasses(
-                                    supertype.asLibraryClass()))));
-      }
+    @Override
+    public void prune(DexProgramClass clazz) {
+      // Empty.
     }
   }
 }
diff --git a/src/main/java/com/android/tools/r8/retrace/RetraceStackTraceElementProxy.java b/src/main/java/com/android/tools/r8/retrace/RetraceStackTraceElementProxy.java
index 7f4747a..714e2b9 100644
--- a/src/main/java/com/android/tools/r8/retrace/RetraceStackTraceElementProxy.java
+++ b/src/main/java/com/android/tools/r8/retrace/RetraceStackTraceElementProxy.java
@@ -43,6 +43,8 @@
 
   String getSourceFile();
 
+  RetracedSourceFile getRetracedSourceFile();
+
   int getLineNumber();
 
   RetraceStackTraceContext getContext();
diff --git a/src/main/java/com/android/tools/r8/retrace/RetracedClassReference.java b/src/main/java/com/android/tools/r8/retrace/RetracedClassReference.java
index 5e6b29b..2c030f7 100644
--- a/src/main/java/com/android/tools/r8/retrace/RetracedClassReference.java
+++ b/src/main/java/com/android/tools/r8/retrace/RetracedClassReference.java
@@ -10,6 +10,10 @@
 @Keep
 public interface RetracedClassReference {
 
+  boolean isUnknown();
+
+  boolean isKnown();
+
   String getTypeName();
 
   String getDescriptor();
diff --git a/src/main/java/com/android/tools/r8/retrace/RetracedSourceFile.java b/src/main/java/com/android/tools/r8/retrace/RetracedSourceFile.java
index d1d3542..d677cb8 100644
--- a/src/main/java/com/android/tools/r8/retrace/RetracedSourceFile.java
+++ b/src/main/java/com/android/tools/r8/retrace/RetracedSourceFile.java
@@ -14,4 +14,6 @@
   String getSourceFile();
 
   String getOrInferSourceFile();
+
+  String getOrInferSourceFile(String original);
 }
diff --git a/src/main/java/com/android/tools/r8/retrace/StringRetrace.java b/src/main/java/com/android/tools/r8/retrace/StringRetrace.java
index df888dc..3f37a84 100644
--- a/src/main/java/com/android/tools/r8/retrace/StringRetrace.java
+++ b/src/main/java/com/android/tools/r8/retrace/StringRetrace.java
@@ -133,26 +133,33 @@
   private List<String> joinAmbiguousLines(
       List<RetraceStackFrameAmbiguousResult<String>> retracedResult) {
     List<String> result = new ArrayList<>();
-    retracedResult.forEach(
-        potentialResults -> {
-          Set<String> reportedFrames = new HashSet<>();
-          potentialResults.forEachWithIndex(
-              (inlineFrames, index) -> {
-                // Check if we already reported position.
-                String topFrame = inlineFrames.get(0);
-                if (reportedFrames.add(topFrame)) {
-                  inlineFrames.forEach(
-                      inlineFrame -> {
-                        boolean isAmbiguous = index > 0 && topFrame.equals(inlineFrame);
-                        if (isAmbiguous) {
-                          result.add(insertOrIntoStackTraceLine(inlineFrame));
-                        } else {
-                          result.add(inlineFrame);
-                        }
-                      });
-                }
-              });
-        });
+    for (RetraceStackFrameAmbiguousResult<String> ambiguousResult : retracedResult) {
+      boolean addedLines = true;
+      int lineIndex = 0;
+      while (addedLines) {
+        addedLines = false;
+        Set<String> reportedFrames = new HashSet<>();
+        RetraceStackFrameResult<String> firstResult = null;
+        for (RetraceStackFrameResult<String> inlineFrames : ambiguousResult.getAmbiguousResult()) {
+          if (firstResult == null) {
+            firstResult = inlineFrames;
+          }
+          if (lineIndex < inlineFrames.size()) {
+            addedLines = true;
+            String frameToAdd = inlineFrames.get(lineIndex);
+            if (reportedFrames.add(frameToAdd)) {
+              boolean isAmbiguous = inlineFrames != firstResult;
+              if (isAmbiguous) {
+                result.add(insertOrIntoStackTraceLine(frameToAdd));
+              } else {
+                result.add(frameToAdd);
+              }
+            }
+          }
+        }
+        lineIndex += 1;
+      }
+    }
     return result;
   }
 
diff --git a/src/main/java/com/android/tools/r8/retrace/internal/MappingPartitionMetadataInternal.java b/src/main/java/com/android/tools/r8/retrace/internal/MappingPartitionMetadataInternal.java
index 9896856..b5438c6 100644
--- a/src/main/java/com/android/tools/r8/retrace/internal/MappingPartitionMetadataInternal.java
+++ b/src/main/java/com/android/tools/r8/retrace/internal/MappingPartitionMetadataInternal.java
@@ -43,7 +43,7 @@
   }
 
   default MetadataAdditionalInfo getAdditionalInfo() {
-    return MetadataAdditionalInfo.create(null);
+    return MetadataAdditionalInfo.create(null, null);
   }
 
   // Magic byte put into the metadata
diff --git a/src/main/java/com/android/tools/r8/retrace/internal/MetadataAdditionalInfo.java b/src/main/java/com/android/tools/r8/retrace/internal/MetadataAdditionalInfo.java
index 706584b..7e0e22d 100644
--- a/src/main/java/com/android/tools/r8/retrace/internal/MetadataAdditionalInfo.java
+++ b/src/main/java/com/android/tools/r8/retrace/internal/MetadataAdditionalInfo.java
@@ -5,20 +5,30 @@
 package com.android.tools.r8.retrace.internal;
 
 import com.android.tools.r8.dex.CompatByteBuffer;
+import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.retrace.RetracePartitionException;
 import com.android.tools.r8.utils.SerializationUtils;
 import com.android.tools.r8.utils.StringUtils;
 import java.io.ByteArrayOutputStream;
 import java.io.DataOutputStream;
 import java.io.IOException;
+import java.util.ArrayList;
 import java.util.Collection;
+import java.util.Collections;
 import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.function.Predicate;
 
 public class MetadataAdditionalInfo {
 
+  private static final int NUMBER_OF_ELEMENTS = 2;
+
   public enum AdditionalInfoTypes {
     UNKNOWN(-1),
-    PREAMBLE(0);
+    PREAMBLE(0),
+    OBFUSCATED_PACKAGES(1);
 
     private final int serializedKey;
 
@@ -29,15 +39,19 @@
     static AdditionalInfoTypes getByKey(int serializedKey) {
       if (serializedKey == 0) {
         return PREAMBLE;
+      } else if (serializedKey == 1) {
+        return OBFUSCATED_PACKAGES;
       }
       return UNKNOWN;
     }
   }
 
   protected final List<String> preamble;
+  protected final Set<String> obfuscatedPackages;
 
-  private MetadataAdditionalInfo(List<String> preamble) {
+  private MetadataAdditionalInfo(List<String> preamble, Set<String> obfuscatedPackages) {
     this.preamble = preamble;
+    this.obfuscatedPackages = obfuscatedPackages;
   }
 
   public boolean hasPreamble() {
@@ -48,70 +62,116 @@
     return preamble;
   }
 
+  public boolean hasObfuscatedPackages() {
+    return obfuscatedPackages != null;
+  }
+
+  public Set<String> getObfuscatedPackages() {
+    return obfuscatedPackages;
+  }
+
   // The serialized format is an extensible list where we first record the offsets for each data
   // section and then emit the data.
   // <total-size:int><number-of-elements:short>[<type-i:short><length-i:int><data-i>]
   public void serialize(DataOutputStream dataOutputStream) throws IOException {
     ByteArrayOutputStream temp = new ByteArrayOutputStream();
     DataOutputStream additionalInfoStream = new DataOutputStream(temp);
-    additionalInfoStream.writeShort(1);
+    additionalInfoStream.writeShort(NUMBER_OF_ELEMENTS);
     additionalInfoStream.writeShort(AdditionalInfoTypes.PREAMBLE.serializedKey);
     SerializationUtils.writeUTFOfIntSize(additionalInfoStream, StringUtils.unixLines(preamble));
+    additionalInfoStream.writeShort(AdditionalInfoTypes.OBFUSCATED_PACKAGES.serializedKey);
+    List<String> sortedPackages = new ArrayList<>(obfuscatedPackages);
+    Collections.sort(sortedPackages);
+    SerializationUtils.writeUTFOfIntSize(
+        additionalInfoStream, StringUtils.unixLines(sortedPackages));
     byte[] payload = temp.toByteArray();
     dataOutputStream.writeInt(payload.length);
     dataOutputStream.write(payload);
   }
 
-  private static MetadataAdditionalInfo deserialize(byte[] bytes) {
+  private static MetadataAdditionalInfo deserialize(
+      byte[] bytes, Predicate<AdditionalInfoTypes> serializeSection) {
     CompatByteBuffer compatByteBuffer = CompatByteBuffer.wrap(bytes);
     int numberOfElements = compatByteBuffer.getShort();
     List<String> preamble = null;
+    Set<String> packages = null;
     for (int i = 0; i < numberOfElements; i++) {
       // We are parsing <type:short><length:int><bytes>
       int additionInfoTypeKey = compatByteBuffer.getShort();
       AdditionalInfoTypes additionalInfoType = AdditionalInfoTypes.getByKey(additionInfoTypeKey);
-      if (additionalInfoType == AdditionalInfoTypes.PREAMBLE) {
-        preamble = StringUtils.splitLines(compatByteBuffer.getUTFOfIntSize());
-      } else {
+      if (additionalInfoType == AdditionalInfoTypes.UNKNOWN) {
         throw new RetracePartitionException(
             "Could not additional info from key: " + additionInfoTypeKey);
       }
+      if (serializeSection.test(additionalInfoType)) {
+        switch (additionalInfoType) {
+          case PREAMBLE:
+            preamble = StringUtils.splitLines(compatByteBuffer.getUTFOfIntSize());
+            break;
+          case OBFUSCATED_PACKAGES:
+            packages = StringUtils.splitLinesIntoSet(compatByteBuffer.getUTFOfIntSize());
+            break;
+          default:
+            throw new Unreachable("Unreachable since we already checked for UNKNOWN");
+        }
+      } else {
+        int length = compatByteBuffer.getInt();
+        compatByteBuffer.position(compatByteBuffer.position() + length);
+      }
     }
-    return new MetadataAdditionalInfo(preamble);
+    return new MetadataAdditionalInfo(preamble, packages);
   }
 
-  public static MetadataAdditionalInfo create(List<String> preamble) {
-    return new MetadataAdditionalInfo(preamble);
+  public static MetadataAdditionalInfo create(
+      List<String> preamble, Set<String> obfuscatedPackages) {
+    return new MetadataAdditionalInfo(preamble, obfuscatedPackages);
   }
 
   public static class LazyMetadataAdditionalInfo extends MetadataAdditionalInfo {
 
-    private byte[] bytes;
-    private MetadataAdditionalInfo metadataAdditionalInfo = null;
+    private final byte[] bytes;
+    private final Map<Integer, MetadataAdditionalInfo> metadataAdditionalInfo =
+        new ConcurrentHashMap<>();
 
     public LazyMetadataAdditionalInfo(byte[] bytes) {
-      super(null);
+      super(null, null);
       this.bytes = bytes;
     }
 
     @Override
     public boolean hasPreamble() {
-      MetadataAdditionalInfo metadataAdditionalInfo = getMetadataAdditionalInfo();
+      MetadataAdditionalInfo metadataAdditionalInfo =
+          getMetadataAdditionalInfo(AdditionalInfoTypes.PREAMBLE);
       return metadataAdditionalInfo != null && metadataAdditionalInfo.hasPreamble();
     }
 
     @Override
     public Collection<String> getPreamble() {
-      MetadataAdditionalInfo metadataAdditionalInfo = getMetadataAdditionalInfo();
+      MetadataAdditionalInfo metadataAdditionalInfo =
+          getMetadataAdditionalInfo(AdditionalInfoTypes.PREAMBLE);
       return metadataAdditionalInfo == null ? null : metadataAdditionalInfo.getPreamble();
     }
 
-    private MetadataAdditionalInfo getMetadataAdditionalInfo() {
-      if (metadataAdditionalInfo == null) {
-        metadataAdditionalInfo = MetadataAdditionalInfo.deserialize(bytes);
-        bytes = null;
-      }
-      return metadataAdditionalInfo;
+    @Override
+    public boolean hasObfuscatedPackages() {
+      MetadataAdditionalInfo metadataAdditionalInfo =
+          getMetadataAdditionalInfo(AdditionalInfoTypes.OBFUSCATED_PACKAGES);
+      return metadataAdditionalInfo != null && metadataAdditionalInfo.hasObfuscatedPackages();
+    }
+
+    @Override
+    public Set<String> getObfuscatedPackages() {
+      MetadataAdditionalInfo metadataAdditionalInfo =
+          getMetadataAdditionalInfo(AdditionalInfoTypes.OBFUSCATED_PACKAGES);
+      return metadataAdditionalInfo == null ? null : metadataAdditionalInfo.getObfuscatedPackages();
+    }
+
+    private MetadataAdditionalInfo getMetadataAdditionalInfo(AdditionalInfoTypes infoType) {
+      return metadataAdditionalInfo.computeIfAbsent(
+          infoType.serializedKey,
+          ignored ->
+              MetadataAdditionalInfo.deserialize(
+                  bytes, deserializeType -> deserializeType == infoType));
     }
 
     public static LazyMetadataAdditionalInfo create(CompatByteBuffer buffer) {
diff --git a/src/main/java/com/android/tools/r8/retrace/internal/PartitionMappingSupplierBase.java b/src/main/java/com/android/tools/r8/retrace/internal/PartitionMappingSupplierBase.java
index 012fb94..a15f89f 100644
--- a/src/main/java/com/android/tools/r8/retrace/internal/PartitionMappingSupplierBase.java
+++ b/src/main/java/com/android/tools/r8/retrace/internal/PartitionMappingSupplierBase.java
@@ -23,6 +23,8 @@
 import com.android.tools.r8.retrace.PrepareMappingPartitionsCallback;
 import com.android.tools.r8.retrace.RegisterMappingPartitionCallback;
 import com.android.tools.r8.retrace.internal.ProguardMapReaderWithFiltering.ProguardMapReaderWithFilteringInputBuffer;
+import com.android.tools.r8.utils.Box;
+import com.android.tools.r8.utils.DescriptorUtils;
 import com.android.tools.r8.utils.StringDiagnostic;
 import java.io.ByteArrayInputStream;
 import java.io.IOException;
@@ -30,6 +32,7 @@
 import java.util.HashSet;
 import java.util.LinkedHashSet;
 import java.util.Set;
+import java.util.function.Predicate;
 
 public abstract class PartitionMappingSupplierBase<T extends PartitionMappingSupplierBase<T>>
     implements Finishable {
@@ -45,7 +48,8 @@
   private final Set<String> pendingKeys = new LinkedHashSet<>();
   private final Set<String> builtKeys = new HashSet<>();
 
-  private MappingPartitionMetadataInternal mappingPartitionMetadataCache;
+  private final Box<MappingPartitionMetadataInternal> mappingPartitionMetadataCache = new Box<>();
+  private final Box<Predicate<String>> typeNameCouldHavePartitionCache = new Box<>();
 
   protected PartitionMappingSupplierBase(
       RegisterMappingPartitionCallback registerCallback,
@@ -63,16 +67,49 @@
   }
 
   public MappingPartitionMetadataInternal getMetadata(DiagnosticsHandler diagnosticsHandler) {
-    if (mappingPartitionMetadataCache != null) {
-      return mappingPartitionMetadataCache;
+    if (mappingPartitionMetadataCache.isSet()) {
+      return mappingPartitionMetadataCache.get();
     }
-    return mappingPartitionMetadataCache =
-        MappingPartitionMetadataInternal.deserialize(
-            CompatByteBuffer.wrapOrNull(metadata), fallbackMapVersion, diagnosticsHandler);
+    synchronized (mappingPartitionMetadataCache) {
+      if (mappingPartitionMetadataCache.isSet()) {
+        return mappingPartitionMetadataCache.get();
+      }
+      MappingPartitionMetadataInternal data =
+          MappingPartitionMetadataInternal.deserialize(
+              CompatByteBuffer.wrapOrNull(metadata), fallbackMapVersion, diagnosticsHandler);
+      mappingPartitionMetadataCache.set(data);
+      return data;
+    }
   }
 
   public T registerClassUse(DiagnosticsHandler diagnosticsHandler, ClassReference classReference) {
-    return registerKeyUse(classReference.getTypeName());
+    // Check if the package name is registered before requesting the bytes for a partition.
+    String typeName = classReference.getTypeName();
+    if (isPotentialRetraceClass(diagnosticsHandler, typeName)) {
+      return registerKeyUse(typeName);
+    }
+    return self();
+  }
+
+  private boolean isPotentialRetraceClass(DiagnosticsHandler diagnosticsHandler, String typeName) {
+    if (typeNameCouldHavePartitionCache.isSet()) {
+      return typeNameCouldHavePartitionCache.get().test(typeName);
+    }
+    synchronized (typeNameCouldHavePartitionCache) {
+      if (typeNameCouldHavePartitionCache.isSet()) {
+        return typeNameCouldHavePartitionCache.get().test(typeName);
+      }
+      Predicate<String> typeNameCouldHavePartitionPredicate =
+          getPartitionPredicate(getPackagesWithClasses(diagnosticsHandler));
+      typeNameCouldHavePartitionCache.set(typeNameCouldHavePartitionPredicate);
+      return typeNameCouldHavePartitionPredicate.test(typeName);
+    }
+  }
+
+  private Predicate<String> getPartitionPredicate(Set<String> packagesWithClasses) {
+    return name ->
+        packagesWithClasses == null
+            || packagesWithClasses.contains(DescriptorUtils.getPackageNameFromTypeName(name));
   }
 
   public T registerMethodUse(
@@ -85,13 +122,24 @@
   }
 
   public T registerKeyUse(String key) {
-    // TODO(b/274735214): only call the register partition if we have a partition for it.
     if (!builtKeys.contains(key) && pendingKeys.add(key)) {
       registerCallback.register(key);
     }
     return self();
   }
 
+  private Set<String> getPackagesWithClasses(DiagnosticsHandler diagnosticsHandler) {
+    MappingPartitionMetadataInternal metadata = getMetadata(diagnosticsHandler);
+    if (metadata == null || !metadata.canGetAdditionalInfo()) {
+      return null;
+    }
+    MetadataAdditionalInfo additionalInfo = metadata.getAdditionalInfo();
+    if (!additionalInfo.hasObfuscatedPackages()) {
+      return null;
+    }
+    return additionalInfo.getObfuscatedPackages();
+  }
+
   public void verifyMappingFileHash(DiagnosticsHandler diagnosticsHandler) {
     String errorMessage = "Cannot verify map file hash for partitions";
     diagnosticsHandler.error(new StringDiagnostic(errorMessage));
@@ -115,7 +163,6 @@
     for (String pendingKey : pendingKeys) {
       try {
         byte[] suppliedPartition = partitionSupplier.get(pendingKey);
-        // TODO(b/274735214): only expect a partition if have generated one for the key.
         if (suppliedPartition == null) {
           continue;
         }
diff --git a/src/main/java/com/android/tools/r8/retrace/internal/ProguardMapPartitionerOnClassNameToText.java b/src/main/java/com/android/tools/r8/retrace/internal/ProguardMapPartitionerOnClassNameToText.java
index 3edde9a..3af7dff 100644
--- a/src/main/java/com/android/tools/r8/retrace/internal/ProguardMapPartitionerOnClassNameToText.java
+++ b/src/main/java/com/android/tools/r8/retrace/internal/ProguardMapPartitionerOnClassNameToText.java
@@ -168,7 +168,8 @@
       return ObfuscatedTypeNameAsKeyMetadataWithPartitionNames.create(
           mapVersion,
           MetadataPartitionCollection.create(keys),
-          MetadataAdditionalInfo.create(classMapper.getPreamble()));
+          MetadataAdditionalInfo.create(
+              classMapper.getPreamble(), classMapper.getObfuscatedPackages()));
     } else {
       RetracePartitionException retraceError =
           new RetracePartitionException("Unknown mapping partitioning strategy");
diff --git a/src/main/java/com/android/tools/r8/retrace/internal/RetraceClassResultImpl.java b/src/main/java/com/android/tools/r8/retrace/internal/RetraceClassResultImpl.java
index dc90e36..98ed799 100644
--- a/src/main/java/com/android/tools/r8/retrace/internal/RetraceClassResultImpl.java
+++ b/src/main/java/com/android/tools/r8/retrace/internal/RetraceClassResultImpl.java
@@ -212,9 +212,8 @@
     return new RetraceClassElementImpl(
         this,
         RetracedClassReferenceImpl.create(
-            mapper == null
-                ? obfuscatedReference
-                : Reference.classFromTypeName(mapper.originalName)),
+            mapper == null ? obfuscatedReference : Reference.classFromTypeName(mapper.originalName),
+            mapper != null),
         mapper);
   }
 
diff --git a/src/main/java/com/android/tools/r8/retrace/internal/RetraceFieldResultImpl.java b/src/main/java/com/android/tools/r8/retrace/internal/RetraceFieldResultImpl.java
index afade35..ac45d92 100644
--- a/src/main/java/com/android/tools/r8/retrace/internal/RetraceFieldResultImpl.java
+++ b/src/main/java/com/android/tools/r8/retrace/internal/RetraceFieldResultImpl.java
@@ -10,7 +10,6 @@
 import com.android.tools.r8.retrace.RetraceFieldElement;
 import com.android.tools.r8.retrace.RetraceFieldResult;
 import com.android.tools.r8.retrace.RetracedSourceFile;
-import com.android.tools.r8.retrace.Retracer;
 import com.android.tools.r8.retrace.internal.RetraceClassResultImpl.RetraceClassElementImpl;
 import com.android.tools.r8.utils.DescriptorUtils;
 import com.android.tools.r8.utils.Pair;
@@ -22,13 +21,13 @@
   private final RetraceClassResultImpl classResult;
   private final List<Pair<RetraceClassElementImpl, List<MemberNaming>>> memberNamings;
   private final FieldDefinition fieldDefinition;
-  private final Retracer retracer;
+  private final RetracerImpl retracer;
 
   RetraceFieldResultImpl(
       RetraceClassResultImpl classResult,
       List<Pair<RetraceClassElementImpl, List<MemberNaming>>> memberNamings,
       FieldDefinition fieldDefinition,
-      Retracer retracer) {
+      RetracerImpl retracer) {
     this.classResult = classResult;
     this.memberNamings = memberNamings;
     this.fieldDefinition = fieldDefinition;
@@ -64,7 +63,8 @@
                                 ? RetracedClassReferenceImpl.create(
                                     Reference.classFromDescriptor(
                                         DescriptorUtils.javaTypeToDescriptor(
-                                            fieldSignature.toHolderFromQualified())))
+                                            fieldSignature.toHolderFromQualified())),
+                                    true)
                                 : classElement.getRetracedClass();
                         return new ElementImpl(
                             this,
@@ -144,7 +144,8 @@
 
     @Override
     public RetracedSourceFile getSourceFile() {
-      return classElement.getSourceFile();
+      return RetraceUtils.getSourceFile(
+          fieldReference.getHolderClass(), retraceFieldResult.retracer);
     }
   }
 }
diff --git a/src/main/java/com/android/tools/r8/retrace/internal/RetraceThrownExceptionResultImpl.java b/src/main/java/com/android/tools/r8/retrace/internal/RetraceThrownExceptionResultImpl.java
index e5d0df2..3488823 100644
--- a/src/main/java/com/android/tools/r8/retrace/internal/RetraceThrownExceptionResultImpl.java
+++ b/src/main/java/com/android/tools/r8/retrace/internal/RetraceThrownExceptionResultImpl.java
@@ -43,9 +43,8 @@
     return new RetraceThrownExceptionElementImpl(
         this,
         RetracedClassReferenceImpl.create(
-            mapper == null
-                ? obfuscatedReference
-                : Reference.classFromTypeName(mapper.originalName)),
+            mapper == null ? obfuscatedReference : Reference.classFromTypeName(mapper.originalName),
+            mapper != null),
         mapper,
         obfuscatedReference);
   }
@@ -89,7 +88,7 @@
           }
         }
       }
-      return new RetracedSourceFileImpl(getRetracedClass().getClassReference(), sourceFile);
+      return new RetracedSourceFileImpl(getRetracedClass(), sourceFile);
     }
 
     @Override
diff --git a/src/main/java/com/android/tools/r8/retrace/internal/RetraceUtils.java b/src/main/java/com/android/tools/r8/retrace/internal/RetraceUtils.java
index 0180960..b339440 100644
--- a/src/main/java/com/android/tools/r8/retrace/internal/RetraceUtils.java
+++ b/src/main/java/com/android/tools/r8/retrace/internal/RetraceUtils.java
@@ -68,8 +68,7 @@
 
   public static RetracedSourceFile getSourceFile(
       RetracedClassReference holder, RetracerImpl retracer) {
-    ClassReference holderReference = holder.getClassReference();
-    return new RetracedSourceFileImpl(holderReference, retracer.getSourceFile(holderReference));
+    return new RetracedSourceFileImpl(holder, retracer.getSourceFile(holder.getClassReference()));
   }
 
   public static String inferSourceFile(
diff --git a/src/main/java/com/android/tools/r8/retrace/internal/RetracedClassReferenceImpl.java b/src/main/java/com/android/tools/r8/retrace/internal/RetracedClassReferenceImpl.java
index d036009..326b157 100644
--- a/src/main/java/com/android/tools/r8/retrace/internal/RetracedClassReferenceImpl.java
+++ b/src/main/java/com/android/tools/r8/retrace/internal/RetracedClassReferenceImpl.java
@@ -10,14 +10,27 @@
 public final class RetracedClassReferenceImpl implements RetracedClassReference {
 
   private final ClassReference classReference;
+  private final boolean hasResult;
 
-  private RetracedClassReferenceImpl(ClassReference classReference) {
+  private RetracedClassReferenceImpl(ClassReference classReference, boolean hasResult) {
     assert classReference != null;
     this.classReference = classReference;
+    this.hasResult = hasResult;
   }
 
-  public static RetracedClassReferenceImpl create(ClassReference classReference) {
-    return new RetracedClassReferenceImpl(classReference);
+  public static RetracedClassReferenceImpl create(
+      ClassReference classReference, boolean hasResult) {
+    return new RetracedClassReferenceImpl(classReference, hasResult);
+  }
+
+  @Override
+  public boolean isUnknown() {
+    return !isKnown();
+  }
+
+  @Override
+  public boolean isKnown() {
+    return hasResult;
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/retrace/internal/RetracedFieldReferenceImpl.java b/src/main/java/com/android/tools/r8/retrace/internal/RetracedFieldReferenceImpl.java
index 8316a3b..304462e 100644
--- a/src/main/java/com/android/tools/r8/retrace/internal/RetracedFieldReferenceImpl.java
+++ b/src/main/java/com/android/tools/r8/retrace/internal/RetracedFieldReferenceImpl.java
@@ -49,7 +49,7 @@
 
     @Override
     public RetracedClassReferenceImpl getHolderClass() {
-      return RetracedClassReferenceImpl.create(fieldReference.getHolderClass());
+      return RetracedClassReferenceImpl.create(fieldReference.getHolderClass(), true);
     }
 
     @Override
@@ -95,7 +95,7 @@
 
     @Override
     public RetracedClassReferenceImpl getHolderClass() {
-      return RetracedClassReferenceImpl.create(fieldDefinition.getHolderClass());
+      return RetracedClassReferenceImpl.create(fieldDefinition.getHolderClass(), false);
     }
 
     @Override
diff --git a/src/main/java/com/android/tools/r8/retrace/internal/RetracedMethodReferenceImpl.java b/src/main/java/com/android/tools/r8/retrace/internal/RetracedMethodReferenceImpl.java
index f896692..8d7d20f 100644
--- a/src/main/java/com/android/tools/r8/retrace/internal/RetracedMethodReferenceImpl.java
+++ b/src/main/java/com/android/tools/r8/retrace/internal/RetracedMethodReferenceImpl.java
@@ -99,7 +99,7 @@
 
     @Override
     public RetracedClassReferenceImpl getHolderClass() {
-      return RetracedClassReferenceImpl.create(methodReference.getHolderClass());
+      return RetracedClassReferenceImpl.create(methodReference.getHolderClass(), true);
     }
 
     @Override
@@ -153,7 +153,7 @@
 
     @Override
     public RetracedClassReferenceImpl getHolderClass() {
-      return RetracedClassReferenceImpl.create(methodDefinition.getHolderClass());
+      return RetracedClassReferenceImpl.create(methodDefinition.getHolderClass(), false);
     }
 
     @Override
diff --git a/src/main/java/com/android/tools/r8/retrace/internal/RetracedSourceFileImpl.java b/src/main/java/com/android/tools/r8/retrace/internal/RetracedSourceFileImpl.java
index c7f2fae..e570354 100644
--- a/src/main/java/com/android/tools/r8/retrace/internal/RetracedSourceFileImpl.java
+++ b/src/main/java/com/android/tools/r8/retrace/internal/RetracedSourceFileImpl.java
@@ -4,15 +4,15 @@
 
 package com.android.tools.r8.retrace.internal;
 
-import com.android.tools.r8.references.ClassReference;
+import com.android.tools.r8.retrace.RetracedClassReference;
 import com.android.tools.r8.retrace.RetracedSourceFile;
 
 public class RetracedSourceFileImpl implements RetracedSourceFile {
 
-  private final ClassReference classReference;
+  private final RetracedClassReference classReference;
   private final String filename;
 
-  RetracedSourceFileImpl(ClassReference classReference, String filename) {
+  RetracedSourceFileImpl(RetracedClassReference classReference, String filename) {
     assert classReference != null;
     this.classReference = classReference;
     this.filename = filename;
@@ -30,9 +30,17 @@
 
   @Override
   public String getOrInferSourceFile() {
-    String sourceFile = getSourceFile();
+    return getOrInferSourceFile(null);
+  }
+
+  @Override
+  public String getOrInferSourceFile(String original) {
+    String sourceFile = filename;
     return sourceFile != null
         ? sourceFile
-        : RetraceUtils.inferSourceFile(classReference.getTypeName(), "", true);
+        : RetraceUtils.inferSourceFile(
+            classReference.getTypeName(),
+            original == null ? "" : original,
+            classReference.isKnown());
   }
 }
diff --git a/src/main/java/com/android/tools/r8/retrace/internal/StackTraceElementProxyRetracerImpl.java b/src/main/java/com/android/tools/r8/retrace/internal/StackTraceElementProxyRetracerImpl.java
index c907105..18623af 100644
--- a/src/main/java/com/android/tools/r8/retrace/internal/StackTraceElementProxyRetracerImpl.java
+++ b/src/main/java/com/android/tools/r8/retrace/internal/StackTraceElementProxyRetracerImpl.java
@@ -103,11 +103,7 @@
         .joinAmbiguous(classResult.isAmbiguous())
         .setTopFrame(true)
         .setContext(thrownExceptionElement.getContext())
-        .apply(
-            setSourceFileOnProxy(
-                thrownExceptionElement::getSourceFile,
-                thrownExceptionElement.getRetracedClass(),
-                classResult))
+        .apply(setSourceFileOnProxy(thrownExceptionElement::getSourceFile))
         .build();
   }
 
@@ -143,11 +139,7 @@
                                         .applyIf(
                                             element.hasLineNumber(),
                                             b -> b.setLineNumber(element.getLineNumber()))
-                                        .apply(
-                                            setSourceFileOnProxy(
-                                                classElement::getSourceFile,
-                                                classElement.getRetracedClass(),
-                                                classResult))
+                                        .apply(setSourceFileOnProxy(classElement::getSourceFile))
                                         .build());
                       }
                       return frameResult.stream()
@@ -160,7 +152,6 @@
                                         singleFrame ->
                                             buildProxyForRewrittenFrameElement(
                                                 element,
-                                                classResult,
                                                 proxy,
                                                 frameResult,
                                                 frameElement,
@@ -172,7 +163,6 @@
 
   private RetraceStackTraceElementProxyImpl<T, ST> buildProxyForRewrittenFrameElement(
       ST element,
-      RetraceClassResult classResult,
       RetraceStackTraceElementProxyImpl<T, ST> proxy,
       RetraceFrameResult frameResult,
       RetraceFrameElement frameElement,
@@ -188,12 +178,9 @@
         .setContext(frameElement.getRetraceStackTraceContext())
         .applyIf(
             element.hasLineNumber(),
-            builder -> {
-              builder.setLineNumber(method.getOriginalPositionOrDefault(element.getLineNumber()));
-            })
-        .apply(
-            setSourceFileOnProxy(
-                () -> frameElement.getSourceFile(method), method.getHolderClass(), classResult))
+            builder ->
+                builder.setLineNumber(method.getOriginalPositionOrDefault(element.getLineNumber())))
+        .apply(setSourceFileOnProxy(() -> frameElement.getSourceFile(method)))
         .build();
   }
 
@@ -213,13 +200,12 @@
                           .map(
                               fieldElement ->
                                   buildProxyForRewrittenFieldElement(
-                                      classResult, proxy, retraceFieldResult, fieldElement));
+                                      proxy, retraceFieldResult, fieldElement));
                     }))
         .build();
   }
 
   private RetraceStackTraceElementProxyImpl<T, ST> buildProxyForRewrittenFieldElement(
-      RetraceClassResult classResult,
       RetraceStackTraceElementProxyImpl<T, ST> proxy,
       RetraceFieldResult retraceFieldResult,
       RetraceFieldElement fieldElement) {
@@ -229,27 +215,18 @@
         .setRetracedField(fieldElement.getField())
         .joinAmbiguous(retraceFieldResult.isAmbiguous())
         .setTopFrame(true)
-        .apply(
-            setSourceFileOnProxy(
-                fieldElement::getSourceFile, fieldElement.getField().getHolderClass(), classResult))
+        .apply(setSourceFileOnProxy(fieldElement::getSourceFile))
         .build();
   }
 
   private Consumer<RetraceStackTraceElementProxyImpl.Builder<T, ST>> setSourceFileOnProxy(
-      Supplier<RetracedSourceFile> sourceFile,
-      RetracedClassReference classReference,
-      RetraceClassResult classResult) {
+      Supplier<RetracedSourceFile> sourceFile) {
     return proxy -> {
       ST original = proxy.originalElement;
       if (!original.hasSourceFile()) {
         return;
       }
-      RetracedSourceFile retracedSourceFile = sourceFile.get();
-      proxy.setSourceFile(
-          retracedSourceFile.hasRetraceResult()
-              ? retracedSourceFile.getSourceFile()
-              : RetraceUtils.inferSourceFile(
-                  classReference.getTypeName(), original.getSourceFile(), classResult.isEmpty()));
+      proxy.setSourceFile(sourceFile.get());
     };
   }
 
@@ -351,7 +328,7 @@
     private final RetracedFieldReference retracedField;
     private final RetracedTypeReference fieldOrReturnType;
     private final List<RetracedTypeReference> methodArguments;
-    private final String sourceFile;
+    private final RetracedSourceFile sourceFile;
     private final int lineNumber;
     private final boolean isAmbiguous;
     private final boolean isTopFrame;
@@ -364,7 +341,7 @@
         RetracedFieldReference retracedField,
         RetracedTypeReference fieldOrReturnType,
         List<RetracedTypeReference> methodArguments,
-        String sourceFile,
+        RetracedSourceFile sourceFile,
         int lineNumber,
         boolean isAmbiguous,
         boolean isTopFrame,
@@ -460,6 +437,16 @@
 
     @Override
     public String getSourceFile() {
+      if (sourceFile == null) {
+        assert originalItem.getSourceFile() == null;
+        return null;
+      }
+      return sourceFile.getOrInferSourceFile(
+          originalItem.getSourceFile() == null ? "" : originalItem.getSourceFile());
+    }
+
+    @Override
+    public RetracedSourceFile getRetracedSourceFile() {
       return sourceFile;
     }
 
@@ -549,7 +536,7 @@
       private RetracedFieldReference retracedField;
       private RetracedTypeReference fieldOrReturnType;
       private List<RetracedTypeReference> methodArguments;
-      private String sourceFile;
+      private RetracedSourceFile sourceFile;
       private int lineNumber = -1;
       private boolean isAmbiguous;
       private boolean isTopFrame;
@@ -584,7 +571,8 @@
         return this;
       }
 
-      private Builder<T, ST> setSourceFile(String sourceFile) {
+      private Builder<T, ST> setSourceFile(RetracedSourceFile sourceFile) {
+        assert sourceFile != null;
         this.sourceFile = sourceFile;
         return this;
       }
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 3bab5ca..472680e 100644
--- a/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
+++ b/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
@@ -12,6 +12,7 @@
 import com.android.tools.r8.features.ClassToFeatureSplitMap;
 import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
 import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.Definition;
 import com.android.tools.r8.graph.DexCallSite;
 import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexClassAndField;
@@ -873,38 +874,36 @@
     return false;
   }
 
-  public boolean isFieldRead(DexEncodedField encodedField) {
+  public boolean isFieldRead(DexClassAndField field) {
     assert checkIfObsolete();
-    DexField field = encodedField.getReference();
-    FieldAccessInfo info = getFieldAccessInfoCollection().get(field);
+    FieldAccessInfo info = getFieldAccessInfoCollection().get(field.getReference());
     if (info != null && info.isRead()) {
       return true;
     }
-    if (isPinned(encodedField)) {
+    if (isPinned(field)) {
       return true;
     }
-    // For library classes we don't know whether a field is read.
-    return isLibraryOrClasspathField(encodedField);
+    // For non-program classes we don't know whether a field is read.
+    return !field.isProgramField();
   }
 
-  public boolean isFieldWritten(DexEncodedField encodedField) {
+  public boolean isFieldWritten(DexClassAndField field) {
     assert checkIfObsolete();
-    return isFieldWrittenByFieldPutInstruction(encodedField) || isPinned(encodedField);
+    return isFieldWrittenByFieldPutInstruction(field) || isPinned(field);
   }
 
-  public boolean isFieldWrittenByFieldPutInstruction(DexEncodedField encodedField) {
+  public boolean isFieldWrittenByFieldPutInstruction(DexClassAndField field) {
     assert checkIfObsolete();
-    DexField field = encodedField.getReference();
-    FieldAccessInfo info = getFieldAccessInfoCollection().get(field);
+    FieldAccessInfo info = getFieldAccessInfoCollection().get(field.getReference());
     if (info != null && info.isWritten()) {
       // The field is written directly by the program itself.
       return true;
     }
-    // For library classes we don't know whether a field is rewritten.
-    return isLibraryOrClasspathField(encodedField);
+    // For non-program classes we don't know whether a field is rewritten.
+    return !field.isProgramField();
   }
 
-  public boolean isFieldOnlyWrittenInMethod(DexEncodedField field, DexEncodedMethod method) {
+  public boolean isFieldOnlyWrittenInMethod(DexClassAndField field, DexEncodedMethod method) {
     assert checkIfObsolete();
     assert isFieldWritten(field) : "Expected field `" + field.toSourceString() + "` to be written";
     if (isPinned(field)) {
@@ -914,7 +913,7 @@
   }
 
   public boolean isFieldOnlyWrittenInMethodIgnoringPinning(
-      DexEncodedField field, DexEncodedMethod method) {
+      DexClassAndField field, DexEncodedMethod method) {
     assert checkIfObsolete();
     assert isFieldWritten(field) : "Expected field `" + field.toSourceString() + "` to be written";
     FieldAccessInfo fieldAccessInfo = getFieldAccessInfoCollection().get(field.getReference());
@@ -924,10 +923,6 @@
   }
 
   public boolean isInstanceFieldWrittenOnlyInInstanceInitializers(DexClassAndField field) {
-    return isInstanceFieldWrittenOnlyInInstanceInitializers(field.getDefinition());
-  }
-
-  public boolean isInstanceFieldWrittenOnlyInInstanceInitializers(DexEncodedField field) {
     assert checkIfObsolete();
     assert isFieldWritten(field) : "Expected field `" + field.toSourceString() + "` to be written";
     if (isPinned(field)) {
@@ -946,7 +941,7 @@
                     .isOrWillBeInlinedIntoInstanceInitializer(dexItemFactory()));
   }
 
-  public boolean isStaticFieldWrittenOnlyInEnclosingStaticInitializer(DexEncodedField field) {
+  public boolean isStaticFieldWrittenOnlyInEnclosingStaticInitializer(DexClassAndField field) {
     assert checkIfObsolete();
     assert isFieldWritten(field) : "Expected field `" + field.toSourceString() + "` to be written";
     DexEncodedMethod staticInitializer =
@@ -954,14 +949,6 @@
     return staticInitializer != null && isFieldOnlyWrittenInMethod(field, staticInitializer);
   }
 
-  public boolean mayPropagateArgumentsTo(ProgramMethod method) {
-    DexMethod reference = method.getReference();
-    return method.getDefinition().hasCode()
-        && !method.getDefinition().isLibraryMethodOverride().isPossiblyTrue()
-        && !neverReprocess.contains(reference)
-        && !keepInfo.getMethodInfo(method).isPinned(options());
-  }
-
   public boolean mayPropagateValueFor(
       AppView<AppInfoWithLiveness> appView, DexClassAndMember<?, ?> member) {
     assert checkIfObsolete();
@@ -997,11 +984,6 @@
     return true;
   }
 
-  private boolean isLibraryOrClasspathField(DexEncodedField field) {
-    DexClass holder = definitionFor(field.getHolderType());
-    return holder == null || holder.isLibraryClass() || holder.isClasspathClass();
-  }
-
   public boolean isInstantiatedInterface(DexProgramClass clazz) {
     assert checkIfObsolete();
     return objectAllocationInfoCollection.isInterfaceWithUnknownSubtypeHierarchy(clazz);
@@ -1075,9 +1057,10 @@
     return keepInfo.isPinned(clazz, options());
   }
 
-  public boolean isPinned(ProgramDefinition definition) {
+  public boolean isPinned(Definition definition) {
     assert definition != null;
-    return keepInfo.isPinned(definition, options());
+    return definition.isProgramDefinition()
+        && keepInfo.isPinned(definition.asProgramDefinition(), options());
   }
 
   public boolean hasPinnedInstanceInitializer(DexType type) {
diff --git a/src/main/java/com/android/tools/r8/shaking/KeepFieldInfo.java b/src/main/java/com/android/tools/r8/shaking/KeepFieldInfo.java
index 312b61e..db53845 100644
--- a/src/main/java/com/android/tools/r8/shaking/KeepFieldInfo.java
+++ b/src/main/java/com/android/tools/r8/shaking/KeepFieldInfo.java
@@ -25,10 +25,12 @@
   }
 
   private final boolean allowFieldTypeStrengthening;
+  private final boolean allowRedundantFieldLoadElimination;
 
   private KeepFieldInfo(Builder builder) {
     super(builder);
     this.allowFieldTypeStrengthening = builder.isFieldTypeStrengtheningAllowed();
+    this.allowRedundantFieldLoadElimination = builder.isRedundantFieldLoadEliminationAllowed();
   }
 
   // This builder is not private as there are known instances where it is safe to modify keep info
@@ -46,6 +48,14 @@
     return allowFieldTypeStrengthening;
   }
 
+  public boolean isRedundantFieldLoadEliminationAllowed(GlobalKeepInfoConfiguration configuration) {
+    return internalIsRedundantFieldLoadEliminationAllowed();
+  }
+
+  boolean internalIsRedundantFieldLoadEliminationAllowed() {
+    return allowRedundantFieldLoadElimination;
+  }
+
   public Joiner joiner() {
     assert !isTop();
     return new Joiner(this);
@@ -64,6 +74,7 @@
   public static class Builder extends KeepInfo.Builder<Builder, KeepFieldInfo> {
 
     private boolean allowFieldTypeStrengthening;
+    private boolean allowRedundantFieldLoadElimination;
 
     private Builder() {
       super();
@@ -72,16 +83,20 @@
     private Builder(KeepFieldInfo original) {
       super(original);
       allowFieldTypeStrengthening = original.internalIsFieldTypeStrengtheningAllowed();
+      allowRedundantFieldLoadElimination =
+          original.internalIsRedundantFieldLoadEliminationAllowed();
     }
 
     @Override
     public Builder makeTop() {
-      return super.makeTop().disallowFieldTypeStrengthening();
+      return super.makeTop()
+          .disallowFieldTypeStrengthening()
+          .disallowRedundantFieldLoadElimination();
     }
 
     @Override
     public Builder makeBottom() {
-      return super.makeBottom().allowFieldTypeStrengthening();
+      return super.makeBottom().allowFieldTypeStrengthening().allowRedundantFieldLoadElimination();
     }
 
     public boolean isFieldTypeStrengtheningAllowed() {
@@ -101,6 +116,24 @@
       return setAllowFieldTypeStrengthening(false);
     }
 
+    public boolean isRedundantFieldLoadEliminationAllowed() {
+      return allowRedundantFieldLoadElimination;
+    }
+
+    public Builder setAllowRedundantFieldLoadElimination(
+        boolean allowRedundantFieldLoadElimination) {
+      this.allowRedundantFieldLoadElimination = allowRedundantFieldLoadElimination;
+      return self();
+    }
+
+    public Builder allowRedundantFieldLoadElimination() {
+      return setAllowRedundantFieldLoadElimination(true);
+    }
+
+    public Builder disallowRedundantFieldLoadElimination() {
+      return setAllowRedundantFieldLoadElimination(false);
+    }
+
     @Override
     public KeepFieldInfo getTopInfo() {
       return TOP;
@@ -124,7 +157,9 @@
     @Override
     boolean internalIsEqualTo(KeepFieldInfo other) {
       return super.internalIsEqualTo(other)
-          && isFieldTypeStrengtheningAllowed() == other.internalIsFieldTypeStrengtheningAllowed();
+          && isFieldTypeStrengtheningAllowed() == other.internalIsFieldTypeStrengtheningAllowed()
+          && isRedundantFieldLoadEliminationAllowed()
+              == other.internalIsRedundantFieldLoadEliminationAllowed();
     }
 
     @Override
@@ -144,6 +179,11 @@
       return self();
     }
 
+    public Joiner disallowRedundantFieldLoadElimination() {
+      builder.disallowRedundantFieldLoadElimination();
+      return self();
+    }
+
     @Override
     public Joiner asFieldJoiner() {
       return this;
@@ -155,7 +195,10 @@
       return super.merge(joiner)
           .applyIf(
               !joiner.builder.isFieldTypeStrengtheningAllowed(),
-              KeepFieldInfo.Joiner::disallowFieldTypeStrengthening);
+              KeepFieldInfo.Joiner::disallowFieldTypeStrengthening)
+          .applyIf(
+              !joiner.builder.isRedundantFieldLoadEliminationAllowed(),
+              KeepFieldInfo.Joiner::disallowRedundantFieldLoadElimination);
     }
 
     @Override
diff --git a/src/main/java/com/android/tools/r8/shaking/NoRedundantFieldLoadEliminationRule.java b/src/main/java/com/android/tools/r8/shaking/NoRedundantFieldLoadEliminationRule.java
new file mode 100644
index 0000000..87c5ee0
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/shaking/NoRedundantFieldLoadEliminationRule.java
@@ -0,0 +1,85 @@
+// Copyright (c) 2023, 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.shaking;
+
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.position.Position;
+import java.util.List;
+
+public class NoRedundantFieldLoadEliminationRule
+    extends NoOptimizationBaseRule<NoRedundantFieldLoadEliminationRule> {
+
+  public static final String RULE_NAME = "noredundantfieldloadelimination";
+
+  public static class Builder
+      extends NoOptimizationBaseRule.Builder<NoRedundantFieldLoadEliminationRule, Builder> {
+
+    Builder() {
+      super();
+    }
+
+    @Override
+    public NoRedundantFieldLoadEliminationRule.Builder self() {
+      return this;
+    }
+
+    @Override
+    public NoRedundantFieldLoadEliminationRule build() {
+      return new NoRedundantFieldLoadEliminationRule(
+          origin,
+          getPosition(),
+          source,
+          buildClassAnnotations(),
+          classAccessFlags,
+          negatedClassAccessFlags,
+          classTypeNegated,
+          classType,
+          classNames,
+          buildInheritanceAnnotations(),
+          inheritanceClassName,
+          inheritanceIsExtends,
+          memberRules);
+    }
+  }
+
+  NoRedundantFieldLoadEliminationRule(
+      Origin origin,
+      Position position,
+      String source,
+      List<ProguardTypeMatcher> classAnnotations,
+      ProguardAccessFlags classAccessFlags,
+      ProguardAccessFlags negatedClassAccessFlags,
+      boolean classTypeNegated,
+      ProguardClassType classType,
+      ProguardClassNameList classNames,
+      List<ProguardTypeMatcher> inheritanceAnnotations,
+      ProguardTypeMatcher inheritanceClassName,
+      boolean inheritanceIsExtends,
+      List<ProguardMemberRule> memberRules) {
+    super(
+        origin,
+        position,
+        source,
+        classAnnotations,
+        classAccessFlags,
+        negatedClassAccessFlags,
+        classTypeNegated,
+        classType,
+        classNames,
+        inheritanceAnnotations,
+        inheritanceClassName,
+        inheritanceIsExtends,
+        memberRules);
+  }
+
+  public static Builder builder() {
+    return new Builder();
+  }
+
+  @Override
+  String typeString() {
+    return RULE_NAME;
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardAccessFlags.java b/src/main/java/com/android/tools/r8/shaking/ProguardAccessFlags.java
index 9bd37a5..cdf0264 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardAccessFlags.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardAccessFlags.java
@@ -132,8 +132,9 @@
     return isSet(Constants.ACC_FINAL);
   }
 
-  public void setAbstract() {
+  public ProguardAccessFlags setAbstract() {
     set(Constants.ACC_ABSTRACT);
+    return this;
   }
 
   public boolean isAbstract() {
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
index c1de766..2c51922 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
@@ -599,6 +599,12 @@
           configurationBuilder.addRule(rule);
           return true;
         }
+        if (acceptString(NoRedundantFieldLoadEliminationRule.RULE_NAME)) {
+          ProguardConfigurationRule rule =
+              parseNoOptimizationRule(optionStart, NoRedundantFieldLoadEliminationRule.builder());
+          configurationBuilder.addRule(rule);
+          return true;
+        }
         if (acceptString(NoReturnTypeStrengtheningRule.RULE_NAME)) {
           ProguardConfigurationRule rule =
               parseNoOptimizationRule(optionStart, NoReturnTypeStrengtheningRule.builder());
@@ -2378,6 +2384,10 @@
       this.wildcards = wildcards;
     }
 
+    static IdentifierPatternWithWildcards init() {
+      return withoutWildcards("<init>");
+    }
+
     static IdentifierPatternWithWildcards withoutWildcards(String pattern) {
       return new IdentifierPatternWithWildcards(pattern, ImmutableList.of());
     }
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationUtils.java b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationUtils.java
index 94ab239..9b0ed2e 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationUtils.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationUtils.java
@@ -4,17 +4,53 @@
 
 package com.android.tools.r8.shaking;
 
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.shaking.ProguardConfigurationParser.IdentifierPatternWithWildcards;
 import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.LongInterval;
 import com.google.common.collect.ImmutableList;
+import java.util.ArrayList;
+import java.util.Collections;
 import java.util.List;
 
 public class ProguardConfigurationUtils {
 
-  public static ProguardAssumeNoSideEffectRule buildAssumeNoSideEffectsRuleForApiLevel(
+  public static List<ProguardConfigurationRule> synthesizeRules(AppView<?> appView) {
+    List<ProguardConfigurationRule> synthesizedRules = new ArrayList<>();
+    DexItemFactory factory = appView.dexItemFactory();
+    InternalOptions options = appView.options();
+    // Add synthesized -assumenosideeffects from min api if relevant.
+    if (options.isGeneratingDex()) {
+      if (!hasExplicitAssumeValuesOrAssumeNoSideEffectsRuleForMinSdk(
+          factory, options.getProguardConfiguration().getRules())) {
+        synthesizedRules.add(
+            buildAssumeNoSideEffectsRuleForApiLevel(factory, options.getMinApiLevel()));
+      }
+    }
+    // Add synthesized -keepclassmembers rules for the default initializer of classes that inherit
+    // from android.app.Fragment and android.app.ZygotePreload. This is needed since the Android
+    // Platform may reflectively access these instance initializers.
+    DexClass androidAppFragment =
+        appView.appInfo().definitionForWithoutExistenceAssert(factory.androidAppFragment);
+    if (androidAppFragment != null) {
+      synthesizedRules.add(
+          buildKeepClassMembersNoShrinkingOfInitializerOnSubclasses(factory, androidAppFragment));
+    }
+    DexClass androidAppZygotePreload =
+        appView.appInfo().definitionForWithoutExistenceAssert(factory.androidAppZygotePreload);
+    if (androidAppZygotePreload != null) {
+      synthesizedRules.add(
+          buildKeepClassMembersNoShrinkingOfInitializerOnSubclasses(
+              factory, androidAppZygotePreload));
+    }
+    return synthesizedRules;
+  }
+
+  private static ProguardAssumeNoSideEffectRule buildAssumeNoSideEffectsRuleForApiLevel(
       DexItemFactory factory, AndroidApiLevel apiLevel) {
     Origin synthesizedFromApiLevel =
         new Origin(Origin.root()) {
@@ -53,7 +89,7 @@
    * Check if an explicit rule matching the field public static final int
    * android.os.Build$VERSION.SDK_INT is present.
    */
-  public static boolean hasExplicitAssumeValuesOrAssumeNoSideEffectsRuleForMinSdk(
+  private static boolean hasExplicitAssumeValuesOrAssumeNoSideEffectsRuleForMinSdk(
       DexItemFactory factory, List<ProguardConfigurationRule> rules) {
     for (ProguardConfigurationRule rule : rules) {
       if (!(rule instanceof ProguardAssumeValuesRule
@@ -108,4 +144,28 @@
     }
     return false;
   }
+
+  // -keepclassmembers,allow* !abstract class * extends T { void <init>(); }
+  private static ProguardKeepRule buildKeepClassMembersNoShrinkingOfInitializerOnSubclasses(
+      DexItemFactory factory, DexClass clazz) {
+    return ProguardKeepRule.builder()
+        .setClassNames(ProguardClassNameList.singletonList(ProguardTypeMatcher.allClassesMatcher()))
+        .setClassType(ProguardClassType.CLASS)
+        .setInheritanceClassName(ProguardTypeMatcher.create(clazz.getType()))
+        .setInheritanceIsExtends(!clazz.isInterface())
+        .setMemberRules(
+            Collections.singletonList(
+                ProguardMemberRule.builder()
+                    .setRuleType(ProguardMemberType.INIT)
+                    .setName(IdentifierPatternWithWildcards.init())
+                    .setArguments(Collections.emptyList())
+                    .setTypeMatcher(ProguardTypeMatcher.create(factory.voidType))
+                    .build()))
+        .setNegatedClassAccessFlags(new ProguardAccessFlags().setAbstract())
+        .setOrigin(clazz.getOrigin())
+        .setType(ProguardKeepRuleType.KEEP_CLASS_MEMBERS)
+        .updateModifiers(
+            modifiersBuilder -> modifiersBuilder.setAllowsAll().setAllowsShrinking(false).build())
+        .build();
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardKeepRuleModifiers.java b/src/main/java/com/android/tools/r8/shaking/ProguardKeepRuleModifiers.java
index a2619f7..ec825c3 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardKeepRuleModifiers.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardKeepRuleModifiers.java
@@ -18,6 +18,16 @@
 
     private Builder() {}
 
+    public Builder setAllowsAll() {
+      setAllowsAccessModification(true);
+      setAllowsAnnotationRemoval(true);
+      setAllowsObfuscation(true);
+      setAllowsOptimization(true);
+      setAllowsRepackaging(true);
+      setAllowsShrinking(true);
+      return this;
+    }
+
     public Builder setAllowsAccessModification(boolean allowsAccessModification) {
       this.allowsAccessModification = allowsAccessModification;
       return this;
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardTypeMatcher.java b/src/main/java/com/android/tools/r8/shaking/ProguardTypeMatcher.java
index 19bd876..fee5278 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardTypeMatcher.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardTypeMatcher.java
@@ -125,6 +125,10 @@
     return new MatchSpecificType(type);
   }
 
+  public static ProguardTypeMatcher allClassesMatcher() {
+    return MatchClassTypes.MATCH_CLASS_TYPES;
+  }
+
   public static ProguardTypeMatcher defaultAllMatcher() {
     return MatchAllTypes.MATCH_ALL_TYPES;
   }
diff --git a/src/main/java/com/android/tools/r8/shaking/RootSetUtils.java b/src/main/java/com/android/tools/r8/shaking/RootSetUtils.java
index 2b6c654..a8fa6fb 100644
--- a/src/main/java/com/android/tools/r8/shaking/RootSetUtils.java
+++ b/src/main/java/com/android/tools/r8/shaking/RootSetUtils.java
@@ -288,7 +288,8 @@
           markMatchingOverriddenMethods(clazz, memberKeepRules, rule, null, true, ifRule);
           markMatchingVisibleFields(clazz, memberKeepRules, rule, null, true, ifRule);
         }
-      } else if (rule instanceof NoFieldTypeStrengtheningRule) {
+      } else if (rule instanceof NoFieldTypeStrengtheningRule
+          || rule instanceof NoRedundantFieldLoadEliminationRule) {
         markMatchingFields(clazz, memberKeepRules, rule, null, ifRule);
       } else if (rule instanceof InlineRule
           || rule instanceof KeepConstantArgumentRule
@@ -1237,6 +1238,13 @@
             .asFieldJoiner()
             .disallowFieldTypeStrengthening();
         context.markAsUsed();
+      } else if (context instanceof NoRedundantFieldLoadEliminationRule) {
+        assert item.isProgramField();
+        dependentMinimumKeepInfo
+            .getOrCreateUnconditionalMinimumKeepInfoFor(item.getReference())
+            .asFieldJoiner()
+            .disallowRedundantFieldLoadElimination();
+        context.markAsUsed();
       } else if (context instanceof NoUnusedInterfaceRemovalRule) {
         noUnusedInterfaceRemoval.add(item.asClass().type);
         context.markAsUsed();
@@ -2003,9 +2011,9 @@
                 if (field != null
                     && (field.getAccessFlags().isStatic()
                         || isKeptDirectlyOrIndirectly(field.getHolderType(), appView))) {
-                  assert appView.appInfo().isFieldRead(field.getDefinition())
+                  assert appView.appInfo().isFieldRead(field)
                       : "Expected kept field `" + fieldReference.toSourceString() + "` to be read";
-                  assert appView.appInfo().isFieldWritten(field.getDefinition())
+                  assert appView.appInfo().isFieldWritten(field)
                       : "Expected kept field `"
                           + fieldReference.toSourceString()
                           + "` to be written";
diff --git a/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java b/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
index 150e7c8..b1673ba 100644
--- a/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
+++ b/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
@@ -1208,12 +1208,12 @@
       builder.addFormalTypeParameters(targetSignature.getFormalTypeParameters());
       if (!source.isInterface()) {
         if (rewrittenSource.hasSignature()) {
-          builder.setSuperClassSignature(rewrittenSource.superClassSignature());
+          builder.setSuperClassSignature(rewrittenSource.getSuperClassSignatureOrNull());
         } else {
           builder.setSuperClassSignature(new ClassTypeSignature(source.superType));
         }
       } else {
-        builder.setSuperClassSignature(targetSignature.superClassSignature());
+        builder.setSuperClassSignature(targetSignature.getSuperClassSignatureOrNull());
       }
       // Compute the seen set for interfaces to add. This is similar to the merging of interfaces
       // but allow us to maintain the type arguments.
@@ -1221,26 +1221,26 @@
       if (source.isInterface()) {
         seenInterfaces.add(source.type);
       }
-      for (ClassTypeSignature iFace : targetSignature.superInterfaceSignatures()) {
+      for (ClassTypeSignature iFace : targetSignature.getSuperInterfaceSignatures()) {
         if (seenInterfaces.add(iFace.type())) {
-          builder.addInterface(iFace);
+          builder.addSuperInterfaceSignature(iFace);
         }
       }
       if (rewrittenSource.hasSignature()) {
-        for (ClassTypeSignature iFace : rewrittenSource.superInterfaceSignatures()) {
+        for (ClassTypeSignature iFace : rewrittenSource.getSuperInterfaceSignatures()) {
           if (!seenInterfaces.contains(iFace.type())) {
-            builder.addInterface(iFace);
+            builder.addSuperInterfaceSignature(iFace);
           }
         }
       } else {
         // Synthesize raw uses of interfaces to align with the actual class
         for (DexType iFace : source.interfaces) {
           if (!seenInterfaces.contains(iFace)) {
-            builder.addInterface(new ClassTypeSignature(iFace));
+            builder.addSuperInterfaceSignature(new ClassTypeSignature(iFace));
           }
         }
       }
-      target.setClassSignature(builder.build());
+      target.setClassSignature(builder.build(appView.dexItemFactory()));
 
       // Go through all type-variable references for members and update them.
       CollectionUtils.forEach(
@@ -1273,7 +1273,9 @@
       // We can assert proper structure below because the generic signature validator has run
       // before and pruned invalid signatures.
       List<FieldTypeSignature> genericArgumentsToSuperType =
-          target.getClassSignature().getGenericArgumentsToSuperType(source.type);
+          target
+              .getClassSignature()
+              .getGenericArgumentsToSuperType(source.type, appView.dexItemFactory());
       if (genericArgumentsToSuperType == null) {
         assert false : "Type should be present in generic signature";
         return null;
diff --git a/src/main/java/com/android/tools/r8/synthesis/SyntheticItems.java b/src/main/java/com/android/tools/r8/synthesis/SyntheticItems.java
index 24dab29..9f7b7f2 100644
--- a/src/main/java/com/android/tools/r8/synthesis/SyntheticItems.java
+++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticItems.java
@@ -1018,6 +1018,11 @@
     SynthesizingContext outerContext = SynthesizingContext.fromType(globalType);
     DexProgramClass globalSynthetic =
         internalEnsureFixedProgramClass(kind, fn, onCreationConsumer, outerContext, appView);
+    Consumer<DexProgramClass> globalSyntheticCreatedCallback =
+        appView.options().testing.globalSyntheticCreatedCallback;
+    if (globalSyntheticCreatedCallback != null) {
+      globalSyntheticCreatedCallback.accept(globalSynthetic);
+    }
     addGlobalContexts(globalSynthetic.getType(), contexts);
     return globalSynthetic;
   }
diff --git a/src/main/java/com/android/tools/r8/utils/CollectionUtils.java b/src/main/java/com/android/tools/r8/utils/CollectionUtils.java
index f5eea66..9a9d27c 100644
--- a/src/main/java/com/android/tools/r8/utils/CollectionUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/CollectionUtils.java
@@ -20,6 +20,14 @@
     return collection;
   }
 
+  public static <T> T getFirst(Collection<T> collection) {
+    return collection.iterator().next();
+  }
+
+  public static <T> T getFirstOrDefault(Collection<T> collection, T defaultValue) {
+    return collection.isEmpty() ? defaultValue : getFirst(collection);
+  }
+
   public static <T> Set<T> mergeSets(Collection<T> first, Collection<T> second) {
     ImmutableSet.Builder<T> builder = ImmutableSet.builder();
     builder.addAll(first);
diff --git a/src/main/java/com/android/tools/r8/utils/DexClassAndFieldEquivalence.java b/src/main/java/com/android/tools/r8/utils/DexClassAndFieldEquivalence.java
new file mode 100644
index 0000000..8b36c33
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/utils/DexClassAndFieldEquivalence.java
@@ -0,0 +1,29 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.utils;
+
+import com.android.tools.r8.graph.DexClassAndField;
+import com.google.common.base.Equivalence;
+
+public class DexClassAndFieldEquivalence extends Equivalence<DexClassAndField> {
+
+  private static final DexClassAndFieldEquivalence INSTANCE = new DexClassAndFieldEquivalence();
+
+  private DexClassAndFieldEquivalence() {}
+
+  public static DexClassAndFieldEquivalence get() {
+    return INSTANCE;
+  }
+
+  @Override
+  protected boolean doEquivalent(DexClassAndField field, DexClassAndField other) {
+    return field.getDefinition() == other.getDefinition();
+  }
+
+  @Override
+  protected int doHash(DexClassAndField field) {
+    return field.getReference().hashCode();
+  }
+}
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 615c439..8a733a5 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -890,7 +890,7 @@
       new OpenClosedInterfacesOptions();
   private final ProtoShrinkingOptions protoShrinking = new ProtoShrinkingOptions();
   private final RedundantBridgeRemovalOptions redundantBridgeRemovalOptions =
-      new RedundantBridgeRemovalOptions(this);
+      new RedundantBridgeRemovalOptions();
   private final KotlinOptimizationOptions kotlinOptimizationOptions =
       new KotlinOptimizationOptions();
   private final ApiModelTestingOptions apiModelTestingOptions = new ApiModelTestingOptions();
@@ -1626,6 +1626,8 @@
 
   public static class InlinerOptions {
 
+    public boolean enableConstructorInlining = true;
+
     public boolean enableInlining =
         !parseSystemPropertyForDevelopmentOrDefault("com.android.tools.r8.disableinlining", false);
 
@@ -1683,6 +1685,14 @@
       return 5;
     }
 
+    public boolean isConstructorInliningEnabled() {
+      return enableConstructorInlining;
+    }
+
+    public void setEnableConstructorInlining(boolean enableConstructorInlining) {
+      this.enableConstructorInlining = enableConstructorInlining;
+    }
+
     public boolean shouldApplyInliningToInlinee(
         AppView<?> appView, ProgramMethod inlinee, int inliningDepth) {
       if (applyInliningToInlineePredicateForTesting != null) {
@@ -2034,9 +2044,19 @@
 
     public boolean shouldApplyInliningToInlinee(
         AppView<?> appView, ProgramMethod inlinee, int inliningDepth) {
-      if (isProtoShrinkingEnabled() && inliningDepth == 1) {
-        ProtoReferences protoReferences = appView.protoShrinker().getProtoReferences();
-        return inlinee.getHolderType() == protoReferences.generatedMessageLiteType;
+      if (isProtoShrinkingEnabled()) {
+        if (appView.protoShrinker().getProtoReferences().isDynamicMethodBridge(inlinee)) {
+          return true;
+        }
+        if (inliningDepth <= 1) {
+          ProtoReferences protoReferences = appView.protoShrinker().getProtoReferences();
+          if (inlinee.getHolderType() == protoReferences.generatedMessageLiteType) {
+            return true;
+          }
+          if (inlinee.getHolder().getSuperType() == protoReferences.generatedMessageLiteType) {
+            return true;
+          }
+        }
       }
       return false;
     }
@@ -2141,6 +2161,8 @@
 
     public Consumer<DebugRepresentation> debugRepresentationCallback = null;
 
+    public Consumer<DexProgramClass> globalSyntheticCreatedCallback = null;
+
     /**
      * If this flag is enabled, we will also compute the set of possible targets for invoke-
      * interface and invoke-virtual instructions that target a library method, and add the
@@ -2541,6 +2563,10 @@
     return hasFeaturePresentFrom(AndroidApiLevel.K);
   }
 
+  public boolean canUseCanonicalizedCodeObjects() {
+    return hasFeaturePresentFrom(AndroidApiLevel.S);
+  }
+
   public CfVersion classFileVersionAfterDesugaring(CfVersion version) {
     assert isGeneratingClassFiles();
     if (!isDesugaring()) {
@@ -2960,4 +2986,8 @@
   public boolean canHaveVerifyErrorForUnknownUnusedReturnValue() {
     return isGeneratingDex() && canHaveBugPresentUntil(AndroidApiLevel.T);
   }
+
+  public boolean canInitNewInstanceUsingSuperclassConstructor() {
+    return canHaveNonReboundConstructorInvoke();
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/utils/IterableUtils.java b/src/main/java/com/android/tools/r8/utils/IterableUtils.java
index 5ca1e24e..3da57da 100644
--- a/src/main/java/com/android/tools/r8/utils/IterableUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/IterableUtils.java
@@ -48,6 +48,19 @@
     return false;
   }
 
+  public static <T> boolean anyBefore(
+      Iterable<T> iterable, Predicate<T> predicate, Predicate<T> stoppingCriterion) {
+    for (T element : iterable) {
+      if (stoppingCriterion.test(element)) {
+        return false;
+      }
+      if (predicate.test(element)) {
+        return true;
+      }
+    }
+    return false;
+  }
+
   public static <T> Iterable<T> append(Iterable<T> iterable, T element) {
     return Iterables.concat(iterable, singleton(element));
   }
diff --git a/src/main/java/com/android/tools/r8/utils/IteratorUtils.java b/src/main/java/com/android/tools/r8/utils/IteratorUtils.java
index a34ec96..0dd5b36 100644
--- a/src/main/java/com/android/tools/r8/utils/IteratorUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/IteratorUtils.java
@@ -156,8 +156,14 @@
   }
 
   public static void skip(InstructionIterator iterator, int times) {
-    for (int i = 0; i < times; i++) {
-      iterator.next();
+    if (times >= 0) {
+      for (int i = 0; i < times; i++) {
+        iterator.next();
+      }
+    } else {
+      for (int i = 0; i > times; i--) {
+        iterator.previous();
+      }
     }
   }
 
diff --git a/src/main/java/com/android/tools/r8/utils/ListUtils.java b/src/main/java/com/android/tools/r8/utils/ListUtils.java
index 47549a6..c998cf6 100644
--- a/src/main/java/com/android/tools/r8/utils/ListUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/ListUtils.java
@@ -55,12 +55,14 @@
     return result;
   }
 
-  public static <T> List<T> filter(Collection<T> list, Predicate<? super T> predicate) {
+  @SuppressWarnings("unchecked")
+  public static <S, T extends S> List<T> filter(
+      Collection<S> list, Predicate<? super S> predicate) {
     ArrayList<T> filtered = new ArrayList<>(list.size());
     list.forEach(
-        t -> {
-          if (predicate.test(t)) {
-            filtered.add(t);
+        s -> {
+          if (predicate.test(s)) {
+            filtered.add((T) s);
           }
         });
     return filtered;
diff --git a/src/main/java/com/android/tools/r8/utils/OffOrAuto.java b/src/main/java/com/android/tools/r8/utils/OffOrAuto.java
index 83d6244..3375b25 100644
--- a/src/main/java/com/android/tools/r8/utils/OffOrAuto.java
+++ b/src/main/java/com/android/tools/r8/utils/OffOrAuto.java
@@ -4,31 +4,7 @@
 
 package com.android.tools.r8.utils;
 
-import joptsimple.ValueConversionException;
-import joptsimple.ValueConverter;
-
 public enum OffOrAuto {
-  Off, Auto;
-
-  static final ValueConverter<OffOrAuto> CONVERTER = new ValueConverter<OffOrAuto>() {
-    @Override
-    public OffOrAuto convert(String input) {
-      try {
-        input = Character.toUpperCase(input.charAt(0)) + input.substring(1).toLowerCase();
-        return Enum.valueOf(OffOrAuto.class, input);
-      } catch (Exception e) {
-        throw new ValueConversionException("Value must be one of: " + valuePattern());
-      }
-    }
-
-    @Override
-    public Class<OffOrAuto> valueType() {
-      return OffOrAuto.class;
-    }
-
-    @Override
-    public String valuePattern() {
-      return "off|auto";
-    }
-  };
+  Off,
+  Auto
 }
diff --git a/src/main/java/com/android/tools/r8/utils/PartitionMapZipContainer.java b/src/main/java/com/android/tools/r8/utils/PartitionMapZipContainer.java
index 242fbcc..e2af968 100644
--- a/src/main/java/com/android/tools/r8/utils/PartitionMapZipContainer.java
+++ b/src/main/java/com/android/tools/r8/utils/PartitionMapZipContainer.java
@@ -32,7 +32,6 @@
         .setMappingPartitionFromKeySupplier(
             key -> {
               try {
-                // TODO(b/274735214): The key should exist.
                 ZipEntry entry = zipFile.getEntry(key);
                 return entry == null
                     ? EMPTY_RESULT
diff --git a/src/main/java/com/android/tools/r8/utils/ProgramFieldEquivalence.java b/src/main/java/com/android/tools/r8/utils/ProgramFieldEquivalence.java
deleted file mode 100644
index 193df46..0000000
--- a/src/main/java/com/android/tools/r8/utils/ProgramFieldEquivalence.java
+++ /dev/null
@@ -1,29 +0,0 @@
-// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
-
-package com.android.tools.r8.utils;
-
-import com.android.tools.r8.graph.ProgramField;
-import com.google.common.base.Equivalence;
-
-public class ProgramFieldEquivalence extends Equivalence<ProgramField> {
-
-  private static final ProgramFieldEquivalence INSTANCE = new ProgramFieldEquivalence();
-
-  private ProgramFieldEquivalence() {}
-
-  public static ProgramFieldEquivalence get() {
-    return INSTANCE;
-  }
-
-  @Override
-  protected boolean doEquivalent(ProgramField field, ProgramField other) {
-    return field.getDefinition() == other.getDefinition();
-  }
-
-  @Override
-  protected int doHash(ProgramField field) {
-    return field.getReference().hashCode();
-  }
-}
diff --git a/src/main/java/com/android/tools/r8/utils/StringUtils.java b/src/main/java/com/android/tools/r8/utils/StringUtils.java
index 9885ab6..af857c4 100644
--- a/src/main/java/com/android/tools/r8/utils/StringUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/StringUtils.java
@@ -11,9 +11,11 @@
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
+import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
 import java.util.Map.Entry;
+import java.util.Set;
 import java.util.function.Consumer;
 import java.util.function.Function;
 import java.util.regex.Matcher;
@@ -273,14 +275,25 @@
     return join(LINE_SEPARATOR, collection, BraceType.NONE);
   }
 
-
   public static List<String> splitLines(String content) {
     return splitLines(content, false);
   }
 
+  public static Set<String> splitLinesIntoSet(String content) {
+    Set<String> set = new HashSet<>();
+    splitLines(content, false, set::add);
+    return set;
+  }
+
   public static List<String> splitLines(String content, boolean includeTrailingEmptyLine) {
+    List<String> list = new ArrayList<>();
+    splitLines(content, includeTrailingEmptyLine, list::add);
+    return list;
+  }
+
+  private static void splitLines(
+      String content, boolean includeTrailingEmptyLine, Consumer<String> consumer) {
     int length = content.length();
-    List<String> lines = new ArrayList<>();
     int start = 0;
     for (int i = 0; i < length; i++) {
       char c = content.charAt(i);
@@ -290,16 +303,15 @@
       } else if (c != '\n') {
         continue;
       }
-      lines.add(content.substring(start, end));
+      consumer.accept(content.substring(start, end));
       start = i + 1;
     }
     if (start < length) {
       String line = content.substring(start);
       if (includeTrailingEmptyLine || !line.isEmpty()) {
-        lines.add(line);
+        consumer.accept(line);
       }
     }
-    return lines;
   }
 
   public static String zeroPrefix(int i, int width) {
diff --git a/src/main/java/com/android/tools/r8/utils/ThreadUtils.java b/src/main/java/com/android/tools/r8/utils/ThreadUtils.java
index 45c36b6..528ba57 100644
--- a/src/main/java/com/android/tools/r8/utils/ThreadUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/ThreadUtils.java
@@ -3,6 +3,8 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.utils;
 
+import static com.google.common.base.Predicates.alwaysTrue;
+
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.utils.ListUtils.ReferenceAndIntConsumer;
@@ -18,6 +20,7 @@
 import java.util.concurrent.ForkJoinPool;
 import java.util.concurrent.Future;
 import java.util.function.Consumer;
+import java.util.function.Predicate;
 
 public class ThreadUtils {
 
@@ -71,6 +74,16 @@
     return processItemsWithResults(items, (item, i) -> consumer.apply(item), executorService);
   }
 
+  public static <T, R, E extends Exception> Collection<R> processItemsWithResultsThatMatches(
+      Iterable<T> items,
+      ThrowingFunction<T, R, E> consumer,
+      Predicate<R> predicate,
+      ExecutorService executorService)
+      throws ExecutionException {
+    return processItemsWithResultsThatMatches(
+        items, (item, i) -> consumer.apply(item), predicate, executorService);
+  }
+
   public static <T, R, E extends Exception> Collection<R> processItemsWithResults(
       Iterable<T> items,
       ThrowingReferenceIntFunction<T, R, E> consumer,
@@ -79,6 +92,15 @@
     return processItemsWithResults(items::forEach, consumer, executorService);
   }
 
+  public static <T, R, E extends Exception> Collection<R> processItemsWithResultsThatMatches(
+      Iterable<T> items,
+      ThrowingReferenceIntFunction<T, R, E> consumer,
+      Predicate<R> predicate,
+      ExecutorService executorService)
+      throws ExecutionException {
+    return processItemsWithResultsThatMatches(items::forEach, consumer, predicate, executorService);
+  }
+
   public static <T, R, E extends Exception> Collection<R> processItemsWithResults(
       ForEachable<T> items, ThrowingFunction<T, R, E> consumer, ExecutorService executorService)
       throws ExecutionException {
@@ -97,7 +119,23 @@
           int index = indexSupplier.getAndIncrement();
           futures.add(executorService.submit(() -> consumer.apply(item, index)));
         });
-    return awaitFuturesWithResults(futures);
+    return awaitFuturesWithResults(futures, alwaysTrue());
+  }
+
+  public static <T, R, E extends Exception> Collection<R> processItemsWithResultsThatMatches(
+      ForEachable<T> items,
+      ThrowingReferenceIntFunction<T, R, E> consumer,
+      Predicate<R> predicate,
+      ExecutorService executorService)
+      throws ExecutionException {
+    IntBox indexSupplier = new IntBox();
+    List<Future<R>> futures = new ArrayList<>();
+    items.forEach(
+        item -> {
+          int index = indexSupplier.getAndIncrement();
+          futures.add(executorService.submit(() -> consumer.apply(item, index)));
+        });
+    return awaitFuturesWithResults(futures, predicate);
   }
 
   public static <T> void processItems(
@@ -195,13 +233,17 @@
     }
   }
 
-  public static <R> Collection<R> awaitFuturesWithResults(Collection<? extends Future<R>> futures)
-      throws ExecutionException {
-    List<R> results = new ArrayList<>(futures.size());
+  public static <R> Collection<R> awaitFuturesWithResults(
+      Collection<? extends Future<R>> futures, Predicate<R> predicate) throws ExecutionException {
+    List<R> results =
+        predicate == alwaysTrue() ? new ArrayList<>(futures.size()) : new ArrayList<>();
     Iterator<? extends Future<R>> futureIterator = futures.iterator();
     try {
       while (futureIterator.hasNext()) {
-        results.add(futureIterator.next().get());
+        R result = futureIterator.next().get();
+        if (predicate.test(result)) {
+          results.add(result);
+        }
       }
     } catch (InterruptedException e) {
       throw new RuntimeException("Interrupted while waiting for future.", e);
diff --git a/src/main/java/com/android/tools/r8/utils/collections/DexClassAndFieldMap.java b/src/main/java/com/android/tools/r8/utils/collections/DexClassAndFieldMap.java
new file mode 100644
index 0000000..ab2a69a
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/utils/collections/DexClassAndFieldMap.java
@@ -0,0 +1,30 @@
+// Copyright (c) 2023, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.utils.collections;
+
+import com.android.tools.r8.graph.DexClassAndField;
+import com.google.common.base.Equivalence.Wrapper;
+import com.google.common.collect.ImmutableMap;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.function.Supplier;
+
+public class DexClassAndFieldMap<V> extends DexClassAndFieldMapBase<DexClassAndField, V> {
+
+  private static final DexClassAndFieldMap<?> EMPTY = new DexClassAndFieldMap<>(ImmutableMap::of);
+
+  private DexClassAndFieldMap(Supplier<Map<Wrapper<DexClassAndField>, V>> backingFactory) {
+    super(backingFactory);
+  }
+
+  public static <V> DexClassAndFieldMap<V> create() {
+    return new DexClassAndFieldMap<>(HashMap::new);
+  }
+
+  @SuppressWarnings("unchecked")
+  public static <V> DexClassAndFieldMap<V> empty() {
+    return (DexClassAndFieldMap<V>) EMPTY;
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/utils/collections/DexClassAndFieldMapBase.java b/src/main/java/com/android/tools/r8/utils/collections/DexClassAndFieldMapBase.java
new file mode 100644
index 0000000..522ff17
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/utils/collections/DexClassAndFieldMapBase.java
@@ -0,0 +1,24 @@
+// Copyright (c) 2023, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.utils.collections;
+
+import com.android.tools.r8.graph.DexClassAndField;
+import com.android.tools.r8.utils.DexClassAndFieldEquivalence;
+import com.google.common.base.Equivalence.Wrapper;
+import java.util.Map;
+import java.util.function.Supplier;
+
+public abstract class DexClassAndFieldMapBase<K extends DexClassAndField, V>
+    extends DexClassAndMemberMap<K, V> {
+
+  DexClassAndFieldMapBase(Supplier<Map<Wrapper<K>, V>> backingFactory) {
+    super(backingFactory);
+  }
+
+  @Override
+  Wrapper<K> wrap(K field) {
+    return DexClassAndFieldEquivalence.get().wrap(field);
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/utils/collections/ProgramMemberMap.java b/src/main/java/com/android/tools/r8/utils/collections/DexClassAndMemberMap.java
similarity index 88%
rename from src/main/java/com/android/tools/r8/utils/collections/ProgramMemberMap.java
rename to src/main/java/com/android/tools/r8/utils/collections/DexClassAndMemberMap.java
index ebba98d..2a044e5 100644
--- a/src/main/java/com/android/tools/r8/utils/collections/ProgramMemberMap.java
+++ b/src/main/java/com/android/tools/r8/utils/collections/DexClassAndMemberMap.java
@@ -4,7 +4,7 @@
 
 package com.android.tools.r8.utils.collections;
 
-import com.android.tools.r8.graph.ProgramMember;
+import com.android.tools.r8.graph.DexClassAndMember;
 import com.google.common.base.Equivalence.Wrapper;
 import java.util.Map;
 import java.util.function.BiConsumer;
@@ -13,15 +13,15 @@
 import java.util.function.Function;
 import java.util.function.Supplier;
 
-public abstract class ProgramMemberMap<K extends ProgramMember<?, ?>, V> {
+public abstract class DexClassAndMemberMap<K extends DexClassAndMember<?, ?>, V> {
 
   private final Map<Wrapper<K>, V> backing;
 
-  ProgramMemberMap(Supplier<Map<Wrapper<K>, V>> backingFactory) {
+  DexClassAndMemberMap(Supplier<Map<Wrapper<K>, V>> backingFactory) {
     this.backing = backingFactory.get();
   }
 
-  ProgramMemberMap(Map<Wrapper<K>, V> backing) {
+  DexClassAndMemberMap(Map<Wrapper<K>, V> backing) {
     this.backing = backing;
   }
 
diff --git a/src/main/java/com/android/tools/r8/utils/collections/ProgramFieldMap.java b/src/main/java/com/android/tools/r8/utils/collections/ProgramFieldMap.java
index dc6a4be..a8f41e2 100644
--- a/src/main/java/com/android/tools/r8/utils/collections/ProgramFieldMap.java
+++ b/src/main/java/com/android/tools/r8/utils/collections/ProgramFieldMap.java
@@ -5,14 +5,13 @@
 package com.android.tools.r8.utils.collections;
 
 import com.android.tools.r8.graph.ProgramField;
-import com.android.tools.r8.utils.ProgramFieldEquivalence;
 import com.google.common.base.Equivalence.Wrapper;
 import com.google.common.collect.ImmutableMap;
 import java.util.HashMap;
 import java.util.Map;
 import java.util.function.Supplier;
 
-public class ProgramFieldMap<V> extends ProgramMemberMap<ProgramField, V> {
+public class ProgramFieldMap<V> extends DexClassAndFieldMapBase<ProgramField, V> {
 
   private static final ProgramFieldMap<?> EMPTY = new ProgramFieldMap<>(ImmutableMap::of);
 
@@ -28,9 +27,4 @@
   public static <V> ProgramFieldMap<V> empty() {
     return (ProgramFieldMap<V>) EMPTY;
   }
-
-  @Override
-  Wrapper<ProgramField> wrap(ProgramField method) {
-    return ProgramFieldEquivalence.get().wrap(method);
-  }
 }
diff --git a/src/main/java/com/android/tools/r8/utils/collections/ProgramMethodMap.java b/src/main/java/com/android/tools/r8/utils/collections/ProgramMethodMap.java
index d4dcca6..b5c3c26 100644
--- a/src/main/java/com/android/tools/r8/utils/collections/ProgramMethodMap.java
+++ b/src/main/java/com/android/tools/r8/utils/collections/ProgramMethodMap.java
@@ -13,7 +13,7 @@
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.function.Supplier;
 
-public class ProgramMethodMap<V> extends ProgramMemberMap<ProgramMethod, V> {
+public class ProgramMethodMap<V> extends DexClassAndMemberMap<ProgramMethod, V> {
 
   private static final ProgramMethodMap<?> EMPTY = new ProgramMethodMap<>(ImmutableMap::of);
 
diff --git a/src/main/keep_retrace.txt b/src/main/keep_retrace.txt
index e8d050a..6ddb273 100644
--- a/src/main/keep_retrace.txt
+++ b/src/main/keep_retrace.txt
@@ -11,9 +11,18 @@
 -keepattributes SourceFile, LineNumberTable, InnerClasses, EnclosingMethod, Exceptions, Signature
 -keepparameternames
 
-# This is run on r8lib so keep everything in lib that is traced. That way
-# we only need a single mapping file
--keep,allowshrinking class * { *; }
+-repackageclasses com.android.tools.r8.retrace_internal
 
 # Keep all things that can be reached from the retrace api
 -keep @com.android.tools.r8.KeepForRetraceApi class * { public *; }
+
+-keep,allowshrinking @com.android.tools.r8.Keep class * { public *; }
+-keep,allowshrinking @com.android.tools.r8.KeepForSubclassing class * { public *; protected *; }
+
+-keep public class com.android.tools.r8.Version { public static final java.lang.String LABEL; }
+-keep public class com.android.tools.r8.Version { public static java.lang.String getVersionString(); }
+-keep public class com.android.tools.r8.Version { public static int getMajorVersion(); }
+-keep public class com.android.tools.r8.Version { public static int getMinorVersion(); }
+-keep public class com.android.tools.r8.Version { public static int getPatchVersion(); }
+-keep public class com.android.tools.r8.Version { public static java.lang.String getPreReleaseString(); }
+-keep public class com.android.tools.r8.Version { public static boolean isDevelopmentVersion(); }
diff --git a/src/test/examples/adaptresourcefilenames/NoInliningOfDefaultInitializer.java b/src/test/examples/adaptresourcefilenames/NoInliningOfDefaultInitializer.java
new file mode 100644
index 0000000..d7a6029
--- /dev/null
+++ b/src/test/examples/adaptresourcefilenames/NoInliningOfDefaultInitializer.java
@@ -0,0 +1,13 @@
+// Copyright (c) 2023, 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 adaptresourcefilenames;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+@Retention(RetentionPolicy.CLASS)
+@Target({ElementType.TYPE})
+public @interface NoInliningOfDefaultInitializer {}
diff --git a/src/test/examples/adaptresourcefilenames/pkg/C.java b/src/test/examples/adaptresourcefilenames/pkg/C.java
index 93d4446..85d846e 100644
--- a/src/test/examples/adaptresourcefilenames/pkg/C.java
+++ b/src/test/examples/adaptresourcefilenames/pkg/C.java
@@ -4,6 +4,9 @@
 
 package adaptresourcefilenames.pkg;
 
+import adaptresourcefilenames.NoInliningOfDefaultInitializer;
+
+@NoInliningOfDefaultInitializer
 public class C {
 
   public void method() {
diff --git a/src/test/examples/adaptresourcefilenames/pkg/innerpkg/D.java b/src/test/examples/adaptresourcefilenames/pkg/innerpkg/D.java
index 2107547..03fc12e 100644
--- a/src/test/examples/adaptresourcefilenames/pkg/innerpkg/D.java
+++ b/src/test/examples/adaptresourcefilenames/pkg/innerpkg/D.java
@@ -4,6 +4,9 @@
 
 package adaptresourcefilenames.pkg.innerpkg;
 
+import adaptresourcefilenames.NoInliningOfDefaultInitializer;
+
+@NoInliningOfDefaultInitializer
 public class D {
 
   public void method() {
diff --git a/src/test/examples/classmerging/ConflictInGeneratedNameTest.java b/src/test/examples/classmerging/ConflictInGeneratedNameTest.java
index 4ef0b3e..528f9bf 100644
--- a/src/test/examples/classmerging/ConflictInGeneratedNameTest.java
+++ b/src/test/examples/classmerging/ConflictInGeneratedNameTest.java
@@ -4,6 +4,7 @@
 
 package classmerging;
 
+
 public class ConflictInGeneratedNameTest {
   public static void main(String[] args) {
     B obj = new B();
@@ -11,7 +12,7 @@
   }
 
   public static class A {
-    @NeverPropagateValue private String name = "A";
+    @NeverPropagateValue @NoRedundantFieldLoadElimination private String name = "A";
 
     public A() {
       print("In A.<init>()");
@@ -56,8 +57,10 @@
   }
 
   public static class B extends A {
-    @NeverPropagateValue private String name = "B";
-    @NeverPropagateValue private String name$classmerging$ConflictInGeneratedNameTest$A = "C";
+    @NeverPropagateValue @NoRedundantFieldLoadElimination private String name = "B";
+
+    @NeverPropagateValue @NoRedundantFieldLoadElimination
+    private String name$classmerging$ConflictInGeneratedNameTest$A = "C";
 
     public B() {
       print("In B.<init>()");
diff --git a/src/test/examples/classmerging/NoRedundantFieldLoadElimination.java b/src/test/examples/classmerging/NoRedundantFieldLoadElimination.java
new file mode 100644
index 0000000..5251e01
--- /dev/null
+++ b/src/test/examples/classmerging/NoRedundantFieldLoadElimination.java
@@ -0,0 +1,10 @@
+// Copyright (c) 2023, 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 classmerging;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Target;
+
+@Target({ElementType.FIELD})
+public @interface NoRedundantFieldLoadElimination {}
diff --git a/src/test/examples/classmerging/keep-rules.txt b/src/test/examples/classmerging/keep-rules.txt
index 9c7188b..8b87740 100644
--- a/src/test/examples/classmerging/keep-rules.txt
+++ b/src/test/examples/classmerging/keep-rules.txt
@@ -74,3 +74,4 @@
 }
 -neverpropagatevalue class * { @classmerging.NeverPropagateValue *; }
 -nohorizontalclassmerging @classmerging.NoHorizontalClassMerging class *
+-noredundantfieldloadelimination class * { @classmerging.NoRedundantFieldLoadElimination *; }
diff --git a/src/test/examples/throwing/proguard.cfg b/src/test/examples/throwing/proguard.cfg
deleted file mode 100644
index 6de724a..0000000
--- a/src/test/examples/throwing/proguard.cfg
+++ /dev/null
@@ -1,14 +0,0 @@
-# Copyright (c) 2017, 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.
-
--keep class throwing.Throwing {
-  public static void main(java.lang.String[]);
-}
-
--keep,allowobfuscation class throwing.Overloaded {
-  *;
-}
-
--keepattributes SourceFile,LineNumberTable
-
diff --git a/src/test/examples/trivial/Trivial.java b/src/test/examples/trivial/Trivial.java
deleted file mode 100644
index b6b8299..0000000
--- a/src/test/examples/trivial/Trivial.java
+++ /dev/null
@@ -1,9 +0,0 @@
-// Copyright (c) 2016, 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 trivial;
-
-public class Trivial {
-  public static void main(String[] args) {
-  }
-}
diff --git a/src/test/examplesJava17/records/RecordBlog.java b/src/test/examplesJava17/records/RecordBlog.java
new file mode 100644
index 0000000..0b40c86
--- /dev/null
+++ b/src/test/examplesJava17/records/RecordBlog.java
@@ -0,0 +1,17 @@
+// Copyright (c) 2023, 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 records;
+
+public class RecordBlog {
+
+  record Person(String name, int age) {}
+
+  public static void main(String[] args) {
+    Person jane = new Person("Jane", 42);
+    System.out.println(jane.toString());
+    Person john = new Person("John", 42);
+    System.out.println(john.toString());
+  }
+}
diff --git a/src/test/examplesJava18/jdk8272564/A.java b/src/test/examplesJava20/jdk8272564/A.java
similarity index 100%
rename from src/test/examplesJava18/jdk8272564/A.java
rename to src/test/examplesJava20/jdk8272564/A.java
diff --git a/src/test/examplesJava18/jdk8272564/B.java b/src/test/examplesJava20/jdk8272564/B.java
similarity index 100%
rename from src/test/examplesJava18/jdk8272564/B.java
rename to src/test/examplesJava20/jdk8272564/B.java
diff --git a/src/test/examplesJava18/jdk8272564/C.java b/src/test/examplesJava20/jdk8272564/C.java
similarity index 100%
rename from src/test/examplesJava18/jdk8272564/C.java
rename to src/test/examplesJava20/jdk8272564/C.java
diff --git a/src/test/examplesJava18/jdk8272564/I.java b/src/test/examplesJava20/jdk8272564/I.java
similarity index 100%
rename from src/test/examplesJava18/jdk8272564/I.java
rename to src/test/examplesJava20/jdk8272564/I.java
diff --git a/src/test/examplesJava18/jdk8272564/J.java b/src/test/examplesJava20/jdk8272564/J.java
similarity index 100%
rename from src/test/examplesJava18/jdk8272564/J.java
rename to src/test/examplesJava20/jdk8272564/J.java
diff --git a/src/test/examplesJava18/jdk8272564/K.java b/src/test/examplesJava20/jdk8272564/K.java
similarity index 100%
rename from src/test/examplesJava18/jdk8272564/K.java
rename to src/test/examplesJava20/jdk8272564/K.java
diff --git a/src/test/examplesJava18/jdk8272564/Main.java b/src/test/examplesJava20/jdk8272564/Main.java
similarity index 100%
rename from src/test/examplesJava18/jdk8272564/Main.java
rename to src/test/examplesJava20/jdk8272564/Main.java
diff --git a/src/test/java/com/android/tools/r8/ExtractMarkerTest.java b/src/test/java/com/android/tools/r8/ExtractMarkerTest.java
index 1f0ef3c..c7a260a 100644
--- a/src/test/java/com/android/tools/r8/ExtractMarkerTest.java
+++ b/src/test/java/com/android/tools/r8/ExtractMarkerTest.java
@@ -13,6 +13,7 @@
 import com.android.tools.r8.graph.DexString;
 import com.android.tools.r8.utils.BooleanUtils;
 import com.android.tools.r8.utils.ExtractMarkerUtils;
+import java.nio.file.Path;
 import java.nio.file.Paths;
 import java.util.Collection;
 import java.util.Set;
@@ -23,8 +24,15 @@
 
 @RunWith(Parameterized.class)
 public class ExtractMarkerTest extends TestBase {
-  private static final String CLASS_FILE =
-      ToolHelper.EXAMPLES_BUILD_DIR + "classes/trivial/Trivial.class";
+
+  static class TestClass {
+
+    public static void main(String[] args) {
+
+    }
+  }
+
+  private static final Path CLASS_FILE = ToolHelper.getClassFileForTestClass(TestClass.class);
 
   private final TestParameters parameters;
   private boolean includeClassesChecksum;
@@ -55,7 +63,7 @@
     boolean[] testExecuted = {false};
     D8.run(
         D8Command.builder()
-            .addProgramFiles(Paths.get(CLASS_FILE))
+            .addProgramFiles(CLASS_FILE)
             .setMinApiLevel(parameters.getApiLevel().getLevel())
             .setIncludeClassesChecksum(includeClassesChecksum)
             .setProgramConsumer(
@@ -98,7 +106,7 @@
     boolean[] testExecuted = {false};
     R8.run(
         R8Command.builder()
-            .addProgramFiles(Paths.get(CLASS_FILE))
+            .addProgramFiles(CLASS_FILE)
             .addLibraryFiles(ToolHelper.getJava8RuntimeJar())
             .setMode(CompilationMode.DEBUG)
             .setDisableTreeShaking(true)
diff --git a/src/test/java/com/android/tools/r8/NoInliningOfDefaultInitializer.java b/src/test/java/com/android/tools/r8/NoInliningOfDefaultInitializer.java
new file mode 100644
index 0000000..6067415
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/NoInliningOfDefaultInitializer.java
@@ -0,0 +1,13 @@
+// Copyright (c) 2023, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+@Retention(RetentionPolicy.CLASS)
+@Target({ElementType.TYPE})
+public @interface NoInliningOfDefaultInitializer {}
diff --git a/src/test/java/com/android/tools/r8/NoRedundantFieldLoadElimination.java b/src/test/java/com/android/tools/r8/NoRedundantFieldLoadElimination.java
new file mode 100644
index 0000000..16b6ebe
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/NoRedundantFieldLoadElimination.java
@@ -0,0 +1,10 @@
+// Copyright (c) 2023, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Target;
+
+@Target({ElementType.FIELD})
+public @interface NoRedundantFieldLoadElimination {}
diff --git a/src/test/java/com/android/tools/r8/ProguardMapMarkerTest.java b/src/test/java/com/android/tools/r8/ProguardMapMarkerTest.java
index e38ecf7..74e36ab 100644
--- a/src/test/java/com/android/tools/r8/ProguardMapMarkerTest.java
+++ b/src/test/java/com/android/tools/r8/ProguardMapMarkerTest.java
@@ -14,6 +14,7 @@
 import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.ExtractMarkerUtils;
 import com.android.tools.r8.utils.VersionProperties;
+import java.nio.file.Path;
 import java.nio.file.Paths;
 import java.util.Collection;
 import java.util.HashSet;
@@ -26,10 +27,16 @@
 @RunWith(Parameterized.class)
 public class ProguardMapMarkerTest extends TestBase {
 
+  static class TestClass {
+
+    public static void main(String[] args) {
+
+    }
+  }
+
   private static final int EXPECTED_NUMBER_OF_KEYS_DEX = 6;
   private static final int EXPECTED_NUMBER_OF_KEYS_CF = 5;
-  private static final String CLASS_FILE =
-      ToolHelper.EXAMPLES_BUILD_DIR + "classes/trivial/Trivial.class";
+  private static final Path CLASS_FILE = ToolHelper.getClassFileForTestClass(TestClass.class);
 
   @Parameters(name = "{0}")
   public static TestParametersCollection data() {
@@ -60,7 +67,7 @@
     ProguardMapIds proguardMapIds = new ProguardMapIds();
     R8.run(
         R8Command.builder()
-            .addProgramFiles(Paths.get(CLASS_FILE))
+            .addProgramFiles(CLASS_FILE)
             .setDisableTreeShaking(true)
             .setProgramConsumer(
                 new DexIndexedConsumer.ForwardingConsumer(null) {
@@ -107,7 +114,7 @@
     ProguardMapIds buildIds = new ProguardMapIds();
     R8.run(
         R8Command.builder()
-            .addProgramFiles(Paths.get(CLASS_FILE))
+            .addProgramFiles(CLASS_FILE)
             .setDisableTreeShaking(true)
             .setProgramConsumer(
                 new ClassFileConsumer.ForwardingConsumer(null) {
diff --git a/src/test/java/com/android/tools/r8/R8RunExamplesTest.java b/src/test/java/com/android/tools/r8/R8RunExamplesTest.java
index 6e40eef..e5db712 100644
--- a/src/test/java/com/android/tools/r8/R8RunExamplesTest.java
+++ b/src/test/java/com/android/tools/r8/R8RunExamplesTest.java
@@ -27,37 +27,9 @@
     String[] tests = {
       "arithmetic.Arithmetic",
       "inlining.Inlining",
-      "nestedtrycatches.NestedTryCatches",
-      "regalloc.RegAlloc",
-      "returns.Returns",
-      "staticfield.StaticField",
-      "stringbuilding.StringBuilding",
-      "switches.Switches",
-      "sync.Sync",
-      "throwing.Throwing",
-      "trivial.Trivial",
-      "trycatch.TryCatch",
-      "trycatchmany.TryCatchMany",
-      "regress.Regress",
-      "regress2.Regress2",
-      "regress_37726195.Regress",
-      "regress_37658666.Regress",
-      "regress_37875803.Regress",
-      "regress_37955340.Regress",
-      "regress_62300145.Regress",
-      "regress_64881691.Regress",
-      "regress_65104300.Regress",
-      "regress_70703087.Test",
-      "regress_70736958.Test",
-      "regress_70737019.Test",
-      "regress_72361252.Test",
-      "regress_110373181.Regress",
       "memberrebinding2.Memberrebinding",
       "memberrebinding3.Memberrebinding",
       "minification.Minification",
-      "enclosingmethod.Main",
-      "enclosingmethod_proguarded.Main",
-      "switchmaps.Switches",
       "uninitializedfinal.UninitializedFinalFieldLeak",
     };
 
diff --git a/src/test/java/com/android/tools/r8/R8TestBuilder.java b/src/test/java/com/android/tools/r8/R8TestBuilder.java
index b9c6c781..c6f1d08 100644
--- a/src/test/java/com/android/tools/r8/R8TestBuilder.java
+++ b/src/test/java/com/android/tools/r8/R8TestBuilder.java
@@ -28,6 +28,7 @@
 import com.android.tools.r8.shaking.NoMethodStaticizingRule;
 import com.android.tools.r8.shaking.NoParameterReorderingRule;
 import com.android.tools.r8.shaking.NoParameterTypeStrengtheningRule;
+import com.android.tools.r8.shaking.NoRedundantFieldLoadEliminationRule;
 import com.android.tools.r8.shaking.NoReturnTypeStrengtheningRule;
 import com.android.tools.r8.shaking.NoUnusedInterfaceRemovalRule;
 import com.android.tools.r8.shaking.NoVerticalClassMergingRule;
@@ -555,6 +556,14 @@
             NoFieldTypeStrengtheningRule.RULE_NAME, NoFieldTypeStrengthening.class);
   }
 
+  public T enableNoInliningOfDefaultInitializerAnnotations() {
+    return addNoInliningOfDefaultInitializerAnnotation()
+        .addInternalKeepRules(
+            "-neverinline @"
+                + NoInliningOfDefaultInitializer.class.getTypeName()
+                + " class * { <init>(); }");
+  }
+
   public T enableNoMethodStaticizingAnnotations() {
     return addNoMethodStaticizingAnnotation()
         .addInternalMatchAnnotationOnMethodRule(
@@ -573,6 +582,12 @@
             NoParameterTypeStrengtheningRule.RULE_NAME, NoParameterTypeStrengthening.class);
   }
 
+  public T enableNoRedundantFieldLoadEliminationAnnotations() {
+    return addNoRedundantFieldLoadEliminationAnnotation()
+        .addInternalMatchAnnotationOnFieldRule(
+            NoRedundantFieldLoadEliminationRule.RULE_NAME, NoRedundantFieldLoadElimination.class);
+  }
+
   public T enableNoReturnTypeStrengtheningAnnotations() {
     return addNoReturnTypeStrengtheningAnnotation()
         .addInternalMatchAnnotationOnMethodRule(
diff --git a/src/test/java/com/android/tools/r8/RunExamplesJava9Test.java b/src/test/java/com/android/tools/r8/RunExamplesJava9Test.java
index 811c18f..8d43e3b 100644
--- a/src/test/java/com/android/tools/r8/RunExamplesJava9Test.java
+++ b/src/test/java/com/android/tools/r8/RunExamplesJava9Test.java
@@ -183,7 +183,7 @@
             "desugared-private-interface-methods",
             "privateinterfacemethods",
             "PrivateInterfaceMethods")
-        .withMinApiLevel(AndroidApiLevel.M.getLevel())
+        .withMinApiLevel(AndroidApiLevel.K.getLevel())
         .withKeepAll()
         .withDexCheck(
             dexInspector -> {
diff --git a/src/test/java/com/android/tools/r8/TestBase.java b/src/test/java/com/android/tools/r8/TestBase.java
index f3fc72a..35eb6dd 100644
--- a/src/test/java/com/android/tools/r8/TestBase.java
+++ b/src/test/java/com/android/tools/r8/TestBase.java
@@ -1743,6 +1743,10 @@
     return AndroidApiLevel.O;
   }
 
+  public static AndroidApiLevel apiLevelWithMethodParametersSupport() {
+    return AndroidApiLevel.O;
+  }
+
   public static boolean canUseJavaUtilObjects(TestParameters parameters) {
     return parameters.isCfRuntime()
         || parameters.getApiLevel().isGreaterThanOrEqualTo(AndroidApiLevel.K);
@@ -2040,4 +2044,12 @@
           "", expected.size() > actual.size() ? expected.get(minLines) : actual.get(minLines));
     }
   }
+
+  public void runGlobalSyntheticsGenerator(
+      GlobalSyntheticsGeneratorCommand command, Consumer<InternalOptions> internalOptionsConsumer)
+      throws CompilationFailedException {
+    InternalOptions internalOptions = command.getInternalOptions();
+    internalOptionsConsumer.accept(internalOptions);
+    GlobalSyntheticsGenerator.runForTesting(command.getInputApp(), internalOptions);
+  }
 }
diff --git a/src/test/java/com/android/tools/r8/TestCompilerBuilder.java b/src/test/java/com/android/tools/r8/TestCompilerBuilder.java
index 33911ae..0da6332 100644
--- a/src/test/java/com/android/tools/r8/TestCompilerBuilder.java
+++ b/src/test/java/com/android/tools/r8/TestCompilerBuilder.java
@@ -25,6 +25,7 @@
 import com.android.tools.r8.utils.codeinspector.HorizontallyMergedClassesInspector;
 import com.android.tools.r8.utils.codeinspector.VerticallyMergedClassesInspector;
 import com.google.common.base.Suppliers;
+import com.google.common.collect.Sets;
 import java.io.ByteArrayOutputStream;
 import java.io.IOException;
 import java.io.PrintStream;
@@ -33,8 +34,10 @@
 import java.util.Collection;
 import java.util.Collections;
 import java.util.List;
+import java.util.Map;
 import java.util.Optional;
 import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.ExecutionException;
 import java.util.function.Consumer;
 import java.util.function.Function;
@@ -88,6 +91,9 @@
 
   private Optional<Integer> isAndroidBuildVersionAdded = null;
 
+  private static final Map<Integer, Set<String>> allowedGlobalSynthetics =
+      new ConcurrentHashMap<>();
+
   LibraryDesugaringTestConfiguration libraryDesugaringTestConfiguration =
       LibraryDesugaringTestConfiguration.DISABLED;
 
@@ -259,6 +265,25 @@
               : getMinApiLevel();
       builder.setMinApiLevel(minApi);
     }
+    if (!noMinApiLevel && backend.isDex() && (isD8TestBuilder() || isR8TestBuilder())) {
+      int minApiLevel = builder.getMinApiLevel();
+      allowedGlobalSynthetics.computeIfAbsent(
+          minApiLevel, TestCompilerBuilder::computeAllGlobalSynthetics);
+      Consumer<InternalOptions> previousConsumer = optionsConsumer;
+      optionsConsumer =
+          options -> {
+            options.testing.globalSyntheticCreatedCallback =
+                programClass -> {
+                  assertTrue(
+                      allowedGlobalSynthetics
+                          .get(minApiLevel)
+                          .contains(programClass.getType().toDescriptorString()));
+                };
+            if (previousConsumer != null) {
+              previousConsumer.accept(options);
+            }
+          };
+    }
     builder.setOptimizeMultidexForLinearAlloc(optimizeMultidexForLinearAlloc);
     if (useDefaultRuntimeLibrary) {
       builder.addLibraryFiles(getDefaultLibraryFiles());
@@ -534,6 +559,26 @@
     return self();
   }
 
+  private static Set<String> computeAllGlobalSynthetics(int minApiLevel) {
+    try {
+      Set<String> generatedGlobalSynthetics = Sets.newConcurrentHashSet();
+      GlobalSyntheticsGeneratorCommand command =
+          GlobalSyntheticsGeneratorCommand.builder()
+              .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.API_DATABASE_LEVEL))
+              .setMinApiLevel(minApiLevel)
+              .setProgramConsumer(DexIndexedConsumer.emptyConsumer())
+              .build();
+      InternalOptions internalOptions = command.getInternalOptions();
+      internalOptions.testing.globalSyntheticCreatedCallback =
+          programClass ->
+              generatedGlobalSynthetics.add(programClass.getType().toDescriptorString());
+      GlobalSyntheticsGenerator.runForTesting(command.getInputApp(), internalOptions);
+      return generatedGlobalSynthetics;
+    } catch (Exception e) {
+      throw new RuntimeException(e);
+    }
+  }
+
   private static class ChainedStringConsumer implements StringConsumer {
 
     private final List<StringConsumer> consumers;
diff --git a/src/test/java/com/android/tools/r8/TestParameters.java b/src/test/java/com/android/tools/r8/TestParameters.java
index 1c44201..7e64f85 100644
--- a/src/test/java/com/android/tools/r8/TestParameters.java
+++ b/src/test/java/com/android/tools/r8/TestParameters.java
@@ -48,6 +48,10 @@
     return builder().withNoneRuntime().build();
   }
 
+  public boolean canHaveIssueWithInlinedMonitors() {
+    return isCfRuntime() || getApiLevel().isLessThanOrEqualTo(AndroidApiLevel.M);
+  }
+
   /**
    * Returns true if the runtime uses resolution to lookup the constructor targeted by a given
    * invoke, so that it is valid to have non-rebound constructor invokes.
@@ -61,6 +65,10 @@
     return isDexRuntime() && getApiLevel().isGreaterThanOrEqualTo(AndroidApiLevel.L);
   }
 
+  public boolean canInitNewInstanceUsingSuperclassConstructor() {
+    return canHaveNonReboundConstructorInvoke();
+  }
+
   public boolean canUseDefaultAndStaticInterfaceMethods() {
     assert isCfRuntime() || isDexRuntime();
     assert !isCfRuntime() || apiLevel == null
@@ -138,6 +146,10 @@
     return isDexRuntime() && getDexRuntimeVersion().isNewerThanOrEqual(vm);
   }
 
+  public boolean isDexRuntimeVersionOlderThanOrEqual(DexVm.Version vm) {
+    return isDexRuntime() && getDexRuntimeVersion().isOlderThanOrEqual(vm);
+  }
+
   public boolean isNoneRuntime() {
     return runtime == NoneRuntime.getInstance();
   }
diff --git a/src/test/java/com/android/tools/r8/TestRuntime.java b/src/test/java/com/android/tools/r8/TestRuntime.java
index 0d937dd..4f95deb 100644
--- a/src/test/java/com/android/tools/r8/TestRuntime.java
+++ b/src/test/java/com/android/tools/r8/TestRuntime.java
@@ -41,6 +41,7 @@
     JDK16("jdk16", 60),
     JDK17("jdk17", 61),
     JDK18("jdk18", 62),
+    JDK20("jdk20", 64),
     ;
 
     /** This should generally be the latest checked in CF runtime we fully support. */
diff --git a/src/test/java/com/android/tools/r8/TestShrinkerBuilder.java b/src/test/java/com/android/tools/r8/TestShrinkerBuilder.java
index 704ce16..d9e3f91 100644
--- a/src/test/java/com/android/tools/r8/TestShrinkerBuilder.java
+++ b/src/test/java/com/android/tools/r8/TestShrinkerBuilder.java
@@ -520,6 +520,10 @@
     return addTestingAnnotation(NoHorizontalClassMerging.class);
   }
 
+  public final T addNoInliningOfDefaultInitializerAnnotation() {
+    return addTestingAnnotation(NoInliningOfDefaultInitializer.class);
+  }
+
   public final T addNoMethodStaticizingAnnotation() {
     return addTestingAnnotation(NoMethodStaticizing.class);
   }
@@ -532,6 +536,10 @@
     return addTestingAnnotation(NoParameterTypeStrengthening.class);
   }
 
+  public final T addNoRedundantFieldLoadEliminationAnnotation() {
+    return addTestingAnnotation(NoRedundantFieldLoadElimination.class);
+  }
+
   public final T addNoReturnTypeStrengtheningAnnotation() {
     return addTestingAnnotation(NoReturnTypeStrengthening.class);
   }
diff --git a/src/test/java/com/android/tools/r8/ToolHelper.java b/src/test/java/com/android/tools/r8/ToolHelper.java
index 7ac3869..bc066b0 100644
--- a/src/test/java/com/android/tools/r8/ToolHelper.java
+++ b/src/test/java/com/android/tools/r8/ToolHelper.java
@@ -1434,22 +1434,6 @@
     return compatSink.build();
   }
 
-  public static void runD8WithoutResult(
-      D8Command command, Consumer<InternalOptions> optionsConsumer)
-      throws CompilationFailedException {
-    InternalOptions internalOptions = command.getInternalOptions();
-    optionsConsumer.accept(internalOptions);
-    D8.runForTesting(command.getInputApp(), internalOptions);
-  }
-
-  public static List<String> runGenerateMainDexList(
-      GenerateMainDexListCommand command, Consumer<InternalOptions> optionsConsumer)
-      throws CompilationFailedException {
-    InternalOptions internalOptions = command.getInternalOptions();
-    optionsConsumer.accept(internalOptions);
-    return GenerateMainDexList.runForTesting(command.getInputApp(), internalOptions);
-  }
-
   @Deprecated
   public static ProcessResult runJava(Class clazz) throws Exception {
     String main = clazz.getTypeName();
diff --git a/src/test/java/com/android/tools/r8/accessrelaxation/EffectiveFinalFieldMarkedFinalTest.java b/src/test/java/com/android/tools/r8/accessrelaxation/EffectiveFinalFieldMarkedFinalTest.java
index 83d80c8..e08fd3e 100644
--- a/src/test/java/com/android/tools/r8/accessrelaxation/EffectiveFinalFieldMarkedFinalTest.java
+++ b/src/test/java/com/android/tools/r8/accessrelaxation/EffectiveFinalFieldMarkedFinalTest.java
@@ -10,6 +10,7 @@
 import static org.hamcrest.CoreMatchers.allOf;
 import static org.hamcrest.MatcherAssert.assertThat;
 
+import com.android.tools.r8.NoRedundantFieldLoadElimination;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.utils.BooleanUtils;
@@ -42,6 +43,7 @@
         .addInnerClasses(getClass())
         .addKeepMainRule(Main.class)
         .allowAccessModification(allowAccessModification)
+        .enableNoRedundantFieldLoadEliminationAnnotations()
         .setMinApi(parameters)
         .compile()
         .inspect(
@@ -50,7 +52,12 @@
               assertThat(mainClassSubject, isPresent());
               assertThat(
                   mainClassSubject.uniqueFieldWithOriginalName("instanceField"),
-                  allOf(isPresent(), onlyIf(allowAccessModification, isFinal())));
+                  allOf(
+                      isPresent(),
+                      onlyIf(
+                          allowAccessModification
+                              && !parameters.canInitNewInstanceUsingSuperclassConstructor(),
+                          isFinal())));
               assertThat(
                   mainClassSubject.uniqueFieldWithOriginalName("staticField"),
                   allOf(isPresent(), onlyIf(allowAccessModification, isFinal())));
@@ -63,6 +70,7 @@
 
     static String staticField = System.currentTimeMillis() > 0 ? "Hello" : null;
 
+    @NoRedundantFieldLoadElimination
     String instanceField = System.currentTimeMillis() > 0 ? ", world!" : null;
 
     public static void main(String[] args) {
diff --git a/src/test/java/com/android/tools/r8/apimodel/AndroidApiHashingDatabaseBuilderGeneratorTest.java b/src/test/java/com/android/tools/r8/apimodel/AndroidApiHashingDatabaseBuilderGeneratorTest.java
index 9ce5fd6..cda9c15 100644
--- a/src/test/java/com/android/tools/r8/apimodel/AndroidApiHashingDatabaseBuilderGeneratorTest.java
+++ b/src/test/java/com/android/tools/r8/apimodel/AndroidApiHashingDatabaseBuilderGeneratorTest.java
@@ -4,6 +4,7 @@
 
 package com.android.tools.r8.apimodel;
 
+import static com.android.tools.r8.androidapi.AndroidApiLevelDatabaseHelper.notModeledTypes;
 import static java.nio.file.StandardCopyOption.REPLACE_EXISTING;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
@@ -33,7 +34,6 @@
 import java.nio.file.Files;
 import java.nio.file.Path;
 import java.nio.file.Paths;
-import java.util.HashSet;
 import java.util.List;
 import java.util.Set;
 import org.junit.Test;
@@ -133,12 +133,7 @@
 
   private static void ensureAllPublicMethodsAreMapped(
       AppView<AppInfoWithClassHierarchy> appView, AndroidApiLevelCompute apiLevelCompute) {
-    Set<String> notModeledTypes = new HashSet<>();
-    notModeledTypes.add("androidx.annotation.RecentlyNullable");
-    notModeledTypes.add("androidx.annotation.RecentlyNonNull");
-    notModeledTypes.add("android.annotation.Nullable");
-    notModeledTypes.add("android.annotation.NonNull");
-    DexItemFactory factory = appView.dexItemFactory();
+    Set<String> notModeledTypes = notModeledTypes();
     for (DexLibraryClass clazz : appView.app().asDirect().libraryClasses()) {
       if (notModeledTypes.contains(clazz.getClassReference().getTypeName())) {
         continue;
diff --git a/src/test/java/com/android/tools/r8/apimodel/ApiModelAndroidxApiImplTest.java b/src/test/java/com/android/tools/r8/apimodel/ApiModelAndroidxApiImplTest.java
index 0dd154d..d52052b 100644
--- a/src/test/java/com/android/tools/r8/apimodel/ApiModelAndroidxApiImplTest.java
+++ b/src/test/java/com/android/tools/r8/apimodel/ApiModelAndroidxApiImplTest.java
@@ -63,7 +63,7 @@
         .setMinApi(parameters)
         .addAndroidBuildVersion(getMaxSupportedApiLevel())
         .apply(ApiModelingTestHelper::enableOutliningOfMethods)
-        .apply(ApiModelingTestHelper::enableStubbingOfClasses)
+        .apply(ApiModelingTestHelper::enableStubbingOfClassesAndDisableGlobalSyntheticCheck)
         .apply(setMockApiLevelForClass(LibraryClass23.class, AndroidApiLevel.M))
         .apply(setMockApiLevelForClass(LibraryClass26.class, AndroidApiLevel.O))
         .apply(setMockApiLevelForClass(LibraryClass30.class, AndroidApiLevel.R))
diff --git a/src/test/java/com/android/tools/r8/apimodel/ApiModelClassMergingPackagePrivateTest.java b/src/test/java/com/android/tools/r8/apimodel/ApiModelClassMergingPackagePrivateTest.java
index 9ef5912..5e1dc96 100644
--- a/src/test/java/com/android/tools/r8/apimodel/ApiModelClassMergingPackagePrivateTest.java
+++ b/src/test/java/com/android/tools/r8/apimodel/ApiModelClassMergingPackagePrivateTest.java
@@ -68,6 +68,7 @@
         .addDefaultRuntimeLibrary(parameters)
         .setMinApi(parameters)
         .apply(ApiModelingTestHelper::enableOutliningOfMethods)
+        .apply(ApiModelingTestHelper::disableStubbingOfClasses)
         .apply(b -> setApiLevels(b, Api1.class))
         .apply(b -> setApiLevels(b, Api2.class));
   }
diff --git a/src/test/java/com/android/tools/r8/apimodel/ApiModelD8GradleSetupTest.java b/src/test/java/com/android/tools/r8/apimodel/ApiModelD8GradleSetupTest.java
index 2332313..024d17a 100644
--- a/src/test/java/com/android/tools/r8/apimodel/ApiModelD8GradleSetupTest.java
+++ b/src/test/java/com/android/tools/r8/apimodel/ApiModelD8GradleSetupTest.java
@@ -73,7 +73,7 @@
                 LibraryClassThree.class.getDeclaredMethod("baz"), mockApiLevelThree))
         .apply(ApiModelingTestHelper::enableApiCallerIdentification)
         .apply(ApiModelingTestHelper::enableOutliningOfMethods)
-        .apply(ApiModelingTestHelper::enableStubbingOfClasses);
+        .apply(ApiModelingTestHelper::enableStubbingOfClassesAndDisableGlobalSyntheticCheck);
   }
 
   private boolean willHorizontallyMergeOutlines() {
@@ -82,7 +82,8 @@
   }
 
   private boolean willStubLibraryClassThree() {
-    return parameters.getApiLevel().isLessThan(mockApiLevelThree);
+    return parameters.getApiLevel().isGreaterThan(AndroidApiLevel.L)
+        && parameters.getApiLevel().isLessThan(mockApiLevelThree);
   }
 
   public AndroidApiLevel getApiLevelForRuntime() {
@@ -222,7 +223,9 @@
     // classes. Depending on the api a number of synthetic classes.
     int numberOfClasses =
         4
-            + (willStubLibraryClassThree() ? 2 : 0)
+            + (willStubLibraryClassThree()
+                ? 2
+                : (BooleanUtils.intValue(parameters.getApiLevel().isLessThan(mockApiLevelThree))))
             + BooleanUtils.intValue(parameters.getApiLevel().isLessThan(mockApiLevelTwo))
             + BooleanUtils.intValue(parameters.getApiLevel().isLessThan(mockApiLevelOne));
     assertEquals(numberOfClasses, inspector.allClasses().size());
diff --git a/src/test/java/com/android/tools/r8/apimodel/ApiModelFieldAssignNewInstanceTest.java b/src/test/java/com/android/tools/r8/apimodel/ApiModelFieldAssignNewInstanceTest.java
index f2a6859..4145d38 100644
--- a/src/test/java/com/android/tools/r8/apimodel/ApiModelFieldAssignNewInstanceTest.java
+++ b/src/test/java/com/android/tools/r8/apimodel/ApiModelFieldAssignNewInstanceTest.java
@@ -56,7 +56,7 @@
         .setMinApi(parameters)
         .addAndroidBuildVersion(getMaxSupportedApiLevel())
         .apply(ApiModelingTestHelper::enableOutliningOfMethods)
-        .apply(ApiModelingTestHelper::enableStubbingOfClasses)
+        .apply(ApiModelingTestHelper::enableStubbingOfClassesAndDisableGlobalSyntheticCheck)
         .apply(setMockApiLevelForClass(LibraryClass.class, AndroidApiLevel.B))
         .apply(setMockApiLevelForDefaultInstanceInitializer(LibraryClass.class, AndroidApiLevel.B))
         .apply(
diff --git a/src/test/java/com/android/tools/r8/apimodel/ApiModelIndirectTargetWithDifferentApiLevelTest.java b/src/test/java/com/android/tools/r8/apimodel/ApiModelIndirectTargetWithDifferentApiLevelTest.java
index c84fae1..10b38c6 100644
--- a/src/test/java/com/android/tools/r8/apimodel/ApiModelIndirectTargetWithDifferentApiLevelTest.java
+++ b/src/test/java/com/android/tools/r8/apimodel/ApiModelIndirectTargetWithDifferentApiLevelTest.java
@@ -60,7 +60,7 @@
         .addDefaultRuntimeLibrary(parameters)
         .setMinApi(parameters)
         .addAndroidBuildVersion(parameters.getApiLevel())
-        .apply(ApiModelingTestHelper::enableStubbingOfClasses)
+        .apply(ApiModelingTestHelper::enableStubbingOfClassesAndDisableGlobalSyntheticCheck)
         .apply(setMockApiLevelForDefaultInstanceInitializer(LibraryClass.class, classApiLevel))
         .apply(setMockApiLevelForClass(LibraryClass.class, classApiLevel))
         .apply(
diff --git a/src/test/java/com/android/tools/r8/apimodel/ApiModelIndirectTargetWithSameApiLevelTest.java b/src/test/java/com/android/tools/r8/apimodel/ApiModelIndirectTargetWithSameApiLevelTest.java
index 6f03205..d01a450 100644
--- a/src/test/java/com/android/tools/r8/apimodel/ApiModelIndirectTargetWithSameApiLevelTest.java
+++ b/src/test/java/com/android/tools/r8/apimodel/ApiModelIndirectTargetWithSameApiLevelTest.java
@@ -57,7 +57,7 @@
         .addDefaultRuntimeLibrary(parameters)
         .setMinApi(parameters)
         .addAndroidBuildVersion(parameters.getApiLevel())
-        .apply(ApiModelingTestHelper::enableStubbingOfClasses)
+        .apply(ApiModelingTestHelper::enableStubbingOfClassesAndDisableGlobalSyntheticCheck)
         .apply(setMockApiLevelForDefaultInstanceInitializer(LibraryClass.class, mockApiLevel))
         .apply(setMockApiLevelForClass(LibraryClass.class, mockApiLevel))
         .apply(setMockApiLevelForMethod(LibraryClass.class.getDeclaredMethod("foo"), mockApiLevel))
diff --git a/src/test/java/com/android/tools/r8/apimodel/ApiModelInlineMissingSuperTypeTest.java b/src/test/java/com/android/tools/r8/apimodel/ApiModelInlineMissingSuperTypeTest.java
index 67f9f6e..1a0197c 100644
--- a/src/test/java/com/android/tools/r8/apimodel/ApiModelInlineMissingSuperTypeTest.java
+++ b/src/test/java/com/android/tools/r8/apimodel/ApiModelInlineMissingSuperTypeTest.java
@@ -47,6 +47,7 @@
         .apply(
             setMockApiLevelForDefaultInstanceInitializer(
                 LibraryClass.class, libraryAdditionApiLevel))
+        .apply(ApiModelingTestHelper::disableStubbingOfClasses)
         .setMinApi(parameters)
         .addKeepMainRule(Main.class)
         .enableInliningAnnotations()
diff --git a/src/test/java/com/android/tools/r8/apimodel/ApiModelMockAbstractMethodOnBaseToOutlineTest.java b/src/test/java/com/android/tools/r8/apimodel/ApiModelMockAbstractMethodOnBaseToOutlineTest.java
index bc50b66..6e942c1 100644
--- a/src/test/java/com/android/tools/r8/apimodel/ApiModelMockAbstractMethodOnBaseToOutlineTest.java
+++ b/src/test/java/com/android/tools/r8/apimodel/ApiModelMockAbstractMethodOnBaseToOutlineTest.java
@@ -54,7 +54,7 @@
         .addDefaultRuntimeLibrary(parameters)
         .setMinApi(parameters)
         .addAndroidBuildVersion()
-        .apply(ApiModelingTestHelper::enableStubbingOfClasses)
+        .apply(ApiModelingTestHelper::enableStubbingOfClassesAndDisableGlobalSyntheticCheck)
         .apply(ApiModelingTestHelper::enableOutliningOfMethods)
         .apply(setMockApiLevelForClass(LibraryClass.class, AndroidApiLevel.B))
         .apply(setMockApiLevelForDefaultInstanceInitializer(LibraryClass.class, AndroidApiLevel.B))
diff --git a/src/test/java/com/android/tools/r8/apimodel/ApiModelMockClassCheckCastTest.java b/src/test/java/com/android/tools/r8/apimodel/ApiModelMockClassCheckCastTest.java
index 0a9cc42..0b6b937 100644
--- a/src/test/java/com/android/tools/r8/apimodel/ApiModelMockClassCheckCastTest.java
+++ b/src/test/java/com/android/tools/r8/apimodel/ApiModelMockClassCheckCastTest.java
@@ -48,7 +48,7 @@
         .addLibraryClasses(LibraryClass.class)
         .addDefaultRuntimeLibrary(parameters)
         .setMinApi(parameters)
-        .apply(ApiModelingTestHelper::enableStubbingOfClasses)
+        .apply(ApiModelingTestHelper::enableStubbingOfClassesAndDisableGlobalSyntheticCheck)
         .apply(setMockApiLevelForClass(LibraryClass.class, mockLevel));
   }
 
diff --git a/src/test/java/com/android/tools/r8/apimodel/ApiModelMockClassLoadingByClassForNameTest.java b/src/test/java/com/android/tools/r8/apimodel/ApiModelMockClassLoadingByClassForNameTest.java
index dccfdbf..6d33017 100644
--- a/src/test/java/com/android/tools/r8/apimodel/ApiModelMockClassLoadingByClassForNameTest.java
+++ b/src/test/java/com/android/tools/r8/apimodel/ApiModelMockClassLoadingByClassForNameTest.java
@@ -48,7 +48,7 @@
         .addLibraryClasses(LibraryClass.class)
         .addDefaultRuntimeLibrary(parameters)
         .setMinApi(parameters)
-        .apply(ApiModelingTestHelper::enableStubbingOfClasses)
+        .apply(ApiModelingTestHelper::enableStubbingOfClassesAndDisableGlobalSyntheticCheck)
         .apply(setMockApiLevelForClass(LibraryClass.class, mockLevel));
   }
 
diff --git a/src/test/java/com/android/tools/r8/apimodel/ApiModelMockClassLoadingByClassReferenceTest.java b/src/test/java/com/android/tools/r8/apimodel/ApiModelMockClassLoadingByClassReferenceTest.java
index 4c90ac5..e19d0e6 100644
--- a/src/test/java/com/android/tools/r8/apimodel/ApiModelMockClassLoadingByClassReferenceTest.java
+++ b/src/test/java/com/android/tools/r8/apimodel/ApiModelMockClassLoadingByClassReferenceTest.java
@@ -48,7 +48,7 @@
         .addLibraryClasses(LibraryClass.class)
         .addDefaultRuntimeLibrary(parameters)
         .setMinApi(parameters)
-        .apply(ApiModelingTestHelper::enableStubbingOfClasses)
+        .apply(ApiModelingTestHelper::enableStubbingOfClassesAndDisableGlobalSyntheticCheck)
         .apply(setMockApiLevelForClass(LibraryClass.class, mockLevel));
   }
 
diff --git a/src/test/java/com/android/tools/r8/apimodel/ApiModelMockClassTest.java b/src/test/java/com/android/tools/r8/apimodel/ApiModelMockClassTest.java
index 9fc0985..6185e26 100644
--- a/src/test/java/com/android/tools/r8/apimodel/ApiModelMockClassTest.java
+++ b/src/test/java/com/android/tools/r8/apimodel/ApiModelMockClassTest.java
@@ -52,7 +52,7 @@
         .addLibraryClasses(LibraryClass.class)
         .addDefaultRuntimeLibrary(parameters)
         .setMinApi(parameters)
-        .apply(ApiModelingTestHelper::enableStubbingOfClasses)
+        .apply(ApiModelingTestHelper::enableStubbingOfClassesAndDisableGlobalSyntheticCheck)
         .apply(ApiModelingTestHelper::disableOutlining)
         .apply(setMockApiLevelForClass(LibraryClass.class, mockLevel))
         .apply(setMockApiLevelForDefaultInstanceInitializer(LibraryClass.class, mockLevel));
diff --git a/src/test/java/com/android/tools/r8/apimodel/ApiModelMockDalvikVerifyErrorTest.java b/src/test/java/com/android/tools/r8/apimodel/ApiModelMockDalvikVerifyErrorTest.java
index 74e7ef0..5931ef7 100644
--- a/src/test/java/com/android/tools/r8/apimodel/ApiModelMockDalvikVerifyErrorTest.java
+++ b/src/test/java/com/android/tools/r8/apimodel/ApiModelMockDalvikVerifyErrorTest.java
@@ -50,7 +50,7 @@
         .addDefaultRuntimeLibrary(parameters)
         .setMinApi(parameters)
         .addAndroidBuildVersion()
-        .apply(ApiModelingTestHelper::enableStubbingOfClasses)
+        .apply(ApiModelingTestHelper::enableStubbingOfClassesAndDisableGlobalSyntheticCheck)
         .apply(setMockApiLevelForClass(LibraryClass.class, mockLevel))
         .apply(setMockApiLevelForDefaultInstanceInitializer(LibraryClass.class, mockLevel))
         .apply(setMockApiLevelForMethod(LibraryClass.class.getDeclaredMethod("foo"), mockLevel));
diff --git a/src/test/java/com/android/tools/r8/apimodel/ApiModelMockExceptionTest.java b/src/test/java/com/android/tools/r8/apimodel/ApiModelMockExceptionTest.java
index ea43cbb..fff6c4a 100644
--- a/src/test/java/com/android/tools/r8/apimodel/ApiModelMockExceptionTest.java
+++ b/src/test/java/com/android/tools/r8/apimodel/ApiModelMockExceptionTest.java
@@ -59,7 +59,7 @@
         .addLibraryClasses(LibrarySuperException.class, LibrarySubException.class, Thrower.class)
         .addDefaultRuntimeLibrary(parameters)
         .setMinApi(parameters)
-        .apply(ApiModelingTestHelper::enableStubbingOfClasses)
+        .apply(ApiModelingTestHelper::enableStubbingOfClassesAndDisableGlobalSyntheticCheck)
         .apply(setMockApiLevelForClass(LibrarySuperException.class, mockSuperExceptionLevel))
         .apply(setMockApiLevelForClass(LibrarySubException.class, mockSubExceptionLevel));
   }
diff --git a/src/test/java/com/android/tools/r8/apimodel/ApiModelMockInheritedClassTest.java b/src/test/java/com/android/tools/r8/apimodel/ApiModelMockInheritedClassTest.java
index c7a2674..739600d 100644
--- a/src/test/java/com/android/tools/r8/apimodel/ApiModelMockInheritedClassTest.java
+++ b/src/test/java/com/android/tools/r8/apimodel/ApiModelMockInheritedClassTest.java
@@ -47,7 +47,7 @@
         .addDefaultRuntimeLibrary(parameters)
         .setMinApi(parameters)
         .addAndroidBuildVersion()
-        .apply(ApiModelingTestHelper::enableStubbingOfClasses)
+        .apply(ApiModelingTestHelper::enableStubbingOfClassesAndDisableGlobalSyntheticCheck)
         .apply(setMockApiLevelForClass(LibraryClass.class, mockLevel))
         .apply(setMockApiLevelForDefaultInstanceInitializer(LibraryClass.class, mockLevel));
   }
@@ -97,7 +97,8 @@
   }
 
   private void inspect(CodeInspector inspector) {
-    verifyThat(inspector, parameters, LibraryClass.class).stubbedUntil(mockLevel);
+    verifyThat(inspector, parameters, LibraryClass.class)
+        .stubbedBetween(AndroidApiLevel.L_MR1, mockLevel);
   }
 
   private void checkOutput(SingleTestRunResult<?> runResult) {
diff --git a/src/test/java/com/android/tools/r8/apimodel/ApiModelMockMergeProgramDefinedDuplicateTest.java b/src/test/java/com/android/tools/r8/apimodel/ApiModelMockMergeProgramDefinedDuplicateTest.java
index 8e974f1..bb28672 100644
--- a/src/test/java/com/android/tools/r8/apimodel/ApiModelMockMergeProgramDefinedDuplicateTest.java
+++ b/src/test/java/com/android/tools/r8/apimodel/ApiModelMockMergeProgramDefinedDuplicateTest.java
@@ -56,7 +56,7 @@
         .addDefaultRuntimeLibrary(parameters)
         .setMinApi(parameters)
         .apply(ApiModelingTestHelper::enableApiCallerIdentification)
-        .apply(ApiModelingTestHelper::enableStubbingOfClasses)
+        .apply(ApiModelingTestHelper::enableStubbingOfClassesAndDisableGlobalSyntheticCheck)
         .apply(ApiModelingTestHelper::disableOutlining)
         .apply(setMockApiLevelForClass(LibraryClass.class, mockLevel))
         .apply(setMockApiLevelForDefaultInstanceInitializer(LibraryClass.class, mockLevel))
diff --git a/src/test/java/com/android/tools/r8/apimodel/ApiModelMockMergeTest.java b/src/test/java/com/android/tools/r8/apimodel/ApiModelMockMergeTest.java
index ab6e2148..15ffd25 100644
--- a/src/test/java/com/android/tools/r8/apimodel/ApiModelMockMergeTest.java
+++ b/src/test/java/com/android/tools/r8/apimodel/ApiModelMockMergeTest.java
@@ -56,7 +56,7 @@
         .addDefaultRuntimeLibrary(parameters)
         .setMinApi(parameters)
         .apply(ApiModelingTestHelper::enableApiCallerIdentification)
-        .apply(ApiModelingTestHelper::enableStubbingOfClasses)
+        .apply(ApiModelingTestHelper::enableStubbingOfClassesAndDisableGlobalSyntheticCheck)
         .apply(ApiModelingTestHelper::disableOutlining)
         .apply(setMockApiLevelForClass(LibraryClass.class, mockLevel))
         .apply(setMockApiLevelForDefaultInstanceInitializer(LibraryClass.class, mockLevel))
diff --git a/src/test/java/com/android/tools/r8/apimodel/ApiModelMockRetraceTest.java b/src/test/java/com/android/tools/r8/apimodel/ApiModelMockRetraceTest.java
index 9394280..8512c5a 100644
--- a/src/test/java/com/android/tools/r8/apimodel/ApiModelMockRetraceTest.java
+++ b/src/test/java/com/android/tools/r8/apimodel/ApiModelMockRetraceTest.java
@@ -48,7 +48,7 @@
         .addDefaultRuntimeLibrary(parameters)
         .setMinApi(parameters)
         .addAndroidBuildVersion()
-        .apply(ApiModelingTestHelper::enableStubbingOfClasses)
+        .apply(ApiModelingTestHelper::enableStubbingOfClassesAndDisableGlobalSyntheticCheck)
         .apply(setMockApiLevelForClass(LibraryClass.class, mockLevel))
         .addKeepMainRule(Main.class)
         .addKeepClassRules(ProgramClass.class)
@@ -60,10 +60,15 @@
   }
 
   private void inspect(CodeInspector inspector) {
-    verifyThat(inspector, parameters, LibraryClass.class).stubbedUntil(mockLevel);
+    verifyThat(inspector, parameters, LibraryClass.class)
+        .stubbedBetween(AndroidApiLevel.L_MR1, mockLevel);
   }
 
   private void checkOutput(SingleTestRunResult<?> runResult) {
+    if (!addToBootClasspath()) {
+      runResult.assertFailureWithErrorThatThrows(NoClassDefFoundError.class);
+      return;
+    }
     StackTraceLine clinitFrame =
         StackTraceLine.builder()
             .setClassName(typeName(LibraryClass.class))
diff --git a/src/test/java/com/android/tools/r8/apimodel/ApiModelMockSuperChainClassTest.java b/src/test/java/com/android/tools/r8/apimodel/ApiModelMockSuperChainClassTest.java
index 3d31170..9ef1d91 100644
--- a/src/test/java/com/android/tools/r8/apimodel/ApiModelMockSuperChainClassTest.java
+++ b/src/test/java/com/android/tools/r8/apimodel/ApiModelMockSuperChainClassTest.java
@@ -49,7 +49,7 @@
         .addDefaultRuntimeLibrary(parameters)
         .setMinApi(parameters)
         .addAndroidBuildVersion()
-        .apply(ApiModelingTestHelper::enableStubbingOfClasses)
+        .apply(ApiModelingTestHelper::enableStubbingOfClassesAndDisableGlobalSyntheticCheck)
         .apply(setMockApiLevelForClass(LibraryClass.class, lowerMockApiLevel))
         .apply(setMockApiLevelForDefaultInstanceInitializer(LibraryClass.class, lowerMockApiLevel))
         .apply(setMockApiLevelForClass(OtherLibraryClass.class, mockApiLevel))
@@ -121,9 +121,12 @@
   }
 
   private void inspect(CodeInspector inspector) {
-    verifyThat(inspector, parameters, LibraryClass.class).stubbedUntil(lowerMockApiLevel);
-    verifyThat(inspector, parameters, LibraryInterface.class).stubbedUntil(lowerMockApiLevel);
-    verifyThat(inspector, parameters, OtherLibraryClass.class).stubbedUntil(mockApiLevel);
+    verifyThat(inspector, parameters, LibraryClass.class)
+        .stubbedBetween(AndroidApiLevel.L_MR1, lowerMockApiLevel);
+    verifyThat(inspector, parameters, LibraryInterface.class)
+        .stubbedBetween(AndroidApiLevel.L_MR1, lowerMockApiLevel);
+    verifyThat(inspector, parameters, OtherLibraryClass.class)
+        .stubbedBetween(AndroidApiLevel.L_MR1, mockApiLevel);
   }
 
   private void checkOutput(SingleTestRunResult<?> runResult) {
diff --git a/src/test/java/com/android/tools/r8/apimodel/ApiModelNoOutlineForFullyMockedTest.java b/src/test/java/com/android/tools/r8/apimodel/ApiModelNoOutlineForFullyMockedTest.java
index 57e692a..1749728 100644
--- a/src/test/java/com/android/tools/r8/apimodel/ApiModelNoOutlineForFullyMockedTest.java
+++ b/src/test/java/com/android/tools/r8/apimodel/ApiModelNoOutlineForFullyMockedTest.java
@@ -54,7 +54,7 @@
         .apply(setMockApiLevelForDefaultInstanceInitializer(LibraryClass.class, libraryApiLevel))
         .apply(setMockApiLevelForMethod(methodOn23, libraryApiLevel))
         .apply(ApiModelingTestHelper::enableOutliningOfMethods)
-        .apply(ApiModelingTestHelper::enableStubbingOfClasses);
+        .apply(ApiModelingTestHelper::enableStubbingOfClassesAndDisableGlobalSyntheticCheck);
   }
 
   private boolean addToBootClasspath() {
diff --git a/src/test/java/com/android/tools/r8/apimodel/ApiModelNoVerticalMergingSubReferenceApiTest.java b/src/test/java/com/android/tools/r8/apimodel/ApiModelNoVerticalMergingSubReferenceApiTest.java
index aa584e4..dff5d14 100644
--- a/src/test/java/com/android/tools/r8/apimodel/ApiModelNoVerticalMergingSubReferenceApiTest.java
+++ b/src/test/java/com/android/tools/r8/apimodel/ApiModelNoVerticalMergingSubReferenceApiTest.java
@@ -6,6 +6,7 @@
 
 import static com.android.tools.r8.apimodel.ApiModelingTestHelper.setMockApiLevelForMethod;
 import static com.android.tools.r8.utils.AndroidApiLevel.L_MR1;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isAbsentIf;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
 import static junit.framework.TestCase.assertEquals;
 import static org.hamcrest.CoreMatchers.not;
@@ -13,6 +14,7 @@
 
 import com.android.tools.r8.NeverClassInline;
 import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.NoInliningOfDefaultInitializer;
 import com.android.tools.r8.NoMethodStaticizing;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
@@ -55,6 +57,7 @@
         .apply(ApiModelingTestHelper::disableOutliningAndStubbing)
         .enableInliningAnnotations()
         .enableNeverClassInliningAnnotations()
+        .enableNoInliningOfDefaultInitializerAnnotations()
         .enableNoMethodStaticizingAnnotations()
         .addVerticallyMergedClassesInspector(
             inspector -> {
@@ -75,7 +78,9 @@
                 assertThat(base, not(isPresent()));
                 ClassSubject sub = inspector.clazz(Sub.class);
                 assertThat(sub, isPresent());
-                assertThat(sub.uniqueInstanceInitializer(), isPresent());
+                assertThat(
+                    sub.uniqueInstanceInitializer(),
+                    isAbsentIf(parameters.canHaveNonReboundConstructorInvoke()));
                 assertEquals(1, sub.virtualMethods().size());
                 FoundMethodSubject callCallApi = sub.virtualMethods().get(0);
                 assertEquals("callCallApi", callCallApi.getOriginalName());
@@ -104,6 +109,7 @@
   }
 
   @NeverClassInline
+  @NoInliningOfDefaultInitializer
   public static class Sub extends Base {
 
     @NeverInline
diff --git a/src/test/java/com/android/tools/r8/apimodel/ApiModelNoVerticalMergingTest.java b/src/test/java/com/android/tools/r8/apimodel/ApiModelNoVerticalMergingTest.java
index 3933284..2e368e9 100644
--- a/src/test/java/com/android/tools/r8/apimodel/ApiModelNoVerticalMergingTest.java
+++ b/src/test/java/com/android/tools/r8/apimodel/ApiModelNoVerticalMergingTest.java
@@ -6,6 +6,7 @@
 
 import static com.android.tools.r8.apimodel.ApiModelingTestHelper.setMockApiLevelForMethod;
 import static com.android.tools.r8.utils.AndroidApiLevel.L_MR1;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isAbsentIf;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
 import static junit.framework.TestCase.assertEquals;
 import static org.hamcrest.CoreMatchers.not;
@@ -13,6 +14,7 @@
 
 import com.android.tools.r8.NeverClassInline;
 import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.NoInliningOfDefaultInitializer;
 import com.android.tools.r8.NoMethodStaticizing;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
@@ -55,6 +57,7 @@
         .apply(ApiModelingTestHelper::disableOutliningAndStubbing)
         .enableInliningAnnotations()
         .enableNeverClassInliningAnnotations()
+        .enableNoInliningOfDefaultInitializerAnnotations()
         .enableNoMethodStaticizingAnnotations()
         .addVerticallyMergedClassesInspector(
             inspector -> {
@@ -74,7 +77,9 @@
                 assertThat(base, not(isPresent()));
                 ClassSubject sub = inspector.clazz(Sub.class);
                 assertThat(sub, isPresent());
-                assertThat(sub.uniqueInstanceInitializer(), isPresent());
+                assertThat(
+                    sub.uniqueInstanceInitializer(),
+                    isAbsentIf(parameters.canHaveNonReboundConstructorInvoke()));
                 assertEquals(1, sub.virtualMethods().size());
                 FoundMethodSubject callCallApi = sub.virtualMethods().get(0);
                 assertEquals("callCallApi", callCallApi.getOriginalName());
@@ -104,6 +109,7 @@
   }
 
   @NeverClassInline
+  @NoInliningOfDefaultInitializer
   public static class Sub extends Base {
 
     @NeverInline
diff --git a/src/test/java/com/android/tools/r8/apimodel/ApiModelOutlineCheckCastTest.java b/src/test/java/com/android/tools/r8/apimodel/ApiModelOutlineCheckCastTest.java
index 86630ca..4c6763e 100644
--- a/src/test/java/com/android/tools/r8/apimodel/ApiModelOutlineCheckCastTest.java
+++ b/src/test/java/com/android/tools/r8/apimodel/ApiModelOutlineCheckCastTest.java
@@ -53,7 +53,7 @@
         .apply(setMockApiLevelForMethod(LibraryClass.class.getDeclaredMethod("foo"), classApiLevel))
         .apply(ApiModelingTestHelper::enableApiCallerIdentification)
         .apply(ApiModelingTestHelper::enableOutliningOfMethods)
-        .apply(ApiModelingTestHelper::enableStubbingOfClasses);
+        .apply(ApiModelingTestHelper::enableStubbingOfClassesAndDisableGlobalSyntheticCheck);
   }
 
   public AndroidApiLevel getApiLevelForRuntime() {
diff --git a/src/test/java/com/android/tools/r8/apimodel/ApiModelOutlineConstClassTest.java b/src/test/java/com/android/tools/r8/apimodel/ApiModelOutlineConstClassTest.java
index 371531b..7c18193 100644
--- a/src/test/java/com/android/tools/r8/apimodel/ApiModelOutlineConstClassTest.java
+++ b/src/test/java/com/android/tools/r8/apimodel/ApiModelOutlineConstClassTest.java
@@ -46,7 +46,7 @@
         .apply(setMockApiLevelForClass(LibraryClass.class, classApiLevel))
         .apply(ApiModelingTestHelper::enableApiCallerIdentification)
         .apply(ApiModelingTestHelper::enableOutliningOfMethods)
-        .apply(ApiModelingTestHelper::enableStubbingOfClasses);
+        .apply(ApiModelingTestHelper::enableStubbingOfClassesAndDisableGlobalSyntheticCheck);
   }
 
   public AndroidApiLevel getApiLevelForRuntime() {
diff --git a/src/test/java/com/android/tools/r8/apimodel/ApiModelOutlineInstanceOfTest.java b/src/test/java/com/android/tools/r8/apimodel/ApiModelOutlineInstanceOfTest.java
index a4e7c2e..d6d7137 100644
--- a/src/test/java/com/android/tools/r8/apimodel/ApiModelOutlineInstanceOfTest.java
+++ b/src/test/java/com/android/tools/r8/apimodel/ApiModelOutlineInstanceOfTest.java
@@ -52,7 +52,7 @@
         .apply(setMockApiLevelForClass(LibraryClass.class, classApiLevel))
         .apply(ApiModelingTestHelper::enableApiCallerIdentification)
         .apply(ApiModelingTestHelper::enableOutliningOfMethods)
-        .apply(ApiModelingTestHelper::enableStubbingOfClasses);
+        .apply(ApiModelingTestHelper::enableStubbingOfClassesAndDisableGlobalSyntheticCheck);
   }
 
   public AndroidApiLevel getApiLevelForRuntime() {
diff --git a/src/test/java/com/android/tools/r8/apimodel/ApiModelOutlineMethodAndStubClassTest.java b/src/test/java/com/android/tools/r8/apimodel/ApiModelOutlineMethodAndStubClassTest.java
index 4663607..8fd5ba1 100644
--- a/src/test/java/com/android/tools/r8/apimodel/ApiModelOutlineMethodAndStubClassTest.java
+++ b/src/test/java/com/android/tools/r8/apimodel/ApiModelOutlineMethodAndStubClassTest.java
@@ -54,7 +54,7 @@
         .addAndroidBuildVersion()
         // TODO(b/213552119): Remove when enabled by default.
         .apply(ApiModelingTestHelper::enableApiCallerIdentification)
-        .apply(ApiModelingTestHelper::enableStubbingOfClasses)
+        .apply(ApiModelingTestHelper::enableStubbingOfClassesAndDisableGlobalSyntheticCheck)
         .apply(ApiModelingTestHelper::enableOutliningOfMethods)
         // We only model the class and not the default initializer, otherwise we outline the new
         // instance call and remove the last reference in non-outlined code.
diff --git a/src/test/java/com/android/tools/r8/apimodel/ApiModelOutlineMethodProtectedTest.java b/src/test/java/com/android/tools/r8/apimodel/ApiModelOutlineMethodProtectedTest.java
index 39b1db2..4883f17 100644
--- a/src/test/java/com/android/tools/r8/apimodel/ApiModelOutlineMethodProtectedTest.java
+++ b/src/test/java/com/android/tools/r8/apimodel/ApiModelOutlineMethodProtectedTest.java
@@ -72,8 +72,8 @@
                 .transform())
         .setMinApi(AndroidApiLevel.B)
         .addAndroidBuildVersion(runApiLevel())
-        // TODO(b/213552119): Remove when enabled by default.
         .apply(ApiModelingTestHelper::enableApiCallerIdentification)
+        .apply(ApiModelingTestHelper::disableStubbingOfClasses)
         .apply(setMockApiLevelForClass(LibraryClass.class, classApiLevel))
         .apply(setMockApiLevelForDefaultInstanceInitializer(LibraryClass.class, classApiLevel))
         .apply(
diff --git a/src/test/java/com/android/tools/r8/apimodel/ApiModelingTestHelper.java b/src/test/java/com/android/tools/r8/apimodel/ApiModelingTestHelper.java
index cfa2e8a..f6a698a 100644
--- a/src/test/java/com/android/tools/r8/apimodel/ApiModelingTestHelper.java
+++ b/src/test/java/com/android/tools/r8/apimodel/ApiModelingTestHelper.java
@@ -134,6 +134,18 @@
         });
   }
 
+  public static void enableStubbingOfClassesAndDisableGlobalSyntheticCheck(
+      TestCompilerBuilder<?, ?, ?, ?, ?> compilerBuilder) {
+    compilerBuilder.addOptionsModification(
+        options -> {
+          options.apiModelingOptions().enableLibraryApiModeling = true;
+          options.apiModelingOptions().enableStubbingOfClasses = true;
+          // Our tests rely on us amending the library path with additional classes that are not
+          // in the library.
+          options.testing.globalSyntheticCreatedCallback = null;
+        });
+  }
+
   public static void enableStubbingOfClasses(TestCompilerBuilder<?, ?, ?, ?, ?> compilerBuilder) {
     compilerBuilder.addOptionsModification(
         options -> {
@@ -277,6 +289,16 @@
                   || parameters.getApiLevel().isGreaterThanOrEqualTo(finalApiLevel)));
     }
 
+    public void stubbedBetween(AndroidApiLevel startingApilevel, AndroidApiLevel endingApiLevel) {
+      assertThat(
+          inspector.clazz(classOfInterest),
+          notIf(
+              isPresent(),
+              parameters.isCfRuntime()
+                  || parameters.getApiLevel().isLessThan(startingApilevel)
+                  || parameters.getApiLevel().isGreaterThanOrEqualTo(endingApiLevel)));
+    }
+
     void hasCheckCastOutlinedFromUntil(Method method, AndroidApiLevel apiLevel) {
       if (parameters.isDexRuntime() && parameters.getApiLevel().isLessThan(apiLevel)) {
         hasCheckCastOutlinedFrom(method);
diff --git a/src/test/java/com/android/tools/r8/classmerging/StatefulSingletonClassesMergingTest.java b/src/test/java/com/android/tools/r8/classmerging/StatefulSingletonClassesMergingTest.java
index 4b17a16..3604880 100644
--- a/src/test/java/com/android/tools/r8/classmerging/StatefulSingletonClassesMergingTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/StatefulSingletonClassesMergingTest.java
@@ -34,8 +34,16 @@
     testForR8(parameters.getBackend())
         .addInnerClasses(getClass())
         .addKeepMainRule(Main.class)
+        // TODO(b/280384153): A and B should always be merged in the final round of tree shaking
+        //  since their class initializers are postponeable, but we fail to conclude so with
+        //  constructor inlining enabled.
         .addHorizontallyMergedClassesInspector(
-            inspector -> inspector.assertIsCompleteMergeGroup(A.class, B.class))
+            inspector ->
+                inspector
+                    .applyIf(
+                        !parameters.canInitNewInstanceUsingSuperclassConstructor(),
+                        i -> i.assertIsCompleteMergeGroup(A.class, B.class))
+                    .assertNoOtherClassesMerged())
         .enableConstantArgumentAnnotations()
         .enableInliningAnnotations()
         .enableMemberValuePropagationAnnotations()
@@ -77,7 +85,7 @@
 
     static final B INSTANCE = new B("B");
 
-    @NeverPropagateValue private final String data;
+    @NeverPropagateValue private String data;
 
     // TODO(b/198758663): With argument propagation the constructors end up not being equivalent,
     //  which prevents merging in the final round of horizontal class merging.
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/ClassesWithFeatureSplitTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/ClassesWithFeatureSplitTest.java
index b5f0cb1..2093d70 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/ClassesWithFeatureSplitTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/ClassesWithFeatureSplitTest.java
@@ -9,6 +9,7 @@
 import static org.hamcrest.MatcherAssert.assertThat;
 
 import com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.NeverInline;
 import com.android.tools.r8.R8TestCompileResult;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
@@ -40,6 +41,7 @@
             .addFeatureSplit(Feature2Class.class, Feature2Main.class)
             .addKeepFeatureMainRule(Feature1Main.class)
             .addKeepFeatureMainRule(Feature2Main.class)
+            .enableInliningAnnotations()
             .enableNeverClassInliningAnnotations()
             .setMinApi(parameters)
             .compile()
@@ -100,6 +102,7 @@
 
   @NeverClassInline
   public static class Feature2Class {
+    @NeverInline
     public Feature2Class() {
       System.out.println("feature 2");
     }
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/ClassesWithNativeMethodsTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/ClassesWithNativeMethodsTest.java
index 52b7cbe..b09d358 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/ClassesWithNativeMethodsTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/ClassesWithNativeMethodsTest.java
@@ -45,6 +45,7 @@
 
   @NeverClassInline
   public static class A {
+    @NeverInline
     public A() {
       System.out.println("a");
     }
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/CompatKeepConstructorLiveTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/CompatKeepConstructorLiveTest.java
index e605671..a4634f4 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/CompatKeepConstructorLiveTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/CompatKeepConstructorLiveTest.java
@@ -8,6 +8,7 @@
 import static org.hamcrest.MatcherAssert.assertThat;
 
 import com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.NeverInline;
 import com.android.tools.r8.NoHorizontalClassMerging;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
@@ -23,6 +24,7 @@
     testForR8Compat(parameters.getBackend())
         .addInnerClasses(getClass())
         .addKeepMainRule(Main.class)
+        .enableInliningAnnotations()
         .enableNeverClassInliningAnnotations()
         .enableNoHorizontalClassMergingAnnotations()
         .setMinApi(parameters)
@@ -47,6 +49,7 @@
   @NeverClassInline
   @NoHorizontalClassMerging
   public static class B {
+    @NeverInline
     public B(String v) {
       System.out.println("b: " + v);
     }
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/ConstructorMergingPreoptimizedTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/ConstructorMergingPreoptimizedTest.java
index 90a19bc..a8a0ec2 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/ConstructorMergingPreoptimizedTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/ConstructorMergingPreoptimizedTest.java
@@ -34,6 +34,8 @@
         .addKeepMainRule(Main.class)
         .addHorizontallyMergedClassesInspector(
             inspector -> inspector.assertMergedInto(B.class, A.class))
+        .addOptionsModification(
+            options -> options.inlinerOptions().setEnableConstructorInlining(false))
         .enableInliningAnnotations()
         .enableNeverClassInliningAnnotations()
         .enableNoHorizontalClassMergingAnnotations()
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/EmptyClassTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/EmptyClassTest.java
index e481a0d..66bdabb 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/EmptyClassTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/EmptyClassTest.java
@@ -6,6 +6,7 @@
 
 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.notIf;
 import static org.hamcrest.MatcherAssert.assertThat;
 
 import com.android.tools.r8.NeverClassInline;
@@ -30,7 +31,9 @@
         .assertSuccessWithOutputLines("a", "b: foo")
         .inspect(
             codeInspector -> {
-              assertThat(codeInspector.clazz(A.class), isPresent());
+              assertThat(
+                  codeInspector.clazz(A.class),
+                  notIf(isPresent(), parameters.canInitNewInstanceUsingSuperclassConstructor()));
               assertThat(codeInspector.clazz(B.class), isAbsent());
             });
   }
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/EquivalentConstructorsWithPrimitiveAndReferencesParametersTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/EquivalentConstructorsWithPrimitiveAndReferencesParametersTest.java
index f751f84..613cc97 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/EquivalentConstructorsWithPrimitiveAndReferencesParametersTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/EquivalentConstructorsWithPrimitiveAndReferencesParametersTest.java
@@ -53,7 +53,8 @@
               ClassSubject aClassSubject = inspector.clazz(A.class);
               assertThat(aClassSubject, isPresent());
               assertEquals(
-                  2, aClassSubject.allMethods(FoundMethodSubject::isInstanceInitializer).size());
+                  parameters.canInitNewInstanceUsingSuperclassConstructor() ? 0 : 2,
+                  aClassSubject.allMethods(FoundMethodSubject::isInstanceInitializer).size());
             })
         .run(parameters.getRuntime(), Main.class)
         .assertSuccessWithOutputLines("A", "B");
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/HorizontalClassMergingAfterConstructorShrinkingTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/HorizontalClassMergingAfterConstructorShrinkingTest.java
index 4283b1a..6558cd4 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/HorizontalClassMergingAfterConstructorShrinkingTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/HorizontalClassMergingAfterConstructorShrinkingTest.java
@@ -81,6 +81,7 @@
 
     Object field;
 
+    @NeverInline
     A() {
       System.out.println("Ouch!");
     }
@@ -103,6 +104,7 @@
     Object field;
 
     // Removed by constructor shrinking.
+    @NeverInline
     B() {}
 
     @NeverInline
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/HorizontalClassMergingAfterConstructorShrinkingWithRepackagingTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/HorizontalClassMergingAfterConstructorShrinkingWithRepackagingTest.java
index e705e0c..c453a01 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/HorizontalClassMergingAfterConstructorShrinkingWithRepackagingTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/HorizontalClassMergingAfterConstructorShrinkingWithRepackagingTest.java
@@ -138,6 +138,7 @@
     Object field;
 
     @KeepConstantArguments
+    @NeverInline
     public A(Parent parent) {
       super(parent);
       System.out.println("Ouch!");
@@ -162,6 +163,7 @@
 
     // Removed by constructor shrinking.
     @KeepConstantArguments
+    @NeverInline
     public B(Parent parent) {
       super(parent);
     }
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/HorizontalClassMergingOfInitArgumentTypesTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/HorizontalClassMergingOfInitArgumentTypesTest.java
index 6694cdb..66f392b 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/HorizontalClassMergingOfInitArgumentTypesTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/HorizontalClassMergingOfInitArgumentTypesTest.java
@@ -8,6 +8,7 @@
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.junit.Assert.assertEquals;
 
+import com.android.tools.r8.NeverInline;
 import com.android.tools.r8.NoHorizontalClassMerging;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
@@ -45,6 +46,7 @@
               options.callSiteOptimizationOptions().setForceSyntheticsForInstanceInitializers(true);
               options.horizontalClassMergerOptions().enableIf(enableHorizontalClassMerging);
             })
+        .enableInliningAnnotations()
         .enableNoHorizontalClassMergingAnnotations()
         .setMinApi(parameters)
         .compile()
@@ -85,9 +87,11 @@
 
     boolean unused;
 
+    @NeverInline
     A() {}
 
     // Unused argument removal will rewrite this into A(A$$ExternalSynthetic$IA0)
+    @NeverInline
     A(Object unused) {
       this.unused = true;
     }
@@ -103,9 +107,11 @@
 
     boolean unused;
 
+    @NeverInline
     B() {}
 
     // Unused argument removal will rewrite this into B(B$$ExternalSynthetic$IA0)
+    @NeverInline
     B(Object unused) {
       this.unused = true;
     }
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/InnerOuterClassesTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/InnerOuterClassesTest.java
index 8617415..c67f07e 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/InnerOuterClassesTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/InnerOuterClassesTest.java
@@ -10,7 +10,9 @@
 
 import com.android.tools.r8.NeverClassInline;
 import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.ir.optimize.Inliner.Reason;
 import com.android.tools.r8.utils.BooleanUtils;
+import com.google.common.collect.ImmutableSet;
 import java.util.List;
 import org.junit.Test;
 import org.junit.runners.Parameterized;
@@ -37,6 +39,8 @@
         .addKeepMainRule(Main.class)
         .enableNeverClassInliningAnnotations()
         .addKeepAttributeInnerClassesAndEnclosingMethod()
+        .addOptionsModification(
+            options -> options.testing.validInliningReasons = ImmutableSet.of(Reason.FORCE))
         .setMinApi(parameters)
         .addOptionsModification(
             options ->
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/InstantiatedAndUninstantiatedClassMergingTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/InstantiatedAndUninstantiatedClassMergingTest.java
index 6ed6c30..ac11021 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/InstantiatedAndUninstantiatedClassMergingTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/InstantiatedAndUninstantiatedClassMergingTest.java
@@ -60,6 +60,7 @@
   @NeverClassInline
   public static final class Instantiated {
 
+    @NeverInline
     Instantiated() {
       System.out.println("Instantiated");
     }
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/LargeConstructorsMergingTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/LargeConstructorsMergingTest.java
index 68bc6a8..5405ddb 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/LargeConstructorsMergingTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/LargeConstructorsMergingTest.java
@@ -11,7 +11,9 @@
 
 import com.android.tools.r8.NeverClassInline;
 import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.ir.optimize.Inliner.Reason;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.google.common.collect.ImmutableSet;
 import org.junit.Test;
 
 public class LargeConstructorsMergingTest extends HorizontalClassMergingTestBase {
@@ -24,7 +26,11 @@
     testForR8(parameters.getBackend())
         .addInnerClasses(getClass())
         .addKeepMainRule(Main.class)
-        .addOptionsModification(options -> options.testing.verificationSizeLimitInBytesOverride = 4)
+        .addOptionsModification(
+            options -> {
+              options.testing.validInliningReasons = ImmutableSet.of(Reason.FORCE);
+              options.testing.verificationSizeLimitInBytesOverride = 4;
+            })
         .enableNeverClassInliningAnnotations()
         .setMinApi(parameters)
         .addHorizontallyMergedClassesInspector(
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/NoHorizontalClassMergingTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/NoHorizontalClassMergingTest.java
index 5074df0..82517ce 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/NoHorizontalClassMergingTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/NoHorizontalClassMergingTest.java
@@ -8,6 +8,7 @@
 import static org.hamcrest.MatcherAssert.assertThat;
 
 import com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.NeverInline;
 import com.android.tools.r8.NoHorizontalClassMerging;
 import com.android.tools.r8.TestParameters;
 import org.junit.Test;
@@ -22,6 +23,7 @@
     testForR8(parameters.getBackend())
         .addInnerClasses(getClass())
         .addKeepMainRule(Main.class)
+        .enableInliningAnnotations()
         .enableNoHorizontalClassMergingAnnotations()
         .enableNeverClassInliningAnnotations()
         .setMinApi(parameters)
@@ -36,6 +38,7 @@
 
   @NeverClassInline
   public static class A {
+    @NeverInline
     public A() {
       System.out.println("a");
     }
@@ -44,6 +47,7 @@
   @NeverClassInline
   @NoHorizontalClassMerging
   public static class B {
+    @NeverInline
     public B(String v) {
       System.out.println(v);
     }
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/PackagePrivateMemberAccessTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/PackagePrivateMemberAccessTest.java
index 2588a63..def0d9e 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/PackagePrivateMemberAccessTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/PackagePrivateMemberAccessTest.java
@@ -12,6 +12,8 @@
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.classmerging.horizontal.testclasses.A;
 import com.android.tools.r8.classmerging.horizontal.testclasses.B;
+import com.android.tools.r8.ir.optimize.Inliner.Reason;
+import com.google.common.collect.ImmutableSet;
 import org.junit.Test;
 
 public class PackagePrivateMemberAccessTest extends HorizontalClassMergingTestBase {
@@ -26,6 +28,8 @@
         .addInnerClasses(getClass())
         .addProgramClasses(A.class, B.class)
         .addKeepMainRule(Main.class)
+        .addOptionsModification(
+            options -> options.testing.validInliningReasons = ImmutableSet.of(Reason.FORCE))
         .enableConstantArgumentAnnotations()
         .enableInliningAnnotations()
         .enableNeverClassInliningAnnotations()
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/PackagePrivateMembersAccessedTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/PackagePrivateMembersAccessedTest.java
index 6dea00f..0d09fd4 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/PackagePrivateMembersAccessedTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/PackagePrivateMembersAccessedTest.java
@@ -8,6 +8,7 @@
 import static org.hamcrest.MatcherAssert.assertThat;
 
 import com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.NeverInline;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.classmerging.horizontal.testclasses.C;
 import com.android.tools.r8.classmerging.horizontal.testclasses.D;
@@ -42,6 +43,7 @@
 
   @NeverClassInline
   public static class E {
+    @NeverInline
     public E(int v) {
       System.out.println(v);
     }
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/PinnedClassMemberTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/PinnedClassMemberTest.java
index bf43463..005c3d4 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/PinnedClassMemberTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/PinnedClassMemberTest.java
@@ -8,6 +8,7 @@
 import static org.hamcrest.MatcherAssert.assertThat;
 
 import com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.NeverInline;
 import com.android.tools.r8.TestParameters;
 import org.junit.Test;
 
@@ -22,6 +23,7 @@
         .addInnerClasses(getClass())
         .addKeepMainRule(Main.class)
         .addKeepRules("-keepclassmembers class " + B.class.getTypeName() + " { void foo(); }")
+        .enableInliningAnnotations()
         .enableNeverClassInliningAnnotations()
         .setMinApi(parameters)
         .run(parameters.getRuntime(), Main.class)
@@ -35,6 +37,7 @@
 
   @NeverClassInline
   public static class A {
+    @NeverInline
     public A() {
       System.out.println("a");
     }
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/SynchronizedClassesTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/SynchronizedClassesTest.java
index 3798008..6e898e9 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/SynchronizedClassesTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/SynchronizedClassesTest.java
@@ -12,7 +12,9 @@
 import com.android.tools.r8.NeverClassInline;
 import com.android.tools.r8.NeverInline;
 import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.ir.optimize.Inliner.Reason;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.google.common.collect.ImmutableSet;
 import org.junit.Test;
 
 public class SynchronizedClassesTest extends HorizontalClassMergingTestBase {
@@ -31,6 +33,8 @@
                     .assertMergedInto(C.class, A.class)
                     .assertMergedInto(D.class, B.class)
                     .assertNoOtherClassesMerged())
+        .addOptionsModification(
+            options -> options.testing.validInliningReasons = ImmutableSet.of(Reason.FORCE))
         .enableConstantArgumentAnnotations()
         .enableInliningAnnotations()
         .enableNeverClassInliningAnnotations()
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/TreeFixerConstructorCollisionTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/TreeFixerConstructorCollisionTest.java
index 30b7679..79b5725 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/TreeFixerConstructorCollisionTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/TreeFixerConstructorCollisionTest.java
@@ -24,6 +24,11 @@
     testForR8(parameters.getBackend())
         .addInnerClasses(getClass())
         .addKeepMainRule(Main.class)
+        .addHorizontallyMergedClassesInspector(
+            inspector ->
+                inspector.assertIsCompleteMergeGroup(A.class, B.class).assertNoOtherClassesMerged())
+        .addOptionsModification(
+            options -> options.inlinerOptions().setEnableConstructorInlining(false))
         .enableInliningAnnotations()
         .enableNeverClassInliningAnnotations()
         .enableNoHorizontalClassMergingAnnotations()
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/VerticallyMergedClassTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/VerticallyMergedClassTest.java
index be027c7..db1c530 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/VerticallyMergedClassTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/VerticallyMergedClassTest.java
@@ -54,6 +54,8 @@
 
   @NeverClassInline
   public static class C {
+
+    @NeverInline
     public C() {
       System.out.println("c");
     }
diff --git a/src/test/java/com/android/tools/r8/classmerging/vertical/ForceInlineConstructorWithRetargetedLibMemberTest.java b/src/test/java/com/android/tools/r8/classmerging/vertical/ForceInlineConstructorWithRetargetedLibMemberTest.java
index 1730dc8..4c11361 100644
--- a/src/test/java/com/android/tools/r8/classmerging/vertical/ForceInlineConstructorWithRetargetedLibMemberTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/vertical/ForceInlineConstructorWithRetargetedLibMemberTest.java
@@ -11,6 +11,7 @@
 import static org.hamcrest.MatcherAssert.assertThat;
 
 import com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.NeverInline;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.desugar.desugaredlibrary.DesugaredLibraryTestBase;
 import com.android.tools.r8.desugar.desugaredlibrary.test.CompilationSpecification;
@@ -53,6 +54,7 @@
         .addInnerClasses(getClass())
         .addKeepMainRule(TestClass.class)
         .enableNeverClassInliningAnnotations()
+        .enableInliningAnnotations()
         .addVerticallyMergedClassesInspector(
             inspector -> inspector.assertMergedIntoSubtype(A.class))
         .compile()
@@ -80,6 +82,7 @@
   @NeverClassInline
   static class B extends A {
 
+    @NeverInline
     B(String[] args) {
       super(args);
     }
diff --git a/src/test/java/com/android/tools/r8/classmerging/vertical/VerticalClassMergerTest.java b/src/test/java/com/android/tools/r8/classmerging/vertical/VerticalClassMergerTest.java
index 5264b18..b6c66b6 100644
--- a/src/test/java/com/android/tools/r8/classmerging/vertical/VerticalClassMergerTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/vertical/VerticalClassMergerTest.java
@@ -256,6 +256,7 @@
     Path[] programFiles =
         new Path[] {
           CF_DIR.resolve("NeverPropagateValue.class"),
+          CF_DIR.resolve("NoRedundantFieldLoadElimination.class"),
           CF_DIR.resolve("ConflictInGeneratedNameTest.class"),
           CF_DIR.resolve("ConflictInGeneratedNameTest$A.class"),
           CF_DIR.resolve("ConflictInGeneratedNameTest$B.class")
diff --git a/src/test/java/com/android/tools/r8/compilerapi/CompilerApiTestCollection.java b/src/test/java/com/android/tools/r8/compilerapi/CompilerApiTestCollection.java
index c59fda4..9535f30 100644
--- a/src/test/java/com/android/tools/r8/compilerapi/CompilerApiTestCollection.java
+++ b/src/test/java/com/android/tools/r8/compilerapi/CompilerApiTestCollection.java
@@ -62,10 +62,11 @@
           ProguardKeepRuleDiagnosticsApiTest.ApiTest.class,
           SyntheticContextsConsumerTest.ApiTest.class,
           ExtractMarkerApiTest.ApiTest.class,
-          PartitionMapCommandTest.ApiTest.class);
+          PartitionMapCommandTest.ApiTest.class,
+          CancelCompilationCheckerTest.ApiTest.class);
 
   private static final List<Class<? extends CompilerApiTest>> CLASSES_PENDING_BINARY_COMPATIBILITY =
-      ImmutableList.of(CancelCompilationCheckerTest.ApiTest.class);
+      ImmutableList.of();
 
   private final TemporaryFolder temp;
 
diff --git a/src/test/java/com/android/tools/r8/d8/WriteToArchiveTest.java b/src/test/java/com/android/tools/r8/d8/WriteToArchiveTest.java
deleted file mode 100644
index 6df4c3d..0000000
--- a/src/test/java/com/android/tools/r8/d8/WriteToArchiveTest.java
+++ /dev/null
@@ -1,44 +0,0 @@
-// Copyright (c) 2017, 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.d8;
-
-import com.android.tools.r8.D8;
-import com.android.tools.r8.D8Command;
-import com.android.tools.r8.OutputMode;
-import com.android.tools.r8.ToolHelper;
-import java.nio.file.Paths;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.rules.TemporaryFolder;
-
-// Test that we allow writing to zip and jar archives.
-public class WriteToArchiveTest {
-
-  private static final String input = ToolHelper.EXAMPLES_BUILD_DIR + "/trivial.jar";
-
-  @Rule public TemporaryFolder zipFolder = ToolHelper.getTemporaryFolderForTest();
-  @Rule public TemporaryFolder jarFolder = ToolHelper.getTemporaryFolderForTest();
-
-  @Test
-  public void writeToZip() throws Exception {
-    D8Command command =
-        D8Command.builder()
-            .addProgramFiles(Paths.get(input))
-            .setOutput(
-                Paths.get(zipFolder.getRoot().toString() + "/output.zip"), OutputMode.DexIndexed)
-            .build();
-    D8.run(command);
-  }
-
-  @Test
-  public void writeToJar() throws Exception {
-    D8Command command =
-        D8Command.builder()
-            .addProgramFiles(Paths.get(input))
-            .setOutput(
-                Paths.get(jarFolder.getRoot().toString() + "/output.jar"), OutputMode.DexIndexed)
-            .build();
-    D8.run(command);
-  }
-}
diff --git a/src/test/java/com/android/tools/r8/debug/ExamplesDebugTest.java b/src/test/java/com/android/tools/r8/debug/ExamplesDebugTest.java
index 88c7358..46a83b7 100644
--- a/src/test/java/com/android/tools/r8/debug/ExamplesDebugTest.java
+++ b/src/test/java/com/android/tools/r8/debug/ExamplesDebugTest.java
@@ -60,131 +60,6 @@
   }
 
   @Test
-  public void testRegAlloc() throws Exception {
-    testDebugging("regalloc", "RegAlloc");
-  }
-
-  @Test
-  public void testReturns() throws Exception {
-    testDebugging("returns", "Returns");
-  }
-
-  @Test
-  public void testStaticField() throws Exception {
-    // TODO(b/79671093): D8 has different line number info during stepping.
-    testDebuggingJvmOnly("staticfield", "StaticField");
-  }
-
-  @Test
-  public void testStringBuilding() throws Exception {
-    testDebugging("stringbuilding", "StringBuilding");
-  }
-
-  @Test
-  public void testSwitches() throws Exception {
-    testDebugging("switches", "Switches");
-  }
-
-  @Test
-  public void testSync() throws Exception {
-    // TODO(b/79671093): Line number mismatch in D8.
-    testDebuggingJvmOnly("sync", "Sync");
-  }
-
-  @Test
-  public void testThrowing() throws Exception {
-    // TODO(b/79671093): D8 has unexpected variables (this in throwing.c <init>).
-    testDebuggingJvmOnly("throwing", "Throwing");
-  }
-
-  @Test
-  public void testTrivial() throws Exception {
-    testDebugging("trivial", "Trivial");
-  }
-
-  @Test
-  public void testTryCatch() throws Exception {
-    testDebugging("trycatch", "TryCatch");
-  }
-
-  @Test
-  public void testNestedTryCatches() throws Exception {
-    testDebugging("nestedtrycatches", "NestedTryCatches");
-  }
-
-  @Test
-  public void testTryCatchMany() throws Exception {
-    testDebugging("trycatchmany", "TryCatchMany");
-  }
-
-  @Test
-  public void testRegress() throws Exception {
-    testDebugging("regress", "Regress");
-  }
-
-  @Test
-  public void testRegress2() throws Exception {
-    testDebugging("regress2", "Regress2");
-  }
-
-  @Test
-  public void testRegress37726195() throws Exception {
-    testDebugging("regress_37726195", "Regress");
-  }
-
-  @Test
-  public void testRegress37658666() throws Exception {
-    testDebugging("regress_37658666", "Regress");
-  }
-
-  @Test
-  public void testRegress37875803() throws Exception {
-    testDebugging("regress_37875803", "Regress");
-  }
-
-  @Test
-  public void testRegress37955340() throws Exception {
-    testDebugging("regress_37955340", "Regress");
-  }
-
-  @Test
-  public void testRegress62300145() throws Exception {
-    // TODO(b/67936230): Executes differently for Java 8 and 9, so don't compare to DEX output.
-    testDebuggingJvmOnly("regress_62300145", "Regress");
-  }
-
-  @Test
-  public void testRegress64881691() throws Exception {
-    testDebugging("regress_64881691", "Regress");
-  }
-
-  @Test
-  public void testRegress65104300() throws Exception {
-    testDebugging("regress_65104300", "Regress");
-  }
-
-  @Ignore("TODO(b/79671093): This test seems to take forever")
-  @Test
-  public void testRegress70703087() throws Exception {
-    testDebugging("regress_70703087", "Test");
-  }
-
-  @Test
-  public void testRegress70736958() throws Exception {
-    testDebugging("regress_70736958", "Test");
-  }
-
-  @Test
-  public void testRegress70737019() throws Exception {
-    testDebugging("regress_70737019", "Test");
-  }
-
-  @Test
-  public void testRegress72361252() throws Exception {
-    testDebugging("regress_72361252", "Test");
-  }
-
-  @Test
   public void testMemberrebinding2() throws Exception {
     testDebugging("memberrebinding2", "Memberrebinding");
   }
@@ -199,23 +74,6 @@
     testDebugging("minification", "Minification");
   }
 
-  @Test
-  public void testEnclosingmethod() throws Exception {
-    testDebugging("enclosingmethod", "Main");
-  }
-
-  @Test
-  public void testEnclosingmethod_proguarded() throws Exception {
-    // TODO(b/79671093): We don't match JVM's behavior on this example.
-    testDebuggingJvmOutputOnly("enclosingmethod_proguarded", "Main");
-  }
-
-  @Test
-  public void testSwitchmaps() throws Exception {
-    // TODO(b/79671093): D8 has different line number info during stepping.
-    testDebuggingJvmOnly("switchmaps", "Switches");
-  }
-
   private void testDebugging(String pkg, String clazz) throws Exception {
     init(pkg, clazz)
         .add("Input", input())
@@ -226,19 +84,6 @@
         .compare();
   }
 
-  private void testDebuggingJvmOnly(String pkg, String clazz) throws Exception {
-    init(pkg, clazz)
-        .add("Input", input())
-        .add("R8/CfSourceCode", r8cf())
-        .compare();
-  }
-
-  private void testDebuggingJvmOutputOnly(String pkg, String clazz) throws Exception {
-    init(pkg, clazz)
-        .add("R8/CfSourceCode", r8cf())
-        .run();
-  }
-
   private DebugStreamComparator init(String pkg, String clazz) {
     // TODO(b/199700280): Reenable on 12.0.0 when we have the libjdwp.so file include and the flags
     // fixed.
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 bb58476..c7d877e 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
@@ -27,8 +27,8 @@
 
   @Parameters(name = "{0}")
   public static TestParametersCollection data() {
-    // TODO(b/218293990): Right now the JDK 18 tests are built with -target 17, as our Gradle
-    //  version does not know of -target 18.
+    // TODO(b/218293990): Right now the JDK 20 tests are built with -target 17, as our Gradle
+    //  version does not know of -target 20.
     return getTestParameters()
         .withCfRuntimesStartingFromIncluding(CfVm.JDK17)
         .withDexRuntimes()
diff --git a/src/test/java/com/android/tools/r8/desugar/records/RecordBlogTest.java b/src/test/java/com/android/tools/r8/desugar/records/RecordBlogTest.java
new file mode 100644
index 0000000..bcc90c5
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/records/RecordBlogTest.java
@@ -0,0 +1,136 @@
+// Copyright (c) 2023, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.desugar.records;
+
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.R8FullTestBuilder;
+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.AndroidApiLevel;
+import com.android.tools.r8.utils.StringUtils;
+import com.google.common.collect.ImmutableMap;
+import java.nio.file.Path;
+import java.util.IdentityHashMap;